[PATCH RFC 08/11] review: test backward revision discovery

Christian Brauner <[email protected]> Sat, 18 Jul 2026 00:37:44 +0200
Newsgroups org.kernel.linux.tools
Message-ID <[email protected]>
Cover discover_older_revisions() (records all previous versions with
an explicit wantvers, offline error, v1 no-op, nothing-new short
circuit without polling) and the tracker action wiring (menu entry
presence, worker invocation with the selected series).

Assisted-by: LLM
Signed-off-by: Christian Brauner (Amutable) <[email protected]>
---
 src/tests/test_review_tracking.py | 118 ++++++++++++++++++++++++++++++++++++++
 src/tests/test_tui_tracking.py    |  44 ++++++++++++++
 2 files changed, 162 insertions(+)

diff --git a/src/tests/test_review_tracking.py b/src/tests/test_review_tracking.py
index 5002aaa..b26933d 100644
--- a/src/tests/test_review_tracking.py
+++ b/src/tests/test_review_tracking.py
@@ -4895,3 +4895,121 @@ class TestRevisionAwareSyncHelpers:
         ).fetchone()[0]
         conn.close()
         assert rev_seen == 6
+
+
+def _series_msgs(base: str, rev: int, n: int = 2) -> list[EmailMessage]:
+    """Raw messages forming one n-patch series at the given revision."""
+    msgs = []
+    for i in range(1, n + 1):
+        msg = EmailMessage()
+        msg['Subject'] = f'[PATCH v{rev} {i}/{n}] {base}: part {i}'
+        msg['From'] = 'Author <[email protected]>'
+        msg['Date'] = 'Thu, 19 Mar 2026 08:51:12 +0530'
+        msg['Message-Id'] = f'<{base}-v{rev}-p{i}@example.com>'
+        msg.set_payload(_pos_diff(i))
+        msgs.append(msg)
+    return msgs
+
+
+class TestDiscoverOlderRevisions:
+    """The backward lore search records all previous versions."""
+
+    def test_records_all_previous_versions(
+        self, tmp_path: pytest.TempPathFactory, monkeypatch: pytest.MonkeyPatch
+    ) -> None:
+        conn = review_tracking.init_db('disc-all')
+        review_tracking.add_revision(conn, 'cid', 3, '[email protected]')
+        conn.close()
+        monkeypatch.setattr(b4, 'can_network', True)
+        monkeypatch.setattr(
+            review_tracking, '_fetch_thread_mbox_bytes', lambda msgid: b'raw'
+        )
+        base_msgs = _series_msgs('thing', 3)
+        monkeypatch.setattr(
+            b4, 'split_and_dedupe_pi_results', lambda raw: list(base_msgs)
+        )
+        seen_calls: list[tuple[int, Any]] = []
+
+        def _fake_extra(
+            msgs: list[EmailMessage],
+            direction: int = 1,
+            wantvers: Any = None,
+            nocache: bool = False,
+        ) -> list[EmailMessage]:
+            seen_calls.append((direction, wantvers))
+            return list(msgs) + _series_msgs('thing', 2) + _series_msgs('thing', 1)
+
+        monkeypatch.setattr(b4.mbox, 'get_extra_series', _fake_extra)
+        polled: list[str] = []
+        monkeypatch.setattr(
+            review_tracking,
+            'update_revision_message_counts',
+            lambda identifier, series_list, topdir=None: polled.append(
+                series_list[0]['change_id']
+            ),
+        )
+        series = {
+            'change_id': 'cid',
+            'revision': 3,
+            'message_id': '[email protected]',
+            'status': 'new',
+        }
+        result = review_tracking.discover_older_revisions('disc-all', series, '')
+        assert result == {'found': 2, 'revisions': [1, 2], 'error': None}
+        assert seen_calls == [(-1, [1, 2])]
+        assert polled == ['cid']
+        conn = review_tracking.get_db('disc-all')
+        revs = {r['revision'] for r in review_tracking.get_revisions(conn, 'cid')}
+        conn.close()
+        assert revs == {1, 2, 3}
+
+    def test_offline_reports_error(self, tmp_path: pytest.TempPathFactory) -> None:
+        review_tracking.init_db('disc-off').close()
+        series = {'change_id': 'cid', 'revision': 3, 'message_id': 'v3@x'}
+        result = review_tracking.discover_older_revisions('disc-off', series, '')
+        assert result == {'found': 0, 'revisions': [], 'error': 'offline'}
+
+    def test_v1_is_noop(
+        self, tmp_path: pytest.TempPathFactory, monkeypatch: pytest.MonkeyPatch
+    ) -> None:
+        review_tracking.init_db('disc-v1').close()
+
+        def _boom(msgid: str) -> bytes:
+            raise AssertionError('must not fetch for v1')
+
+        monkeypatch.setattr(review_tracking, '_fetch_thread_mbox_bytes', _boom)
+        series = {'change_id': 'cid', 'revision': 1, 'message_id': 'v1@x'}
+        result = review_tracking.discover_older_revisions('disc-v1', series, '')
+        assert result == {'found': 0, 'revisions': [], 'error': None}
+
+    def test_nothing_new_found(
+        self, tmp_path: pytest.TempPathFactory, monkeypatch: pytest.MonkeyPatch
+    ) -> None:
+        conn = review_tracking.init_db('disc-none')
+        review_tracking.add_revision(conn, 'cid', 3, '[email protected]')
+        conn.close()
+        monkeypatch.setattr(b4, 'can_network', True)
+        monkeypatch.setattr(
+            review_tracking, '_fetch_thread_mbox_bytes', lambda msgid: b'raw'
+        )
+        base_msgs = _series_msgs('thing', 3)
+        monkeypatch.setattr(
+            b4, 'split_and_dedupe_pi_results', lambda raw: list(base_msgs)
+        )
+        monkeypatch.setattr(
+            b4.mbox,
+            'get_extra_series',
+            lambda msgs, direction=1, wantvers=None, nocache=False: list(msgs),
+        )
+
+        def _no_poll(*args: Any, **kw: Any) -> None:
+            raise AssertionError('poller must not run when nothing was found')
+
+        monkeypatch.setattr(review_tracking, 'update_revision_message_counts', _no_poll)
+        series = {
+            'change_id': 'cid',
+            'revision': 3,
+            'message_id': '[email protected]',
+        }
+        result = review_tracking.discover_older_revisions('disc-none', series, '')
+        assert result == {'found': 0, 'revisions': [], 'error': None}
diff --git a/src/tests/test_tui_tracking.py b/src/tests/test_tui_tracking.py
index eb1105c..e9d646e 100644
--- a/src/tests/test_tui_tracking.py
+++ b/src/tests/test_tui_tracking.py
@@ -4137,3 +4137,47 @@ class TestDoTakeMergeConflict:
         common_dir = b4.git_get_common_dir(gitdir)
         assert common_dir is not None
         assert not os.path.isdir(os.path.join(common_dir, 'b4-take-worktree'))
