"""Tests for federated store management and reporting.""" from __future__ import annotations import json from datetime import datetime, timezone from pathlib import Path import pytest from phase_memory.cli import main from phase_memory.management import ( FEDERATED_REPORT_SCHEMA, STORE_KIND_LOCAL_GRAPH, STORE_KIND_OPS_WARDEN, build_federated_report, build_store_list, classify_store, discover_memory_stores, resolve_store_reference, ) FIXTURES = Path(__file__).resolve().parent / "fixtures" / "management" WINDOW_END = datetime(2026, 7, 3, 12, 0, tzinfo=timezone.utc) def _fixture_environ(tmp_path: Path) -> dict[str, str]: registry = FIXTURES / "registry.json" ops_store = FIXTURES / "ops-warden-store" local_store = FIXTURES / "local-store" return { "WARDEN_MEMORY_STORE": str(ops_store), "PHASE_MEMORY_REGISTRY": str(registry), "PHASE_MEMORY_STORE_PATHS": str(local_store), "HOME": str(tmp_path), "XDG_DATA_HOME": str(tmp_path / "xdg"), } def test_classify_store_kinds() -> None: assert classify_store(FIXTURES / "ops-warden-store") == STORE_KIND_OPS_WARDEN assert classify_store(FIXTURES / "local-store") == STORE_KIND_LOCAL_GRAPH assert classify_store(FIXTURES / "registry.json") is None def test_discover_memory_stores_deduplicates_and_merges_registry(tmp_path: Path) -> None: environ = _fixture_environ(tmp_path) stores, diagnostics = discover_memory_stores(environ) assert not any(item.get("severity") == "error" for item in diagnostics) store_ids = {store.store_id for store in stores} assert "fixture-local-graph" in store_ids assert len({str(store.path) for store in stores}) == len(stores) def test_build_federated_report_aggregate_and_detail(tmp_path: Path) -> None: environ = _fixture_environ(tmp_path) report = build_federated_report(days=7, environ=environ, window_end=WINDOW_END) assert report["schema_version"] == FEDERATED_REPORT_SCHEMA assert report["valid"] is True assert report["store_count"] == 2 aggregate = report["aggregate"] assert aggregate["episode_count"] == 3 assert aggregate["audit_event_count"] == 1 assert aggregate["active_store_count"] == 2 assert aggregate["by_outcome"]["resolved"] == 1 assert aggregate["by_outcome"]["skipped"] == 1 assert aggregate["by_session_kind"]["warden.operator"] == 1 assert aggregate["by_operation"]["audit.query"] == 1 detail = build_federated_report( days=7, focus_store_id="fixture-local-graph", environ=environ, window_end=WINDOW_END, ) assert detail["focus_store_id"] == "fixture-local-graph" assert detail["store_count"] == 1 assert detail["store_detail"]["episode_count"] == 1 assert detail["store_detail"]["audit_event_count"] == 1 def test_resolve_store_reference_by_id_and_path(tmp_path: Path) -> None: environ = _fixture_environ(tmp_path) stores, _ = discover_memory_stores(environ) by_id = resolve_store_reference("fixture-local-graph", stores, environ=environ) assert by_id is not None assert by_id.store_kind == STORE_KIND_LOCAL_GRAPH by_path = resolve_store_reference(str(FIXTURES / "ops-warden-store"), stores, environ=environ) assert by_path is not None assert by_path.store_kind == STORE_KIND_OPS_WARDEN def test_federated_report_aggregate_fixture(tmp_path: Path) -> None: environ = _fixture_environ(tmp_path) report = build_federated_report(days=7, environ=environ, window_end=WINDOW_END) payload = { "aggregate": report["aggregate"], "store_count": report["store_count"], "stores": [ {key: store[key] for key in ("store_id", "store_kind", "episode_count", "audit_event_count")} for store in report["stores"] ], } expected = json.loads((FIXTURES / "federated-report-aggregate.json").read_text(encoding="utf-8")) assert payload["aggregate"] == expected["aggregate"] assert payload["store_count"] == expected["store_count"] actual_stores = sorted(payload["stores"], key=lambda item: item["store_kind"]) expected_stores = sorted(expected["stores"], key=lambda item: item["store_kind"]) for actual, item in zip(actual_stores, expected_stores, strict=True): assert actual["store_kind"] == item["store_kind"] assert actual["episode_count"] == item["episode_count"] assert actual["audit_event_count"] == item["audit_event_count"] def test_cli_stores_list_and_report(tmp_path: Path, capsys, monkeypatch: pytest.MonkeyPatch) -> None: environ = _fixture_environ(tmp_path) monkeypatch.setenv("WARDEN_MEMORY_STORE", environ["WARDEN_MEMORY_STORE"]) monkeypatch.setenv("PHASE_MEMORY_REGISTRY", environ["PHASE_MEMORY_REGISTRY"]) monkeypatch.setenv("PHASE_MEMORY_STORE_PATHS", environ["PHASE_MEMORY_STORE_PATHS"]) assert main(["stores", "list", "--format", "summary"]) == 0 listed = capsys.readouterr().out assert "Fixture local graph store" in listed assert main(["report", "--days", "7", "--format", "summary"]) == 0 reported = capsys.readouterr().out assert "episodes=" in reported assert "active_stores=" in reported def test_build_store_list_from_registry_only(tmp_path: Path) -> None: environ = { "WARDEN_MEMORY_STORE": str(tmp_path / "missing-ops"), "PHASE_MEMORY_REGISTRY": str(FIXTURES / "registry.json"), "PHASE_MEMORY_STORE_PATHS": str(FIXTURES / "local-store"), } listing = build_store_list(environ=environ) assert listing["valid"] is True assert listing["store_count"] == 1 assert listing["stores"][0]["store_id"] == "fixture-local-graph"