123 lines
4.7 KiB
Python
123 lines
4.7 KiB
Python
"""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, "_now", 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"])
|