+
+
+class TestDiscoverOlderAction:
+    """The 'Find older revisions' action wires into the discovery seam."""
+
+    @pytest.mark.asyncio
+    async def test_action_menu_offers_discover(self, tmp_path: pathlib.Path) -> None:
+        _seed_db(
+            'test-discover-menu',
+            [{'change_id': 'cid-d', 'revision': 3, 'status': 'new'}],
+        )
+        app = TrackingApp('test-discover-menu')
+        async with app.run_test(size=(120, 30)) as pilot:
+            await pilot.pause()
+            await pilot.press('a')
+            await pilot.pause()
+            keys = [key for key, _label in app.screen._actions]
+            assert 'discover' in keys
+
+    @pytest.mark.asyncio
+    async def test_action_runs_discovery(self, tmp_path: pathlib.Path) -> None:
+        _seed_db(
+            'test-discover-run',
+            [{'change_id': 'cid-d', 'revision': 3, 'status': 'new'}],
+        )
+        calls: List[Tuple[str, Optional[str]]] = []
+
+        def _fake_discover(
+            identifier: str,
+            series: Dict[str, Any],
+            linkmask: str,
+            topdir: Optional[str] = None,
+        ) -> Dict[str, Any]:
+            calls.append((identifier, series.get('change_id')))
+            return {'found': 2, 'revisions': [1, 2], 'error': None}
+
+        app = TrackingApp('test-discover-run')
+        with patch.object(tracking, 'discover_older_revisions', _fake_discover):
+            async with app.run_test(size=(120, 30)) as pilot:
+                await pilot.pause()
+                app.action_discover_older()
+                await app.workers.wait_for_complete()
+                await pilot.pause()
+        assert calls == [('test-discover-run', 'cid-d')]

-- 
2.53.0