| """Comic-panel overlay backend: image_prompt soft-validation + FLUX client.""" |
| import copy |
| import os |
|
|
| from game import comic |
| from game.state import GameState |
| from game.validator import validate_crisis, validate_event |
|
|
| 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, |
| } |
|
|
| GOOD_EVENT = { |
| "affected_npc": "brad", |
| "category": "professional", |
| "headline": "The deck went to the wrong client", |
| "intro": "> A laptop is shoved toward you. 'I think I clicked send,' " |
| "comes the panicked whisper.", |
| "option_a": "Call the client and own it.", |
| "option_b": "Pretend it was a teaser.", |
| "urgency": "The client replies in five minutes.", |
| "setup_animation": "npc_confused", |
| "morale_preview": -3, |
| } |
|
|
| CLEAN_PROMPT = ("flat 2D comic panel, bold ink outlines, chibi office workers. " |
| "Panel 1: Brad sweating at a laptop, bubble 'I clicked send'.") |
|
|
|
|
| def _state(): |
| return GameState(session_id="t") |
|
|
|
|
| def test_clean_image_prompt_kept_on_crisis(): |
| p = copy.deepcopy(GOOD_CRISIS) |
| p["image_prompt"] = CLEAN_PROMPT |
| out = validate_crisis(p, _state(), "brad") |
| assert out is not None and out["image_prompt"] == CLEAN_PROMPT |
|
|
|
|
| def test_clean_image_prompt_kept_on_event(): |
| p = copy.deepcopy(GOOD_EVENT) |
| p["image_prompt"] = CLEAN_PROMPT |
| out = validate_event(p, _state()) |
| assert out is not None and out["image_prompt"] == CLEAN_PROMPT |
|
|
|
|
| def test_absent_image_prompt_is_fine(): |
| out = validate_crisis(copy.deepcopy(GOOD_CRISIS), _state(), "brad") |
| assert out is not None and "image_prompt" not in out |
|
|
|
|
| def test_mechanics_leak_drops_image_prompt_without_rejecting(): |
| p = copy.deepcopy(GOOD_CRISIS) |
| p["image_prompt"] = "comic panel showing morale dropping and revenue_delta" |
| out = validate_crisis(p, _state(), "brad") |
| |
| assert out is not None and "image_prompt" not in out |
|
|
|
|
| def test_blank_image_prompt_dropped(): |
| p = copy.deepcopy(GOOD_EVENT) |
| p["image_prompt"] = " " |
| out = validate_event(p, _state()) |
| assert out is not None and "image_prompt" not in out |
|
|
|
|
| def test_overlong_image_prompt_clamped(): |
| p = copy.deepcopy(GOOD_CRISIS) |
| p["image_prompt"] = "comic panel of Brad " + "x" * 500 |
| out = validate_crisis(p, _state(), "brad") |
| assert out is not None and len(out["image_prompt"]) <= 400 |
|
|
|
|
| def test_generate_comic_none_without_flux_url(): |
| saved = os.environ.pop("FLUX_URL", None) |
| try: |
| assert comic.flux_available() is False |
| assert comic.generate_comic(CLEAN_PROMPT) is None |
| finally: |
| if saved is not None: |
| os.environ["FLUX_URL"] = saved |
|
|
|
|
| def test_generate_comic_none_on_empty_prompt(): |
| assert comic.generate_comic("") is None |
|
|
|
|
| def test_comic_caption_kept(): |
| p = copy.deepcopy(GOOD_CRISIS) |
| p["comic_caption"] = "Brad discovers the send button is load-bearing." |
| out = validate_crisis(p, _state(), "brad") |
| assert out is not None and out["comic_caption"].startswith("Brad discovers") |
|
|
|
|
| def test_comic_caption_mechanics_dropped_event_survives(): |
| p = copy.deepcopy(GOOD_EVENT) |
| p["comic_caption"] = "This will tank morale and your relationship_delta." |
| out = validate_event(p, _state()) |
| assert out is not None and "comic_caption" not in out |
|
|
|
|
| def test_banned_in_image_prompt_drops_not_rejects(): |
| |
| |
| p = copy.deepcopy(GOOD_CRISIS) |
| p["image_prompt"] = CLEAN_PROMPT + " logo of Microsoft acquiring us" |
| out = validate_crisis(p, _state(), "brad") |
| assert out is not None and "image_prompt" not in out |
|
|