|
37 | 37 | from trpc_agent_sdk.sessions._session_summarizer import SessionSummarizer |
38 | 38 | from trpc_agent_sdk.sessions._sql_session_service import SqlSessionService |
39 | 39 | from trpc_agent_sdk.sessions._types import SessionServiceConfig |
| 40 | +from trpc_agent_sdk.storage import RedisCommand |
| 41 | +from trpc_agent_sdk.storage import RedisCondition |
40 | 42 | from trpc_agent_sdk.types import Content, EventActions, FunctionCall, FunctionResponse, Part |
41 | 43 |
|
42 | 44 | _APP_NAME = "replay-app" |
@@ -97,7 +99,20 @@ async def query(self, _session: Any, pattern: str, conditions: Any) -> list[tupl |
97 | 99 | keys = self._keys(pattern) |
98 | 100 | if conditions.limit > 0: |
99 | 101 | 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 |
101 | 116 |
|
102 | 117 | async def expire(self, _session: Any, _command: Any) -> None: |
103 | 118 | return None |
@@ -354,6 +369,9 @@ async def _replay_case(case: dict[str, Any]) -> dict[str, dict[str, Any]]: |
354 | 369 | ): |
355 | 370 | with pytest.raises(RuntimeError, match="injected summary persistence failure"): |
356 | 371 | 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 |
357 | 375 | await original_update(session) |
358 | 376 | elif operation["op"] == "store_memory_retry": |
359 | 377 | original_store = memory_service.store_session |
@@ -455,8 +473,8 @@ def _inject_case_difference(case_id: str, snapshot: dict[str, Any]) -> tuple[dic |
455 | 473 | return injected, expected_path |
456 | 474 |
|
457 | 475 |
|
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") |
460 | 478 |
|
461 | 479 |
|
462 | 480 | def _compare_snapshot( |
@@ -516,13 +534,18 @@ async def test_single_turn_matches_default_backends(self): |
516 | 534 | assert result["status"] == "match" |
517 | 535 | assert [item["backend"] for item in result["comparisons"]] == _expected_comparison_backends() |
518 | 536 |
|
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) |
520 | 541 | 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) |
522 | 544 | expected = {case["id"]: "match" for case in _load_cases()} |
523 | 545 | 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")) |
525 | 547 | assert len(report["cases"]) == 10 |
| 548 | + assert report == json.loads(_REPORT_PATH.read_text(encoding="utf-8")) |
526 | 549 |
|
527 | 550 | async def test_all_public_cases_detect_injected_difference(self): |
528 | 551 | for case in _load_cases(): |
@@ -552,6 +575,15 @@ async def test_lightweight_mode_runs_without_a_persistent_backend(self, monkeypa |
552 | 575 |
|
553 | 576 | class TestReplayComparison: |
554 | 577 |
|
| 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 | + |
555 | 587 | def test_design_note_contains_150_to_300_chinese_characters(self): |
556 | 588 | design = _DESIGN_PATH.read_text(encoding="utf-8") |
557 | 589 | assert 150 <= len(re.findall(r"[\u4e00-\u9fff]", design)) <= 300 |
@@ -624,8 +656,18 @@ def test_allowed_difference_is_retained_in_report_data(self): |
624 | 656 | def test_allowed_difference_includes_its_reason_in_the_report(self): |
625 | 657 | comparison = _compare_snapshot( |
626 | 658 | "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 | + }, |
629 | 671 | {"state.backend"}, |
630 | 672 | "Persistent backend stores this field differently.", |
631 | 673 | ) |
|
0 commit comments