"""Tests for the quest system: rotation, progress, claiming, schema detection.""" import logging from datetime import datetime, timedelta, timezone from core import economy from conftest import run UID = 111 def _fixed_now(monkeypatch, dt: datetime): monkeypatch.setattr(economy.store, "_clock", lambda: dt) return dt def _complete_all_active_quests(fake_pb, user_id: int) -> tuple[int, int]: """Push every active quest's tracked counter past its goal directly in the store. Returns (expected_coins, expected_exp).""" rec = fake_pb.record_for(user_id) coins = exp = 0 for pool, block in ( (economy.QUESTS_DAILY, rec["quest_daily"]), (economy.QUESTS_WEEKLY, rec["quest_weekly"]), ): for qid, state in block["quests"].items(): stat = pool[qid]["stat"] rec[stat] = state["snap"] + pool[qid]["goal"] coins += pool[qid]["coins"] exp += pool[qid]["exp"] return coins, exp class TestRotation: def test_deterministic_per_seed(self): a = economy._pick_quests(economy.QUESTS_DAILY, 3, "1:date:2026-07-26") b = economy._pick_quests(economy.QUESTS_DAILY, 3, "1:date:2026-07-26") assert a == b def test_users_get_different_sets(self): sets = { tuple(economy._pick_quests(economy.QUESTS_DAILY, 3, f"{uid}:date:2026-07-26")) for uid in range(50) } assert len(sets) > 1 def test_counts(self, fake_pb): data = run(economy.get_quests(UID)) assert len(data["daily"]) == economy.DAILY_QUEST_COUNT assert len(data["weekly"]) == economy.WEEKLY_QUEST_COUNT def test_daily_rolls_over_weekly_stays(self, fake_pb, monkeypatch): # a Tuesday, so day+1 stays inside the same ISO week t0 = _fixed_now(monkeypatch, datetime(2026, 7, 21, 12, tzinfo=timezone.utc)) run(economy.get_quests(UID)) rec = fake_pb.record_for(UID) daily_before, weekly_before = dict(rec["quest_daily"]), dict(rec["quest_weekly"]) _fixed_now(monkeypatch, t0 + timedelta(days=1)) run(economy.get_quests(UID)) rec = fake_pb.record_for(UID) assert rec["quest_daily"]["date"] != daily_before["date"] assert rec["quest_weekly"] == weekly_before class TestProgress: def test_progress_tracks_counter_delta(self, fake_pb): run(economy.get_quests(UID)) rec = fake_pb.record_for(UID) qid, state = next(iter(rec["quest_daily"]["quests"].items())) stat = economy.QUESTS_DAILY[qid]["stat"] rec[stat] = state["snap"] + 1 data = run(economy.get_quests(UID)) quest = next(q for q in data["daily"] if q["id"] == qid) assert quest["progress"] == 1 def test_pre_roll_stats_do_not_count(self, fake_pb): run(economy.get_user(UID)) fake_pb.record_for(UID)["work_count"] = 500 data = run(economy.get_quests(UID)) assert all(q["progress"] == 0 for q in data["daily"] + data["weekly"]) class TestClaim: def test_claim_pays_and_is_idempotent(self, fake_pb): run(economy.get_quests(UID)) coins, exp = _complete_all_active_quests(fake_pb, UID) res = run(economy.claim_quests(UID)) assert res["ok"] assert res["claimed"] == economy.DAILY_QUEST_COUNT + economy.WEEKLY_QUEST_COUNT assert res["coins"] == coins and res["exp"] == exp assert fake_pb.record_for(UID)["balance"] == coins res = run(economy.claim_quests(UID)) assert not res["ok"] and res["reason"] == "nothing" def test_claim_with_nothing_done(self, fake_pb): run(economy.get_quests(UID)) res = run(economy.claim_quests(UID)) assert not res["ok"] class TestSchemaDetection: def test_missing_fields_reported(self, fake_pb_without_quest_fields): assert run(economy.missing_schema_fields()) == ["quest_daily", "quest_weekly"] def test_healthy_schema_reports_nothing(self, fake_pb): assert run(economy.missing_schema_fields()) == [] def test_missing_quest_fields_warn_and_zero_progress( self, fake_pb_without_quest_fields, caplog ): """Reproduces the live 'quests never progress' symptom: when the collection schema lacks quest_daily/quest_weekly, PocketBase drops the rolled quest block, so every call re-rolls with a fresh snapshot.""" fake = fake_pb_without_quest_fields with caplog.at_level(logging.WARNING): run(economy.get_quests(UID)) assert any("quest fields" in r.message for r in caplog.records) # counters advance, but progress stays 0 because the snapshot re-rolls fake.record_for(UID)["work_count"] = 500 data = run(economy.get_quests(UID)) assert all(q["progress"] == 0 for q in data["daily"] + data["weekly"])