Skip to content

Commit a06779e

Browse files
committed
sessions: improve replay harness reliability
The replay test now writes generated reports to a temporary directory and compares them strictly against the committed golden report, preventing tests from modifying the source tree or silently accepting report drift. Mock Redis query behavior now covers string and hash values in addition to lists. Summary retry testing also verifies that the in-memory session has valid summary metadata after persistence fails and before the retry succeeds. Fixes #89 RELEASE NOTES: Improved Session, Memory, and Summary replay consistency test reliability.
1 parent c5e069d commit a06779e

1 file changed

Lines changed: 50 additions & 8 deletions

File tree

tests/sessions/test_replay_consistency.py

Lines changed: 50 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@
3737
from trpc_agent_sdk.sessions._session_summarizer import SessionSummarizer
3838
from trpc_agent_sdk.sessions._sql_session_service import SqlSessionService
3939
from trpc_agent_sdk.sessions._types import SessionServiceConfig
40+
from trpc_agent_sdk.storage import RedisCommand
41+
from trpc_agent_sdk.storage import RedisCondition
4042
from trpc_agent_sdk.types import Content, EventActions, FunctionCall, FunctionResponse, Part
4143

4244
_APP_NAME = "replay-app"
@@ -97,7 +99,20 @@ async def query(self, _session: Any, pattern: str, conditions: Any) -> list[tupl
9799
keys = self._keys(pattern)
98100
if conditions.limit > 0:
99101
keys = keys[:conditions.limit]
100-
return [(key, list(self._lists[key])) for key in keys if key in self._lists]
102+
results = []
103+
for key in keys:
104+
if key in self._strings and self._strings[key]:
105+
value = self._strings[key]
106+
try:
107+
value = json.loads(value)
108+
except (json.JSONDecodeError, TypeError):
109+
pass
110+
results.append((key, value))
111+
elif key in self._hashes and self._hashes[key]:
112+
results.append((key, dict(self._hashes[key])))
113+
elif key in self._lists and self._lists[key]:
114+
results.append((key, list(self._lists[key])))
115+
return results
101116

102117
async def expire(self, _session: Any, _command: Any) -> None:
103118
return None
@@ -354,6 +369,9 @@ async def _replay_case(case: dict[str, Any]) -> dict[str, dict[str, Any]]:
354369
):
355370
with pytest.raises(RuntimeError, match="injected summary persistence failure"):
356371
await _create_summary(session, session_service, operation, summary_version, timestamp)
372+
pending_summary = next(event for event in session.events if event.is_summary_event())
373+
assert pending_summary.custom_metadata["summary_session_id"] == session.id
374+
assert pending_summary.custom_metadata["summary_version"] == summary_version
357375
await original_update(session)
358376
elif operation["op"] == "store_memory_retry":
359377
original_store = memory_service.store_session
@@ -455,8 +473,8 @@ def _inject_case_difference(case_id: str, snapshot: dict[str, Any]) -> tuple[dic
455473
return injected, expected_path
456474

457475

458-
def _write_report(results: list[dict[str, Any]]) -> None:
459-
_REPORT_PATH.write_text(json.dumps({"cases": results}, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
476+
def _write_report(results: list[dict[str, Any]], report_path: Path) -> None:
477+
report_path.write_text(json.dumps({"cases": results}, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
460478

461479

462480
def _compare_snapshot(
@@ -516,13 +534,18 @@ async def test_single_turn_matches_default_backends(self):
516534
assert result["status"] == "match"
517535
assert [item["backend"] for item in result["comparisons"]] == _expected_comparison_backends()
518536

519-
async def test_all_public_cases_match_and_write_report(self):
537+
async def test_all_public_cases_match_and_write_report(self, monkeypatch, tmp_path):
538+
monkeypatch.delenv("REPLAY_LIGHTWEIGHT", raising=False)
539+
monkeypatch.delenv("REPLAY_SQL_URL", raising=False)
540+
monkeypatch.delenv("REPLAY_REDIS_URL", raising=False)
520541
results = [await _compare_case(case) for case in _load_cases()]
521-
_write_report(results)
542+
report_path = tmp_path / _REPORT_PATH.name
543+
_write_report(results, report_path)
522544
expected = {case["id"]: "match" for case in _load_cases()}
523545
assert {result["case_id"]: result["status"] for result in results} == expected
524-
report = json.loads(_REPORT_PATH.read_text(encoding="utf-8"))
546+
report = json.loads(report_path.read_text(encoding="utf-8"))
525547
assert len(report["cases"]) == 10
548+
assert report == json.loads(_REPORT_PATH.read_text(encoding="utf-8"))
526549

527550
async def test_all_public_cases_detect_injected_difference(self):
528551
for case in _load_cases():
@@ -552,6 +575,15 @@ async def test_lightweight_mode_runs_without_a_persistent_backend(self, monkeypa
552575

553576
class TestReplayComparison:
554577

578+
async def test_mock_redis_query_returns_string_and_hash_values(self):
579+
storage = _MockRedisStorage()
580+
await storage.execute_command(None, RedisCommand(method="set", args=("value:key", '"stored"')))
581+
await storage.execute_command(None, RedisCommand(method="hset", args=("hash:key", "field", "value")))
582+
583+
result = await storage.query(None, "*:key", RedisCondition())
584+
585+
assert result == [("hash:key", {"field": "value"}), ("value:key", "stored")]
586+
555587
def test_design_note_contains_150_to_300_chinese_characters(self):
556588
design = _DESIGN_PATH.read_text(encoding="utf-8")
557589
assert 150 <= len(re.findall(r"[\u4e00-\u9fff]", design)) <= 300
@@ -624,8 +656,18 @@ def test_allowed_difference_is_retained_in_report_data(self):
624656
def test_allowed_difference_includes_its_reason_in_the_report(self):
625657
comparison = _compare_snapshot(
626658
"sql",
627-
{"session_id": "replay-session", "state": {"backend": "a"}},
628-
{"session_id": "replay-session", "state": {"backend": "b"}},
659+
{
660+
"session_id": "replay-session",
661+
"state": {
662+
"backend": "a"
663+
}
664+
},
665+
{
666+
"session_id": "replay-session",
667+
"state": {
668+
"backend": "b"
669+
}
670+
},
629671
{"state.backend"},
630672
"Persistent backend stores this field differently.",
631673
)

0 commit comments

Comments
 (0)