import json from pathlib import Path from app.canon_log import validate_canon_log FIXTURE = Path(__file__).parent / "fixtures" / "canon_log_valid.json" def _valid(): with open(FIXTURE) as f: return json.load(f) def test_valid_canon_log_passes(): assert validate_canon_log(_valid()) == [] def test_numeric_luck_is_rejected(): # Charter §7: the AI must never be able to calculate Luck. A stray numeric # luck field must fail validation, not slip through. doc = _valid() doc["player"]["luck"] = 5 assert validate_canon_log(doc) != [] def test_recent_events_over_five_is_rejected(): doc = _valid() doc["recent_events"] = [f"event {i}" for i in range(6)] assert validate_canon_log(doc) != [] def test_disposition_out_of_range_is_rejected(): doc = _valid() doc["party"][0]["disposition"] = 200 assert validate_canon_log(doc) != [] def test_unknown_quest_status_is_rejected(): doc = _valid() doc["active_quests"][0]["status"] = "abandoned" assert validate_canon_log(doc) != [] def test_humiliation_weight_bounds_enforced(): doc = _valid() doc["humiliations"][0]["weight"] = 11 assert validate_canon_log(doc) != [] def test_empty_optional_arrays_are_allowed(): doc = _valid() doc["party"] = [] doc["recent_events"] = [] doc["established_facts"] = [] doc["active_quests"] = [] doc["humiliations"] = [] assert validate_canon_log(doc) == [] def test_root_level_stray_field_is_rejected(): # §2/§7: stats/HP/inventory must never enter the log — additionalProperties:false at root. doc = _valid() doc["hp"] = 10 assert validate_canon_log(doc) != [] def test_negative_turn_is_rejected(): doc = _valid() doc["humiliations"][0]["turn"] = -1 assert validate_canon_log(doc) != [] def test_dead_class_id_field_is_rejected(): # The player block migrated from {name, class_id, luck_descriptor} to # {name, race_id, calling_id, luck_descriptor}. class_id must not slip # back in via additionalProperties: false. doc = _valid() doc["player"]["class_id"] = "sellsword" assert validate_canon_log(doc) != [] def test_unknown_calling_id_is_rejected(): # assassin/priest are dead ids from the pre-migration class roster and # must not validate as calling_id values. doc = _valid() doc["player"]["calling_id"] = "priest" assert validate_canon_log(doc) != []