From dbfb99d901b958c3c893cbad4f3142f0a8edb78c Mon Sep 17 00:00:00 2001 From: HeaTTap Date: Mon, 27 Jul 2026 07:28:11 +0000 Subject: [PATCH 1/2] fix(fossil): narrow empty exception handlers in forum adapter (#954) --- tools/fossil/src/magpie_fossil/forum.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/fossil/src/magpie_fossil/forum.py b/tools/fossil/src/magpie_fossil/forum.py index 15e7fff7f..5025225d1 100644 --- a/tools/fossil/src/magpie_fossil/forum.py +++ b/tools/fossil/src/magpie_fossil/forum.py @@ -85,7 +85,7 @@ def list_forum_threads(repo_path: Path) -> list[dict[str, Any]]: "date": parsed["date"], } ) - except Exception as exc: + except FossilError as exc: import sys print( @@ -134,7 +134,7 @@ def read_forum_thread(repo_path: Path, thread_uuid: str) -> list[dict[str, Any]] "body": parsed["body"], } ) - except Exception as exc: + except FossilError as exc: import sys print( From a3de2ac40c211514c09492958ac51a7e0d435a3e Mon Sep 17 00:00:00 2001 From: HeaTTap Date: Sat, 1 Aug 2026 15:28:24 +0000 Subject: [PATCH 2/2] fix(fossil): catch UnicodeDecodeError and add test coverage for corrupt skipping and exception propagation --- tools/fossil/src/magpie_fossil/forum.py | 4 +- tools/fossil/tests/test_fossil.py | 76 +++++++++++++++++++++++++ 2 files changed, 78 insertions(+), 2 deletions(-) diff --git a/tools/fossil/src/magpie_fossil/forum.py b/tools/fossil/src/magpie_fossil/forum.py index 5025225d1..9375840aa 100644 --- a/tools/fossil/src/magpie_fossil/forum.py +++ b/tools/fossil/src/magpie_fossil/forum.py @@ -85,7 +85,7 @@ def list_forum_threads(repo_path: Path) -> list[dict[str, Any]]: "date": parsed["date"], } ) - except FossilError as exc: + except (FossilError, UnicodeDecodeError) as exc: import sys print( @@ -134,7 +134,7 @@ def read_forum_thread(repo_path: Path, thread_uuid: str) -> list[dict[str, Any]] "body": parsed["body"], } ) - except FossilError as exc: + except (FossilError, UnicodeDecodeError) as exc: import sys print( diff --git a/tools/fossil/tests/test_fossil.py b/tools/fossil/tests/test_fossil.py index e2abcd0b1..12cdbccec 100644 --- a/tools/fossil/tests/test_fossil.py +++ b/tools/fossil/tests/test_fossil.py @@ -171,6 +171,82 @@ def test_read_forum_thread(mock_query_db: MagicMock, mock_run_fossil: MagicMock) assert posts[0]["body"] == "Post content" +@patch("magpie_fossil.forum.run_fossil") +@patch("magpie_fossil.forum.query_db") +def test_list_forum_threads_skips_corrupt_artifact( + mock_query_db: MagicMock, mock_run_fossil: MagicMock, capsys: pytest.CaptureFixture[str] +) -> None: + mock_query_db.return_value = [{"root_uuid": "uuid1"}, {"root_uuid": "uuid2"}] + mock_run_fossil.side_effect = [ + FossilError("missing artifact"), + "U bob\nD 2026-07-02T12:00:00.000\nW Thread 2\nZ checksum\nBody content", + ] + threads = list_forum_threads(Path("repo.fossil")) + assert len(threads) == 1 + assert threads[0]["title"] == "Thread 2" + captured = capsys.readouterr() + assert "skipped corrupt or missing forum post artifact 'uuid1'" in captured.err + + +@patch("magpie_fossil.forum.run_fossil") +@patch("magpie_fossil.forum.query_db") +def test_list_forum_threads_skips_unicode_decode_error( + mock_query_db: MagicMock, mock_run_fossil: MagicMock, capsys: pytest.CaptureFixture[str] +) -> None: + mock_query_db.return_value = [{"root_uuid": "uuid1"}, {"root_uuid": "uuid2"}] + mock_run_fossil.side_effect = [ + UnicodeDecodeError("utf-8", b"\xff", 0, 1, "invalid start byte"), + "U bob\nD 2026-07-02T12:00:00.000\nW Thread 2\nZ checksum\nBody content", + ] + threads = list_forum_threads(Path("repo.fossil")) + assert len(threads) == 1 + assert threads[0]["title"] == "Thread 2" + captured = capsys.readouterr() + assert "skipped corrupt or missing forum post artifact 'uuid1'" in captured.err + + +@patch("magpie_fossil.forum.run_fossil") +@patch("magpie_fossil.forum.query_db") +def test_list_forum_threads_propagates_unexpected_exception( + mock_query_db: MagicMock, mock_run_fossil: MagicMock +) -> None: + mock_query_db.return_value = [{"root_uuid": "uuid1"}] + mock_run_fossil.side_effect = RuntimeError("unexpected database error") + with pytest.raises(RuntimeError, match="unexpected database error"): + list_forum_threads(Path("repo.fossil")) + + +@patch("magpie_fossil.forum.run_fossil") +@patch("magpie_fossil.forum.query_db") +def test_read_forum_thread_skips_corrupt_artifact( + mock_query_db: MagicMock, mock_run_fossil: MagicMock, capsys: pytest.CaptureFixture[str] +) -> None: + mock_query_db.side_effect = [ + [{"rid": 10}], + [{"uuid": "uuid1", "parent_uuid": None}, {"uuid": "uuid2", "parent_uuid": "uuid1"}], + ] + mock_run_fossil.side_effect = [ + UnicodeDecodeError("utf-8", b"\xff", 0, 1, "invalid start byte"), + "U bob\nD 2026-07-02T12:00:00.000\nW Thread 1\nZ checksum\nPost content", + ] + posts = read_forum_thread(Path("repo.fossil"), "uuid1") + assert len(posts) == 1 + assert posts[0]["post_uuid"] == "uuid2" + captured = capsys.readouterr() + assert "skipped corrupt or missing forum post artifact 'uuid1'" in captured.err + + +@patch("magpie_fossil.forum.run_fossil") +@patch("magpie_fossil.forum.query_db") +def test_read_forum_thread_propagates_unexpected_exception( + mock_query_db: MagicMock, mock_run_fossil: MagicMock +) -> None: + mock_query_db.side_effect = [[{"rid": 10}], [{"uuid": "uuid1", "parent_uuid": None}]] + mock_run_fossil.side_effect = RuntimeError("unexpected system error") + with pytest.raises(RuntimeError, match="unexpected system error"): + read_forum_thread(Path("repo.fossil"), "uuid1") + + # -- CLI and client tests --------------------------------------------------