"""Tests for the Achievement Gallery — profile_builder.compute_achievements(). All tests use the temp_db fixture (auto-use in conftest.py) which wires BIGMIND_DB_PATH + BIGMIND_USER to a fresh SQLite file per test. """ import pytest from datetime import datetime, timezone, timedelta from bigmind import memory_store from bigmind.db import db from bigmind.profile_builder import compute_achievements, build_profile_data # ── Helpers ─────────────────────────────────────────────────────────────────── def _uid(): user = memory_store.get_or_create_user(memory_store.get_current_username()) return user["id"] def _close_session(session_id: str, has_tier2: bool = False): """Close a session with a one-liner summary.""" memory_store.close_session( session_id=session_id, one_liner="test session", topics="test", outcome="ok", importance=5, ) if has_tier2: memory_store.save_session_summary(session_id, summary="detailed summary") # ── TestComputeAchievements ─────────────────────────────────────────────────── class TestComputeAchievements: def test_returns_list_of_expected_ids(self): uid = _uid() achievements = compute_achievements(uid) ids = {a["id"] for a in achievements} expected = { "first_breath", "first_thought", "eureka", "honest_mind", "scholar", "deep_knowledge", "scientist", "veteran", "on_fire", "storyteller", "night_owl", "speed_thinker", "first_handshake", "birthday", "shared_mind", "frugal_mind", "quarter_million", "token_millionaire", "sniper", "networker_bronze", "networker_silver", "networker_gold", "networker_platinum", "tokensniper_bronze", "tokensniper_silver", "tokensniper_gold", "tokensniper_platinum", "hypothesismaster_bronze", "hypothesismaster_silver", "hypothesismaster_gold", "hypothesismaster_platinum", "memoryarchitect_bronze", "memoryarchitect_silver", "memoryarchitect_gold", "memoryarchitect_platinum", "sessionveteran_bronze", "sessionveteran_silver", "sessionveteran_gold", "sessionveteran_platinum", } assert expected == ids def test_all_locked_for_empty_db(self): """Fresh DB: most achievements locked, except First Handshake (hardcoded).""" uid = _uid() achievements = compute_achievements(uid) by_id = {a["id"]: a for a in achievements} # First Handshake is always unlocked (hardcoded to 2026-03-31) assert by_id["first_handshake"]["unlocked"] is True assert by_id["first_handshake"]["unlocked_at"] == "2026-03-31" # Everything else locked for aid in ["first_breath", "first_thought", "eureka", "honest_mind", "scholar", "veteran", "on_fire", "storyteller", "night_owl", "speed_thinker", "frugal_mind", "quarter_million", "token_millionaire", "sniper"]: assert by_id[aid]["unlocked"] is False, f"{aid} should be locked" # Shared Mind is always locked (Phase 3 not yet) assert by_id["shared_mind"]["unlocked"] is False # ── First Breath ────────────────────────────────────────────────────────── def test_first_breath_unlocks_after_first_session(self): uid = _uid() sid = memory_store.create_session(uid) _close_session(sid) ach = {a["id"]: a for a in compute_achievements(uid)} assert ach["first_breath"]["unlocked"] is True assert ach["first_breath"]["unlocked_at"] is not None def test_first_breath_locked_with_only_open_session(self): """Open (unclosed) session does NOT unlock First Breath.""" uid = _uid() memory_store.create_session(uid) # not closed ach = {a["id"]: a for a in compute_achievements(uid)} assert ach["first_breath"]["unlocked"] is False # ── First Thought / Eureka / Honest Mind ───────────────────────────────── def test_first_thought_unlocks_on_first_hypothesis(self): uid = _uid() sid = memory_store.create_session(uid) memory_store.add_hypothesis(uid, sid, "test hypothesis", confidence=0.7) ach = {a["id"]: a for a in compute_achievements(uid)} assert ach["first_thought"]["unlocked"] is True def test_eureka_locked_until_confirmed(self): uid = _uid() sid = memory_store.create_session(uid) hid = memory_store.add_hypothesis(uid, sid, "will be confirmed") ach = {a["id"]: a for a in compute_achievements(uid)} assert ach["eureka"]["unlocked"] is False # still open memory_store.resolve_hypothesis(hid, uid, "confirmed", "yes it was true") ach = {a["id"]: a for a in compute_achievements(uid)} assert ach["eureka"]["unlocked"] is True def test_honest_mind_locked_until_refuted(self): uid = _uid() sid = memory_store.create_session(uid) hid = memory_store.add_hypothesis(uid, sid, "will be refuted") ach = {a["id"]: a for a in compute_achievements(uid)} assert ach["honest_mind"]["unlocked"] is False memory_store.resolve_hypothesis(hid, uid, "refuted", "nope, was wrong") ach = {a["id"]: a for a in compute_achievements(uid)} assert ach["honest_mind"]["unlocked"] is True # ── Scholar ─────────────────────────────────────────────────────────────── def test_scholar_locks_below_25_facts(self): uid = _uid() for i in range(24): memory_store.store_fact(uid, "test", f"fact number {i}") ach = {a["id"]: a for a in compute_achievements(uid)} assert ach["scholar"]["unlocked"] is False assert "24" in ach["scholar"]["condition"] def test_scholar_unlocks_at_25_facts(self): uid = _uid() for i in range(25): memory_store.store_fact(uid, "test", f"fact number {i}") ach = {a["id"]: a for a in compute_achievements(uid)} assert ach["scholar"]["unlocked"] is True assert ach["scholar"]["unlocked_at"] is not None def test_deep_knowledge_unlocks_at_100_facts(self): uid = _uid() for i in range(100): memory_store.store_fact(uid, "test", f"fact number {i}") ach = {a["id"]: a for a in compute_achievements(uid)} assert ach["deep_knowledge"]["unlocked"] is True # Scholar should also be unlocked assert ach["scholar"]["unlocked"] is True # ── Scientist ───────────────────────────────────────────────────────────── def test_scientist_unlocks_at_10_hypotheses(self): uid = _uid() sid = memory_store.create_session(uid) for i in range(9): memory_store.add_hypothesis(uid, sid, f"hypothesis {i}") ach = {a["id"]: a for a in compute_achievements(uid)} assert ach["scientist"]["unlocked"] is False memory_store.add_hypothesis(uid, sid, "hypothesis 9") ach = {a["id"]: a for a in compute_achievements(uid)} assert ach["scientist"]["unlocked"] is True assert ach["scientist"]["unlocked_at"] is not None # ── Veteran ─────────────────────────────────────────────────────────────── def test_veteran_unlocks_at_50_sessions(self): uid = _uid() for _ in range(50): sid = memory_store.create_session(uid) _close_session(sid) ach = {a["id"]: a for a in compute_achievements(uid)} assert ach["veteran"]["unlocked"] is True assert ach["veteran"]["unlocked_at"] is not None def test_veteran_locks_at_49_sessions(self): uid = _uid() for _ in range(49): sid = memory_store.create_session(uid) _close_session(sid) ach = {a["id"]: a for a in compute_achievements(uid)} assert ach["veteran"]["unlocked"] is False # ── On Fire ─────────────────────────────────────────────────────────────── def test_on_fire_locked_below_5_sessions_per_day(self): uid = _uid() for _ in range(4): sid = memory_store.create_session(uid) _close_session(sid) ach = {a["id"]: a for a in compute_achievements(uid)} assert ach["on_fire"]["unlocked"] is False def test_on_fire_unlocks_at_5_sessions_same_day(self): uid = _uid() for _ in range(5): sid = memory_store.create_session(uid) _close_session(sid) ach = {a["id"]: a for a in compute_achievements(uid)} assert ach["on_fire"]["unlocked"] is True # ── Storyteller ─────────────────────────────────────────────────────────── def test_storyteller_requires_20_tier2_sessions(self): uid = _uid() # 19 sessions with tier2 for _ in range(19): sid = memory_store.create_session(uid) _close_session(sid, has_tier2=True) ach = {a["id"]: a for a in compute_achievements(uid)} assert ach["storyteller"]["unlocked"] is False sid = memory_store.create_session(uid) _close_session(sid, has_tier2=True) ach = {a["id"]: a for a in compute_achievements(uid)} assert ach["storyteller"]["unlocked"] is True # ── Speed Thinker ───────────────────────────────────────────────────────── def test_speed_thinker_unlocks_same_day_confirm(self): uid = _uid() sid = memory_store.create_session(uid) hid = memory_store.add_hypothesis(uid, sid, "quick thought") memory_store.resolve_hypothesis(hid, uid, "confirmed", "yes!") ach = {a["id"]: a for a in compute_achievements(uid)} assert ach["speed_thinker"]["unlocked"] is True # ── Token achievements ──────────────────────────────────────────────────── def test_frugal_mind_unlocks_on_first_token_save(self): uid = _uid() sid = memory_store.create_session(uid) memory_store.log_token_save(sid, uid, "saved tokens by grep", 5000, "grep") ach = {a["id"]: a for a in compute_achievements(uid)} assert ach["frugal_mind"]["unlocked"] is True assert ach["frugal_mind"]["unlocked_at"] is not None def test_quarter_million_unlocks_at_250k(self): uid = _uid() sid = memory_store.create_session(uid) memory_store.log_token_save(sid, uid, "big save", 250_000, "grep") ach = {a["id"]: a for a in compute_achievements(uid)} assert ach["quarter_million"]["unlocked"] is True def test_token_millionaire_unlocks_at_1m(self): uid = _uid() sid = memory_store.create_session(uid) memory_store.log_token_save(sid, uid, "huge save", 1_000_000, "memory_hit") ach = {a["id"]: a for a in compute_achievements(uid)} assert ach["token_millionaire"]["unlocked"] is True def test_sniper_requires_single_save_over_500k(self): uid = _uid() sid = memory_store.create_session(uid) # Multiple saves that total > 500k but none individual exceeds it memory_store.log_token_save(sid, uid, "save 1", 300_000, "grep") memory_store.log_token_save(sid, uid, "save 2", 300_000, "grep") ach = {a["id"]: a for a in compute_achievements(uid)} assert ach["sniper"]["unlocked"] is False # no single save > 500k memory_store.log_token_save(sid, uid, "sniper shot", 600_000, "grep") ach = {a["id"]: a for a in compute_achievements(uid)} assert ach["sniper"]["unlocked"] is True # ── Birthday ────────────────────────────────────────────────────────────── def test_birthday_locked_shows_countdown(self): uid = _uid() sid = memory_store.create_session(uid) _close_session(sid) ach = {a["id"]: a for a in compute_achievements(uid)} bday = ach["birthday"] assert bday["unlocked"] is False assert bday["extra"] is not None assert "In " in bday["extra"] or "day" in bday["extra"] # ── Hardcoded achievements ───────────────────────────────────────────────── def test_first_handshake_always_unlocked(self): uid = _uid() ach = {a["id"]: a for a in compute_achievements(uid)} assert ach["first_handshake"]["unlocked"] is True assert ach["first_handshake"]["unlocked_at"] == "2026-03-31" def test_shared_mind_always_locked(self): uid = _uid() ach = {a["id"]: a for a in compute_achievements(uid)} assert ach["shared_mind"]["unlocked"] is False # ── Achievement structure ───────────────────────────────────────────────── def test_all_achievements_have_required_keys(self): uid = _uid() achievements = compute_achievements(uid) for a in achievements: assert "id" in a assert "icon" in a assert "name" in a assert "description" in a assert "unlocked" in a assert "unlocked_at" in a assert "condition" in a def test_unlocked_achievement_has_no_extra_for_non_birthday(self): """Non-birthday unlocked achievements should not have 'extra' countdown text.""" uid = _uid() sid = memory_store.create_session(uid) _close_session(sid) ach = {a["id"]: a for a in compute_achievements(uid)} fb = ach["first_breath"] assert fb["unlocked"] is True assert fb.get("extra") is None # ── build_profile_data integration ──────────────────────────────────────── def test_build_profile_data_includes_achievements(self): uid = _uid() data = build_profile_data(uid) assert "achievements" in data assert isinstance(data["achievements"], list) assert len(data["achievements"]) > 0 def test_build_profile_data_achievement_count_correct(self): uid = _uid() # Add one session so first_breath and on_fire can unlock sid = memory_store.create_session(uid) _close_session(sid) data = build_profile_data(uid) unlocked = [a for a in data["achievements"] if a["unlocked"]] # At minimum: first_breath + first_handshake = 2 assert len(unlocked) >= 2 class TestTieredAchievements: def test_networker_bronze(self): uid = _uid() with db() as conn: conn.execute("INSERT INTO people (user_id, username) VALUES (?, ?)", (uid, "test")) conn.commit() achs = compute_achievements(uid) bronze = next(a for a in achs if a['id'] == 'networker_bronze') assert bronze['unlocked'] is True assert bronze['image'].endswith('networker_bronze.png') def test_tokensniper_silver(self): uid = _uid() sid = memory_store.create_session(uid) memory_store.log_token_save(sid, uid, "big save", 60000, "grep") achs = compute_achievements(uid) silver = next(a for a in achs if a['id'] == 'tokensniper_silver') assert silver['unlocked'] is True def test_hypothesismaster_bronze(self): uid = _uid() sid = memory_store.create_session(uid) for _ in range(3): hid = memory_store.add_hypothesis(uid, sid, "test", 0.8) memory_store.resolve_hypothesis(hid, uid, "confirmed", "yes") achs = compute_achievements(uid) bronze = next(a for a in achs if a['id'] == 'hypothesismaster_bronze') assert bronze['unlocked'] is True def test_memoryarchitect_silver(self): uid = _uid() for _ in range(100): memory_store.store_fact(uid, "test", f"fact {_}") achs = compute_achievements(uid) silver = next(a for a in achs if a['id'] == 'memoryarchitect_silver') assert silver['unlocked'] is True def test_sessionveteran_bronze(self): uid = _uid() for _ in range(50): sid = memory_store.create_session(uid) _close_session(sid) achs = compute_achievements(uid) bronze = next(a for a in achs if a['id'] == 'sessionveteran_bronze') assert bronze['unlocked'] is True def test_tiered_achievements_have_image(self): uid = _uid() achs = compute_achievements(uid) tiered_ids = [ f"{cat}_{tier}" for cat in ["networker", "tokensniper", "hypothesismaster", "memoryarchitect", "sessionveteran"] for tier in ["bronze", "silver", "gold", "platinum"] ] for tid in tiered_ids: a = next(aa for aa in achs if aa['id'] == tid) assert a['image'] is not None assert a['image'].endswith(tid + '.png')