generated from coulomb/repo-seed
session-memory Phase 0: Tier1/Tier2 store (T03)
- session_memory/core/store.py: SQLite rows + blob-dir bodies, idempotent ingest on (session_uid,seq), Tier1/Tier2 usage accounting, evict_raw that drops raw but preserves the digest; watermark columns authoritative - tests/test_store.py: ingest idempotency, accounting, eviction invariant Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
82
tests/test_store.py
Normal file
82
tests/test_store.py
Normal file
@@ -0,0 +1,82 @@
|
||||
"""Store tests (T03): idempotent ingest, usage accounting, eviction."""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
from session_memory.adapters.claude import Normalized # noqa: E402
|
||||
from session_memory.core.schema import Cost, Session, SessionEvent # noqa: E402
|
||||
from session_memory.core.store import Store # noqa: E402
|
||||
|
||||
|
||||
def _bundle(uid_native="s1", n_events=3):
|
||||
s = Session(
|
||||
session_uid=Session.make_uid("claude", uid_native),
|
||||
flavor="claude", native_session_id=uid_native,
|
||||
repo="agentic-resources", domain="helix_forge",
|
||||
cost=Cost(input_tokens=10),
|
||||
)
|
||||
events, blobs = [], {}
|
||||
for i in range(n_events):
|
||||
ref = f"blob://{uid_native}/{i}"
|
||||
events.append(SessionEvent(session_uid=s.session_uid, seq=i, kind="assistant_msg",
|
||||
payload_ref=ref, summary=f"msg {i}"))
|
||||
blobs[ref] = "x" * 100
|
||||
return Normalized(session=s, events=events, blobs=blobs)
|
||||
|
||||
|
||||
def _store(tmp_path):
|
||||
return Store(str(tmp_path / "mem.db"), str(tmp_path / "blobs"))
|
||||
|
||||
|
||||
def test_ingest_and_read_back(tmp_path):
|
||||
st = _store(tmp_path)
|
||||
b = _bundle("s1", 3)
|
||||
st.ingest(b)
|
||||
s = st.get_session(b.session.session_uid)
|
||||
assert s is not None and s.ingested_at is not None
|
||||
assert st.count_events(b.session.session_uid) == 3
|
||||
assert st.tier1_usage_bytes() > 0
|
||||
|
||||
|
||||
def test_ingest_is_idempotent(tmp_path):
|
||||
st = _store(tmp_path)
|
||||
b = _bundle("s1", 3)
|
||||
st.ingest(b)
|
||||
before = st.tier1_usage_bytes()
|
||||
st.ingest(b) # re-run same sweep
|
||||
assert st.count_events(b.session.session_uid) == 3 # no duplicate rows
|
||||
assert st.tier1_usage_bytes() == before # blobs upserted, not doubled
|
||||
|
||||
|
||||
def test_digest_sets_analyzed_and_tier2_bytes(tmp_path):
|
||||
st = _store(tmp_path)
|
||||
b = _bundle("s1", 2)
|
||||
st.ingest(b)
|
||||
assert st.get_session(b.session.session_uid).analyzed_at is None
|
||||
st.write_digest(b.session.session_uid, {"outcome": "success", "tools": {"Edit": 1}})
|
||||
assert st.get_session(b.session.session_uid).analyzed_at is not None
|
||||
assert st.tier2_usage_bytes() > 0
|
||||
assert st.get_digest(b.session.session_uid)["outcome"] == "success"
|
||||
|
||||
|
||||
def test_evict_raw_keeps_digest_drops_raw(tmp_path):
|
||||
st = _store(tmp_path)
|
||||
b = _bundle("s1", 3)
|
||||
st.ingest(b)
|
||||
st.write_digest(b.session.session_uid, {"outcome": "unknown"})
|
||||
blob_dir_files_before = sum(len(f) for _, _, f in os.walk(str(tmp_path / "blobs")))
|
||||
assert blob_dir_files_before > 0
|
||||
|
||||
freed = st.evict_raw(b.session.session_uid)
|
||||
assert freed > 0
|
||||
assert st.count_events(b.session.session_uid) == 0 # raw gone
|
||||
assert st.get_events(b.session.session_uid) == []
|
||||
assert st.get_session(b.session.session_uid).evicted_at is not None
|
||||
assert st.get_digest(b.session.session_uid) is not None # Tier 2 preserved
|
||||
# blob files removed from disk
|
||||
remaining = [f for _, _, fs in os.walk(str(tmp_path / "blobs")) for f in fs]
|
||||
assert remaining == []
|
||||
# evicted session no longer counts toward Tier 1 usage
|
||||
assert st.tier1_usage_bytes() == 0
|
||||
Reference in New Issue
Block a user