| import copy |
|
|
| from game.state import GameState |
| from game.validator import (validate_crisis, validate_event, |
| validate_presentation) |
|
|
| GOOD_CRISIS = { |
| "npc_reaction": "Noted, boss.", |
| "consequence": "It resolved somehow.", |
| "revenue_delta": 50_000, |
| "animation": "npc_happy", |
| "boss_title": "VP of Tests", |
| "log_entry": "Brad did a thing. +$50K.", |
| "morale_delta": 3, |
| "npc_id": "brad", |
| "relationship_delta": 5, |
| "pocket_money_delta": 0, |
| "special_next_event": None, |
| } |
|
|
|
|
| def make_state(**kw): |
| s = GameState(session_id="t") |
| for k, v in kw.items(): |
| setattr(s, k, v) |
| return s |
|
|
|
|
| def test_good_crisis_passes(): |
| assert validate_crisis(copy.deepcopy(GOOD_CRISIS), make_state(), "brad") |
|
|
|
|
| def test_missing_field_rejected(): |
| bad = copy.deepcopy(GOOD_CRISIS) |
| del bad["animation"] |
| assert validate_crisis(bad, make_state(), "brad") is None |
|
|
|
|
| def test_bad_animation_rejected(): |
| bad = copy.deepcopy(GOOD_CRISIS) |
| bad["animation"] = "npc_breakdance" |
| assert validate_crisis(bad, make_state(), "brad") is None |
|
|
|
|
| def test_confetti_reserved_for_win(): |
| bad = copy.deepcopy(GOOD_CRISIS) |
| bad["animation"] = "confetti_burst" |
| assert validate_crisis(bad, make_state(), "brad") is None |
|
|
|
|
| def test_revenue_floor_clamped(): |
| state = make_state(revenue=30_000) |
| p = copy.deepcopy(GOOD_CRISIS) |
| p["revenue_delta"] = -200_000 |
| out = validate_crisis(p, state, "brad") |
| assert out["revenue_delta"] == -30_000 |
|
|
|
|
| def test_pocket_money_capped_by_bribe_offer(): |
| p = copy.deepcopy(GOOD_CRISIS) |
| p["pocket_money_delta"] = 5_000 |
| out = validate_crisis(p, make_state(), "brad", bribe_offer=2_000) |
| assert out["pocket_money_delta"] == 2_000 |
| p2 = copy.deepcopy(GOOD_CRISIS) |
| p2["pocket_money_delta"] = 3_000 |
| out2 = validate_crisis(p2, make_state(), "brad", bribe_offer=0) |
| assert out2["pocket_money_delta"] == 0 |
|
|
|
|
| def test_office_dates_are_not_romance(): |
| state = make_state() |
| p = copy.deepcopy(GOOD_CRISIS) |
| p["npc_reaction"] = "The launch date is Friday and we are up to date, boss." |
| assert validate_crisis(p, state, "brad") is not None |
|
|
|
|
| def test_romance_gated_below_65(): |
| state = make_state() |
| state.npc("stacey").relationship = 50 |
| p = copy.deepcopy(GOOD_CRISIS) |
| p["npc_id"] = "stacey" |
| p["npc_reaction"] = "Are you asking me on a date?" |
| assert validate_crisis(p, state, "stacey") is None |
| state.npc("stacey").relationship = 70 |
| p2 = copy.deepcopy(GOOD_CRISIS) |
| p2["npc_id"] = "stacey" |
| p2["npc_reaction"] = "Are you asking me on a date?" |
| assert validate_crisis(p2, state, "stacey") is not None |
|
|
|
|
| def test_banned_content_rejected(): |
| for word in ("lawsuit", "cancer", "Google", "election", "sue", "suing"): |
| p = copy.deepcopy(GOOD_CRISIS) |
| p["consequence"] = f"Something about {word} happened." |
| assert validate_crisis(p, make_state(), "brad") is None, word |
|
|
|
|
| def test_mechanics_leak_rejected(): |
| for phrase in ("morale dropped by 5", "relationship_delta -1 applied", |
| "the event_log shows everything"): |
| p = copy.deepcopy(GOOD_CRISIS) |
| p["consequence"] = phrase |
| assert validate_crisis(p, make_state(), "brad") is None, phrase |
| |
| p = copy.deepcopy(GOOD_CRISIS) |
| p["consequence"] = "The relationship with TerraLogix survived, barely." |
| assert validate_crisis(p, make_state(), "brad") is not None |
|
|
|
|
| def test_grammar_cut_text_trimmed_to_sentence(): |
| from game.validator import _fit |
| |
| long = ("He says the board just approved a budget. Then he said the " |
| "thing about the 'synergistic") |
| out = _fit(long, len(long)) |
| assert out.endswith("budget.") |
| |
| assert _fit("It is still printing", 120) == "It is still printing" |
| |
| s = "Exactly at the cap." |
| assert _fit(s, len(s)) == s |
| |
| assert _fit("x" * 200, 200) is None |
|
|
|
|
| def test_event_intro_self_narration_rejected(): |
| state = make_state() |
| p = copy.deepcopy(GOOD_EVENT) |
| p["affected_npc"] = "derek" |
| p["intro"] = "Derek is sweating through his polo while staring at the wall." |
| assert validate_event(p, state) is None |
| p2 = copy.deepcopy(GOOD_EVENT) |
| p2["affected_npc"] = "derek" |
| p2["intro"] = "Boss. The wiki is on their conference wall. All of it." |
| assert validate_event(p2, state) is not None |
|
|
|
|
| def test_crisis_reaction_self_narration_rejected(): |
| p = copy.deepcopy(GOOD_CRISIS) |
| p["npc_reaction"] = "Brad will handle this, because Brad is a closer." |
| assert validate_crisis(p, make_state(), "brad") is None |
|
|
|
|
| def test_log_entry_gets_dollar_amount(): |
| p = copy.deepcopy(GOOD_CRISIS) |
| p["log_entry"] = "Brad did a thing without consequences listed" |
| out = validate_crisis(p, make_state(), "brad") |
| assert "$" in out["log_entry"] |
|
|
|
|
| def test_log_dollar_matches_applied_not_model_claim(): |
| |
| |
| state = make_state(revenue=10_000) |
| p = copy.deepcopy(GOOD_CRISIS) |
| p["revenue_delta"] = -200_000 |
| p["log_entry"] = "Brad torched the deal, resulting in a -$200000 outcome." |
| out = validate_crisis(p, state, "brad") |
| assert "$200" not in out["log_entry"], out["log_entry"] |
| assert "$10K" in out["log_entry"], out["log_entry"] |
|
|
|
|
| def test_log_floored_to_zero_says_no_impact(): |
| state = make_state(revenue=0) |
| p = copy.deepcopy(GOOD_CRISIS) |
| p["revenue_delta"] = -120_000 |
| p["log_entry"] = "Janet's cloud cost us, resulting in a -$120000 outcome." |
| out = validate_crisis(p, state, "brad") |
| assert "no revenue impact" in out["log_entry"].lower(), out["log_entry"] |
|
|
|
|
| def test_tool_names_allowed_companies_blocked(): |
| state = make_state() |
| ok = copy.deepcopy(GOOD_CRISIS) |
| ok["consequence"] = "The Google Docs file started arguing with itself." |
| assert validate_crisis(ok, state, "brad") is not None |
| bad = copy.deepcopy(GOOD_CRISIS) |
| bad["consequence"] = "Google signed our biggest deal of the year." |
| assert validate_crisis(bad, state, "brad") is None |
|
|
|
|
| GOOD_EVENT = { |
| "affected_npc": "kevin", |
| "category": "professional", |
| "headline": "Kevin's chart escaped", |
| "intro": "The chart is out there.", |
| "option_a": "Catch the chart.", |
| "option_b": "Deny the chart.", |
| "urgency": "It is multiplying.", |
| "setup_animation": "npc_confused", |
| "morale_preview": -3, |
| } |
|
|
|
|
| def test_event_dedup_against_log(): |
| state = make_state() |
| state.log("Event 1 β kevin's chart escaped containment. -$10K.") |
| assert validate_event(copy.deepcopy(GOOD_EVENT), state) is None |
|
|
|
|
| def test_event_passes_clean_log(): |
| assert validate_event(copy.deepcopy(GOOD_EVENT), make_state()) |
|
|
|
|
| def test_presentation_must_reference_logged_event(): |
| state = make_state() |
| state.log("Event 1 β Brad promised TerraLogix the moon. -$50K.") |
| p = { |
| "round": 1, "board_tone": "neutral", |
| "event_referenced": "Brad promised TerraLogix the moon", |
| "round_difficulty": "standard", |
| "option_a": "a", "option_b": "b", |
| "board_dialogue": "Explain the moon.", |
| } |
| assert validate_presentation(copy.deepcopy(p), state, 1, closing=False) |
| p["event_referenced"] = "the great flood of imaginary events" |
| assert validate_presentation(p, state, 1, closing=False) is None |
|
|