From b149a975c6417975d9dbc596bada4554e7b42198 Mon Sep 17 00:00:00 2001 From: nesquena-hermes Date: Thu, 25 Jun 2026 02:37:35 +0000 Subject: [PATCH] fix(#4842): update #4385 archive tests to real sidecar files + document stat-gate assumption MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Opus gate finding (BLOCKING): the two #4385 archive-reappears regression tests mocked Session.load_metadata_only to return an archived stub WITHOUT writing a real sidecar file, and never set SESSION_DIR. The new stat-gate in _state_projection_sidecar_metadata short-circuits on SESSION_DIR/{sid}.json before consulting metadata, so those mocks were never reached -> archived=False. This is NOT a production regression (in prod, load_metadata_only reads exactly SESSION_DIR/{sid}.json and returns None when absent, so archived metadata only exists when the file does — in which case the stat() succeeds and behavior is identical). The divergence existed only at the tests' mock boundary. Fix: both tests now write a real archived sidecar JSON under a monkeypatched SESSION_DIR, exercising the actual production read path (strictly better coverage than mocking load_metadata_only). Also documented the SESSION_DIR-as-sole-source assumption on the cache function per Opus's forward-looking note. --- api/models.py | 5 +++ .../test_issue4385_cron_archive_reappears.py | 40 +++++++++++-------- 2 files changed, 29 insertions(+), 16 deletions(-) diff --git a/api/models.py b/api/models.py index b53cd29d..c03ab058 100644 --- a/api/models.py +++ b/api/models.py @@ -4313,6 +4313,11 @@ def _state_projection_sidecar_metadata(sid: str) -> dict: (#4842). A rename/archive/edit bumps the signature and invalidates just that entry, so a stale title/archived flag is impossible without re-reading. Returns a COPY so callers can't mutate the cached dict. + + NOTE: this stat-gates on ``SESSION_DIR / f'{sid}.json'`` because that file is + ``Session.load_metadata_only``'s sole source for title+archived. If that ever + stops being true (metadata moves to another store), this gate would short- + circuit before the real source — update both together. """ default = {"title": None, "archived": False} if not is_safe_session_id(sid): diff --git a/tests/test_issue4385_cron_archive_reappears.py b/tests/test_issue4385_cron_archive_reappears.py index bf1fa547..76384521 100644 --- a/tests/test_issue4385_cron_archive_reappears.py +++ b/tests/test_issue4385_cron_archive_reappears.py @@ -109,16 +109,22 @@ def test_cron_state_projection_preserves_archived_sidecar(monkeypatch, tmp_path) (sid, "Cron Session", "test-model", 1, 20, "cron", "cron"), ) - class ArchivedSidecar: - title = "Cron Session" - archived = True - - monkeypatch.setattr( - models.Session, - "load_metadata_only", - staticmethod(lambda candidate: ArchivedSidecar() if candidate == sid else None), + # Write a real archived sidecar JSON under a patched SESSION_DIR so the + # projection's sidecar read exercises the production path (the #4842 perf + # fix stat-gates on SESSION_DIR/{sid}.json before consulting metadata, so a + # mock of load_metadata_only without a real file would never be reached — + # which mirrors production, where archived metadata only exists when the + # file does). + session_dir = tmp_path / "sessions" + session_dir.mkdir() + (session_dir / f"{sid}.json").write_text( + '{"session_id": "%s", "title": "Cron Session", "created_at": 1.0,' + ' "updated_at": 2.0, "archived": true, "messages": []}' % sid, + encoding="utf-8", ) + monkeypatch.setattr(models, "SESSION_DIR", session_dir) monkeypatch.setattr(models, "ensure_cron_project", lambda: "cron-project") + models.clear_sidecar_metadata_cache() rows = models._load_cli_sessions_uncached( tmp_path, @@ -195,15 +201,17 @@ def test_webhook_state_projection_preserves_archived_sidecar(monkeypatch, tmp_pa (sid,), ) - class ArchivedWebhookSidecar: - title = "Webhook Session" - archived = True - - monkeypatch.setattr( - models.Session, - "load_metadata_only", - staticmethod(lambda candidate: ArchivedWebhookSidecar() if candidate == sid else None), + # Real archived sidecar JSON under a patched SESSION_DIR (see the cron test + # above — the #4842 stat-gate requires the file to exist, matching prod). + session_dir = tmp_path / "sessions" + session_dir.mkdir() + (session_dir / f"{sid}.json").write_text( + '{"session_id": "%s", "title": "Webhook Session", "created_at": 1.0,' + ' "updated_at": 2.0, "archived": true, "messages": []}' % sid, + encoding="utf-8", ) + monkeypatch.setattr(models, "SESSION_DIR", session_dir) + models.clear_sidecar_metadata_cache() rows = models._load_cli_sessions_uncached( tmp_path,