from datetime import timedelta from sqlalchemy import create_engine, select from sqlalchemy.orm import sessionmaker from app.core.database import Base from app.core.security import ApiPrincipal from app.core.utils.time import utc_now from app.modules.ai_memory.constants import AIMemoryResponseKey, AIMemoryStatus from app.modules.ai_memory.models import AIMemoryEntry from app.modules.ai_memory.routes import list_memory from app.modules.dashboard.routes import dashboard_summary from app.modules.observability.routes import metrics as observability_metrics def _session_factory(): engine = create_engine("sqlite://") Base.metadata.create_all(engine) return engine, sessionmaker(bind=engine, expire_on_commit=False) def _seed_expired_memory(factory: sessionmaker, code: str, subject: str) -> None: with factory() as db: db.add( AIMemoryEntry( code=code, scope="project", subject=subject, content="expired", status=AIMemoryStatus.ACTIVE, expires_at=utc_now() - timedelta(seconds=1), ) ) db.commit() def _assert_archived(factory: sessionmaker, code: str) -> None: with factory() as db: stored = db.execute( select(AIMemoryEntry).where(AIMemoryEntry.code == code) ).scalar_one() assert stored.status == AIMemoryStatus.ARCHIVED def test_memory_list_route_persists_expiration_at_request_boundary() -> None: engine, factory = _session_factory() try: code = "MEM-LIST-EXPIRED" _seed_expired_memory(factory, code, "P-LIST") with factory() as db: result = list_memory( scope="project", subject="P-LIST", status=AIMemoryStatus.ACTIVE, limit=100, db=db, ) assert result[AIMemoryResponseKey.ITEMS] == [] _assert_archived(factory, code) finally: engine.dispose() def test_observability_metrics_route_persists_memory_expiration() -> None: engine, factory = _session_factory() try: code = "MEM-METRICS-EXPIRED" _seed_expired_memory(factory, code, "P-METRICS") with factory() as db: observability_metrics(db=db) _assert_archived(factory, code) finally: engine.dispose() def test_dashboard_route_excludes_and_archives_expired_memory() -> None: engine, factory = _session_factory() try: code = "MEM-DASHBOARD-EXPIRED" _seed_expired_memory(factory, code, "P-DASHBOARD") with factory() as db: result = dashboard_summary( db=db, principal=ApiPrincipal(actor="pytest"), ) assert result["metrics"]["active_ai_memory"] == 0 _assert_archived(factory, code) finally: engine.dispose()