"""Per-user (client/localStorage) memory: pure helpers, parsers, UI handlers, and per-request injection into the agent prompt. Stub mode; no network.""" import os from datetime import datetime os.environ.setdefault("USE_STUB_EXTRACTOR", "1") from server import agent, memory # noqa: E402 from server.schema import ActionPlan, Event # noqa: E402 from ui import blocks # noqa: E402 # --- pure helpers ----------------------------------------------------------- def test_facts_to_recall_and_merge(): facts = memory.merge_facts([], [("Dana is the coach", "contact"), "you decline Mondays"]) assert len(facts) == 2 # dedup + weight bump facts = memory.merge_facts(facts, ["Dana is the coach"]) dana = [f for f in facts if "Dana" in f["text"]][0] assert dana["weight"] == 2 block = memory.facts_to_recall(facts) assert "What I know about you" in block and "Dana is the coach" in block assert memory.facts_to_recall([]) == "" def test_learn_from_plan_returns_contacts(): plan = ActionPlan(reasoning="x", events=[Event(title="Game", start="2026-06-12T10:00", attendees=["Coach Dana"])]) learned = memory.learn_from_plan(plan) assert any("Coach Dana" in t for t in learned) # --- per-request injection (the whole point) -------------------------------- def test_build_messages_uses_passed_memory(): msgs = agent.build_messages("hi", datetime(2026, 6, 12, 9, 0), [], None, memory_block="What I know about you (memory):\n- Dana is the coach") blob = str(msgs) assert "Dana is the coach" in blob # --- UI client handlers ----------------------------------------------------- def test_mem_remember_and_forget_roundtrip(): mem, status = blocks._mem_remember("you decline Mondays", "preference", "[]") facts = blocks._facts_load(mem) assert facts and facts[0]["text"] == "you decline Mondays" fid = facts[0]["id"] mem2, status2 = blocks._mem_forget(fid, mem) assert blocks._facts_load(mem2) == [] assert "Forgotten" in status2 def test_onboard_writes_facts(): mem, vis, msg = blocks._on_onboard("Dana = soccer coach\nMr. Lee = teacher", 30, "Mondays", "[]") texts = [f["text"] for f in blocks._facts_load(mem)] assert any("Dana is the soccer coach" in t for t in texts) assert any("teacher" in t for t in texts) assert any("30 minutes" in t for t in texts) assert any("Mondays" in t for t in texts) # --- file import parsers ---------------------------------------------------- def test_parse_vcf_csv_ics(tmp_path): vcf = tmp_path / "c.vcf" vcf.write_text("BEGIN:VCARD\nFN:Coach Dana\nEND:VCARD\nBEGIN:VCARD\nFN:Mr. Lee\nEND:VCARD\n") names, _ = blocks._parse_memory_file(str(vcf)) assert "Coach Dana" in names and "Mr. Lee" in names csv = tmp_path / "c.csv" csv.write_text("name,phone\nAlex Kim,123\nJamie Fox,456\n") names, _ = blocks._parse_memory_file(str(csv)) assert "Alex Kim" in names and "Jamie Fox" in names ics = tmp_path / "cal.ics" ics.write_text("BEGIN:VEVENT\nSUMMARY:Soccer practice\nATTENDEE;CN=Coach Dana:mailto:d@x.com\nEND:VEVENT\n") names, prefs = blocks._parse_memory_file(str(ics)) assert "Coach Dana" in names assert any("Soccer practice" in p[0] for p in prefs) def test_import_file_merges_into_memory(tmp_path): vcf = tmp_path / "c.vcf" vcf.write_text("BEGIN:VCARD\nFN:Coach Dana\nEND:VCARD\n") mem, status = blocks._import_file(str(vcf), "[]") assert any("Coach Dana" in f["text"] for f in blocks._facts_load(mem)) assert "Imported" in status def test_import_gcal_unchecked_is_graceful(): mem, status = blocks._import_gcal(False, "[]") assert "Tick the box" in status