"""Tests for the backend extensions — plain asserts, run: python tests/test_extensions.py The real local model is never loaded: engine._llm is replaced with a fake so generation-shaped tests stay fast and offline. """ import os import sys import tempfile sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) import hashlib import re from datetime import date, timedelta import numpy as np import engine import memory import rag from features import websearch from features.health import plan_workout_slots def _fake_embed(text: str, is_query: bool = False) -> np.ndarray: """Deterministic hashed bag-of-words vector so RAG tests run without downloading/loading the real nomic-embed model.""" vec = np.zeros(256, dtype=np.float32) for tok in re.findall(r"[a-z0-9]+", text.lower()): vec[int(hashlib.md5(tok.encode()).hexdigest(), 16) % 256] += 1.0 norm = np.linalg.norm(vec) return vec / norm if norm > 0 else vec rag.embed = _fake_embed # never touch the real embedder in tests # Redirect the memory store to a temp file so tests don't clobber demo data. _tmpdir = tempfile.mkdtemp() memory.DATA_DIR = _tmpdir memory.MEMORY_PATH = os.path.join(_tmpdir, "memory.json") class _FakeLLM: """Stand-in for the llama.cpp model so engine tests need no GGUF download. Mentions 'chicken' and '$8,000' so existing content assertions hold.""" TEXT = "Recommend chicken thighs this week; your used car goal of $8,000 is on track." def create_chat_completion(self, messages, max_tokens=384, temperature=0.4, stream=False): if stream: return ( {"choices": [{"delta": {"content": w + " "}}]} for w in self.TEXT.split(" ") ) return {"choices": [{"message": {"content": self.TEXT}}]} # Inject the fake so get_llm() returns it instead of loading the real model. engine._llm = _FakeLLM() engine.MODEL_STATE = "ready" engine.ACTIVE_BACKEND = "fake" def test_seed_schema() -> None: mem = memory.reset_to_seed() assert mem["user_profile"]["city"] == "Toronto" assert mem["user_profile"]["address"] == "" assert len(mem["calendar"]) >= 3 for ev in mem["calendar"]: assert set(ev) == {"id", "title", "date", "start", "end", "kind"} assert len(ev["id"]) == 8 assert ev["kind"] in ("work", "class", "social", "other") assert mem["workout_schedule"] == {"days": ["mon", "wed", "sat"], "time": "07:00"} pays = mem["finances"]["monthly_payments"] assert sorted(p["amount"] for p in pays) == [45, 128, 650] g = mem["goals"][0] assert g["title"] == "Used car" and g["target_amount"] == 8000 and g["saved"] == 1200 def test_event_helpers_roundtrip() -> None: memory.reset_to_seed() mem = memory.add_event({"title": "Study group", "date": date.today().isoformat(), "start": "14:00", "end": "16:00", "kind": "social"}) added = [e for e in mem["calendar"] if e["title"] == "Study group"] assert len(added) == 1 and len(added[0]["id"]) == 8 n = len(mem["calendar"]) mem = memory.delete_event(added[0]["id"]) assert len(mem["calendar"]) == n - 1 assert not [e for e in mem["calendar"] if e["title"] == "Study group"] def test_schedule_payments_goal_profile_helpers() -> None: memory.reset_to_seed() mem = memory.set_workout_schedule(["Tuesday", "thu"], "18:30") assert mem["workout_schedule"] == {"days": ["tue", "thu"], "time": "18:30"} mem = memory.set_monthly_payments([{"name": "Gym", "amount": 35, "due_day": 5}]) assert mem["finances"]["monthly_payments"] == [{"name": "Gym", "amount": 35, "due_day": 5}] mem = memory.upsert_goal({"title": "Trip", "target_amount": 2000, "saved": 100, "deadline": "2027-01"}) trip = [g for g in mem["goals"] if g["title"] == "Trip"][0] mem = memory.upsert_goal({"id": trip["id"], "title": "Trip", "saved": 500}) trip2 = [g for g in mem["goals"] if g["id"] == trip["id"]][0] assert trip2["saved"] == 500 and trip2["target_amount"] == 2000 assert len(mem["goals"]) == 2 # seed goal + Trip, no dup mem = memory.set_profile({"city": "Ottawa", "income_monthly": 1600}) assert mem["user_profile"]["city"] == "Ottawa" assert mem["finances"]["income_monthly"] == 1600 assert mem["user_profile"]["name"] == "Awais Aziz" # merge, not replace assert "income_monthly" not in mem["user_profile"] def test_websearch_format() -> None: sample = [ {"title": "FreshMart flyer", "snippet": "chicken $2.49/lb", "url": "https://example.com/a"}, {"title": "City Grocer", "snippet": "beef $3.99/lb", "url": "https://example.com/b"}, ] text = websearch.format_results(sample) assert "WEB SEARCH RESULTS:" in text and sample[0]["title"] in text assert websearch.format_results([]) == "" # Real network call: returns a list (possibly empty offline) and never raises. out = websearch.search_web("toronto grocery deals", max_results=2) assert isinstance(out, list) and len(out) <= 2 assert all(set(r) == {"title", "snippet", "url"} for r in out) def test_health_plan_slots() -> None: # Fixed Monday so weekday math is deterministic. monday = date(2026, 6, 8) schedule = {"days": ["mon", "wed"], "time": "07:00"} # Monday event overlaps 07:00-08:00 -> clash; Wednesday event is later -> free. events = [ {"id": "aaaaaaaa", "title": "Early shift", "date": monday.isoformat(), "start": "06:30", "end": "09:00"}, {"id": "bbbbbbbb", "title": "Lecture", "date": (monday + timedelta(days=2)).isoformat(), "start": "10:00", "end": "12:00"}, ] slots = plan_workout_slots(schedule, events, today=monday) assert [s["day"] for s in slots] == ["mon", "wed"] mon_slot, wed_slot = slots assert mon_slot["free"] is False and mon_slot["clashes"][0]["title"] == "Early shift" assert wed_slot["free"] is True and wed_slot["clashes"] == [] # Boundary: event ending exactly at slot start does not clash. events2 = [{"id": "c", "title": "X", "date": monday.isoformat(), "start": "06:00", "end": "07:00"}] assert plan_workout_slots(schedule, events2, today=monday)[0]["free"] is True # Event starting exactly at slot end does not clash. events3 = [{"id": "d", "title": "Y", "date": monday.isoformat(), "start": "08:00", "end": "09:00"}] assert plan_workout_slots(schedule, events3, today=monday)[0]["free"] is True # No preferred days -> no slots. assert plan_workout_slots({"days": [], "time": "07:00"}, events, today=monday) == [] def test_goal_and_meal_prompts() -> None: mem = memory.reset_to_seed() msgs = engine.build_prompt("goal", mem, "I want to buy a used car") assert msgs[0]["role"] == "system" and "Socratic" in msgs[0]["content"] assert "monthly_payments" in msgs[1]["content"] or "Rent" in msgs[1]["content"] msgs = engine.build_prompt("meal_photo", mem, "FOOD ITEMS:\nchicken thighs") assert "FOOD ITEMS" in msgs[1]["content"] # generation runs through the injected fake LLM out = list(engine.generate_stream(msgs, domain="meal_photo")) assert out and "chicken" in out[-1].lower() out = list(engine.generate_stream(engine.build_prompt("goal", mem, "car"), domain="goal")) assert out and "8,000" in out[-1] # extra_context is appended to the user message and still streams msgs = engine.build_prompt("chat", mem, "any deals?") extra = "WEB SEARCH RESULTS:\n1. test" chunks = list(engine.generate_stream(msgs, domain="chat", extra_context=extra)) assert chunks def test_blank_slate_prompt() -> None: """A brand-new user (no name set) gets a graceful, possessive-safe prompt.""" blank = memory.empty_data() msgs = engine.build_prompt("chat", blank, "hello") sys_msg = msgs[0]["content"] assert "your own machine" in sys_msg and "'s own machine" not in sys_msg assert "=== YOUR MEMORY ===" in msgs[1]["content"] def test_events_in_window() -> None: memory.reset_to_seed() memory.add_event({"title": "Future", "date": (date.today() + timedelta(days=3)).isoformat(), "start": "09:00", "end": "10:00"}) memory.add_event({"title": "Far", "date": (date.today() + timedelta(days=30)).isoformat(), "start": "09:00", "end": "10:00"}) evs = memory.events_in_window(7) titles = [e["title"] for e in evs] assert "Future" in titles and "Far" not in titles assert evs == sorted(evs, key=lambda e: (e["date"], e["start"])) def test_profile_name_split_and_backfill() -> None: import json mem = memory.reset_to_seed() p = mem["user_profile"] assert p["first_name"] == "Awais" and p["last_name"] == "Aziz" and p["name"] == "Awais Aziz" assert p["address"] == "" and p["city"] == "Toronto" and p["postal_code"] == "" and p["country"] == "Canada" # Legacy file with only `name` upgrades on load. Missing fields backfill # to blank defaults (never demo persona data) — country stays "". p.pop("first_name"); p.pop("last_name"); p.pop("postal_code"); p.pop("country") p["name"] = "Jane Q Doe" with open(memory.MEMORY_PATH, "w", encoding="utf-8") as f: json.dump(mem, f) mem2 = memory.load() p2 = mem2["user_profile"] assert p2["first_name"] == "Jane" and p2["last_name"] == "Q Doe" assert p2["name"] == "Jane Q Doe" and p2["country"] == "" # set_profile recomputes name. mem3 = memory.set_profile({"first_name": "Ali", "last_name": "Khan", "postal_code": "M5V 1A1"}) assert mem3["user_profile"]["name"] == "Ali Khan" assert mem3["user_profile"]["postal_code"] == "M5V 1A1" def test_set_section() -> None: memory.reset_to_seed() meals = [{"date": "2026-01-01", "dish": "Toast", "ingredients": ["bread"]}] mem = memory.set_section("meals", meals) assert mem["meals"] == meals subs = [{"name": "OnlySub", "cost": 5, "last_used": "2026-01-01"}] mem = memory.set_section("subscriptions", subs) assert mem["finances"]["subscriptions"] == subs mem = memory.set_section("monthly_payments", []) assert mem["finances"]["monthly_payments"] == [] try: memory.set_section("goals", []) raise AssertionError("expected ValueError") except ValueError: pass def test_goal_id_backfill_and_delete() -> None: import json mem = memory.reset_to_seed() assert all(len(g["id"]) == 8 for g in mem["goals"]) # Strip ids -> load backfills. for g in mem["goals"]: g.pop("id") with open(memory.MEMORY_PATH, "w", encoding="utf-8") as f: json.dump(mem, f) mem = memory.load() assert all(g.get("id") for g in mem["goals"]) mem = memory.upsert_goal({"title": "Laptop", "target_amount": 1500}) gid = [g for g in mem["goals"] if g["title"] == "Laptop"][0]["id"] mem = memory.upsert_goal({"id": gid, "saved": 300}) assert [g for g in mem["goals"] if g["id"] == gid][0]["saved"] == 300 n = len(mem["goals"]) mem = memory.delete_goal(gid) assert len(mem["goals"]) == n - 1 and not [g for g in mem["goals"] if g["id"] == gid] def test_rag_note_management() -> None: import json import rag rag.DATA_DIR = _tmpdir rag.STORE_PATH = os.path.join(_tmpdir, "longterm.json") if os.path.exists(rag.STORE_PATH): os.remove(rag.STORE_PATH) rag.ensure_seeded() notes = rag.list_notes() assert notes and all(set(n) == {"id", "text", "kind"} for n in notes) # id backfill for old stores raw = json.load(open(rag.STORE_PATH, encoding="utf-8")) for n in raw: n.pop("id", None) json.dump(raw, open(rag.STORE_PATH, "w", encoding="utf-8")) notes = rag.list_notes() assert all(n["id"] for n in notes) nid = notes[0]["id"] assert rag.update_note(nid, "Awais now loves cilantro") is True updated = [n for n in rag.list_notes() if n["id"] == nid][0] assert updated["text"] == "Awais now loves cilantro" assert updated["kind"] == notes[0]["kind"] assert rag.delete_note(nid) is True assert not [n for n in rag.list_notes() if n["id"] == nid] assert rag.delete_note("nope") is False and rag.update_note("nope", "x") is False def test_build_prompt_domains() -> None: import engine mem = memory.reset_to_seed() msgs = engine.build_prompt("chat", mem, "what should I cook?", domains=["food"]) body = msgs[1]["content"] assert "recent_meals" in body assert "subscriptions" not in body and "income_monthly" not in body sl = engine.slice_for_domains(mem, ["kitchen", "health"]) assert "recent_meals" in sl and "workouts_last_14_days" in sl and "user_profile" in sl assert "finances" not in sl # None = full context unchanged full = engine.build_prompt("chat", mem, "hi")[1]["content"] assert "finances" in full and "recent_meals" in full def test_chat_endpoint_refs() -> None: import app memory.reset_to_seed() out = list(app.chat("what should I cook?", "[]", "", '["kitchen"]')) assert out and len(out[-1]) > 20 # backward compat: no refs arg out = list(app.chat("hello", "[]")) assert out if __name__ == "__main__": fns = [v for k, v in sorted(globals().items()) if k.startswith("test_")] for fn in fns: fn() print(f"PASS {fn.__name__}") print(f"All {len(fns)} tests passed.")