244 lines
9.1 KiB
Python
244 lines
9.1 KiB
Python
"""Unit tests for layered Anima prompt assembly (no LLM)."""
|
|
|
|
import os
|
|
import sys
|
|
from unittest.mock import patch
|
|
|
|
import pytest
|
|
|
|
# Ensure project root on path
|
|
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
|
|
|
from services import sd_prompt as sp
|
|
|
|
|
|
@pytest.fixture
|
|
def anima():
|
|
with patch.object(sp, "_is_anima", return_value=True), patch.object(sp, "_is_pony", return_value=False):
|
|
yield
|
|
|
|
|
|
PERSONA_WOLF = {
|
|
"appearance_tags": "wolfgirl, white_hair, golden_eyes, wolf_ears, tail, big_breast",
|
|
"appearance_prose": "",
|
|
"lora_name": "",
|
|
}
|
|
|
|
PERSONA_CARRIE = {
|
|
"appearance_tags": "short_hair, brown_hair, blue_eyes, skinny",
|
|
"appearance_prose": "",
|
|
"lora_name": "",
|
|
}
|
|
|
|
|
|
def test_walking_scene_includes_action_tags_and_contextual_pov(anima):
|
|
scene = sp._sanitize_scene_fields({
|
|
"shot_type": "first_person_pov",
|
|
"pov_cue": "walking_together",
|
|
"viewer_body_visible": False,
|
|
"action_tags": "holding_hands, walking, smiling, looking_at_each_other",
|
|
"environment_tags": "outdoors, sunlight, golden_hour",
|
|
"scene_description": "She walks beside you, laughter in the warm afternoon light.",
|
|
})
|
|
hybrid = sp.build_positive_prompt_hybrid(scene, PERSONA_WOLF, "")
|
|
assert "walking" in hybrid
|
|
assert "smiling" in hybrid
|
|
assert "holding_hands" not in hybrid
|
|
assert "looking_at_each_other" not in hybrid
|
|
assert "outdoors" in hybrid
|
|
assert "threshold" not in hybrid.lower()
|
|
assert "POV: walking beside you" in hybrid
|
|
assert "someone" not in hybrid.lower()
|
|
assert "both " not in hybrid.lower()
|
|
|
|
|
|
def test_hybrid_differs_from_tags_only_when_prose_present(anima):
|
|
scene = {
|
|
"shot_type": "first_person_pov",
|
|
"pov_cue": "walking_together",
|
|
"viewer_body_visible": False,
|
|
"action_tags": "holding_hands, walking",
|
|
"environment_tags": "outdoors, sunlight",
|
|
"scene_description": "Shared laughter drifts through the golden afternoon.",
|
|
}
|
|
tags_only = sp.build_positive_prompt_tags_only(scene, PERSONA_WOLF, "")
|
|
hybrid = sp.build_positive_prompt_hybrid(scene, PERSONA_WOLF, "")
|
|
assert tags_only != hybrid
|
|
assert "Shared laughter" in hybrid
|
|
assert "Shared laughter" not in tags_only
|
|
|
|
|
|
def test_carrie_doorway_scene(anima):
|
|
scene = {
|
|
"shot_type": "first_person_pov",
|
|
"pov_cue": "doorway_invite",
|
|
"viewer_body_visible": False,
|
|
"action_tags": "arms_out, inviting_hug, smirk, looking_at_viewer",
|
|
"environment_tags": "doorway, apartment, night, indoors",
|
|
"scene_description": "She waits in the doorway with playful hunger in half-lidded eyes.",
|
|
}
|
|
outfit = "crop_top, ripped_jeans, black_jeans"
|
|
hybrid = sp.build_positive_prompt_hybrid(scene, PERSONA_CARRIE, outfit)
|
|
assert "arms_out" in hybrid
|
|
assert "doorway" in hybrid
|
|
assert "crop_top" in hybrid
|
|
assert "threshold" not in hybrid.lower()
|
|
assert "POV: she blocks the doorway" in hybrid
|
|
|
|
|
|
def test_pov_inferred_from_action_when_cue_missing(anima):
|
|
scene = {
|
|
"shot_type": "first_person_pov",
|
|
"action_tags": "holding_hands, walking, smiling",
|
|
"environment_tags": "outdoors, park",
|
|
"scene_description": "",
|
|
}
|
|
tags = sp.build_positive_prompt_tags_only(scene, PERSONA_WOLF, "")
|
|
assert "POV: walking beside you" in tags
|
|
|
|
|
|
def test_negative_includes_interaction_block_for_pov_contact(anima):
|
|
scene = {
|
|
"shot_type": "first_person_pov",
|
|
"viewer_body_visible": False,
|
|
"action_tags": "arms_out, hug, inviting_hug",
|
|
"environment_tags": "doorway",
|
|
}
|
|
neg = sp._negative_for_scene(scene)
|
|
assert "duplicate" in neg
|
|
assert "extra_person" in neg
|
|
assert "third person" in neg
|
|
|
|
|
|
def test_scene_should_generate_false():
|
|
assert sp._scene_should_generate({"should_generate": False}) is False
|
|
assert sp._scene_should_generate({"should_generate": True}) is True
|
|
assert sp._scene_should_generate({}) is True
|
|
|
|
|
|
def test_format_builder_user_block_illustrate_vs_context(anima):
|
|
messages = [
|
|
{"role": "assistant", "content": "Long old first_mes " + ("x" * 900)},
|
|
{"role": "user", "content": "Hi"},
|
|
{"role": "assistant", "content": "*walks holding your hand*"},
|
|
]
|
|
block = sp._format_builder_user_block(PERSONA_WOLF, messages, "[]")
|
|
assert "=== ILLUSTRATE" in block
|
|
assert "=== Context" in block
|
|
assert "*walks holding your hand*" in block
|
|
assert "Long old first_mes" in block
|
|
assert len(block.split("Long old first_mes")[1].split("assistant:")[0]) < 900
|
|
|
|
|
|
def test_bundle_from_scene_anima_uses_hybrid_as_tag_full(anima):
|
|
scene = {
|
|
"should_generate": True,
|
|
"shot_type": "first_person_pov",
|
|
"pov_cue": "face_to_face",
|
|
"action_tags": "smiling",
|
|
"environment_tags": "indoors",
|
|
"scene_description": "A warm smile greets you.",
|
|
}
|
|
with patch.object(sp, "anima_dual_enabled", return_value=False):
|
|
bundle = sp._bundle_from_scene(scene, PERSONA_WOLF, "")
|
|
assert "A warm smile" in bundle.tag_full
|
|
assert bundle.desc_full is None
|
|
|
|
|
|
def test_user_example_walking_llm_output_cleaned(anima):
|
|
"""Regression: LLM prose/sentence leakage and second-person refs."""
|
|
scene = sp._sanitize_scene_fields({
|
|
"shot_type": "first_person_pov",
|
|
"pov_cue": "walking_together",
|
|
"action_tags": (
|
|
"holding_hands, walking, smiling, looking_at_each_other, "
|
|
"A wolfgirl walks hand in hand with someone, both smiling and chatting"
|
|
),
|
|
"environment_tags": "outdoor, daylight, path",
|
|
"scene_description": (
|
|
"A wolfgirl walks hand in hand with someone, both smiling and chatting under the daylight."
|
|
),
|
|
})
|
|
persona = {**PERSONA_WOLF, "appearance_tags": PERSONA_WOLF["appearance_tags"] + ", pumped_up"}
|
|
tags_only = sp.build_positive_prompt_tags_only(scene, persona, "")
|
|
hybrid = sp.build_positive_prompt_hybrid(scene, persona, "")
|
|
assert "pumped_up" not in tags_only
|
|
assert "someone" not in hybrid.lower()
|
|
assert "both " not in hybrid.lower()
|
|
assert ". A wolfgirl walks" not in tags_only
|
|
assert tags_only != hybrid or not scene.get("scene_description")
|
|
|
|
|
|
def test_user_example_carrie_env_reconciled(anima):
|
|
scene = sp._sanitize_scene_fields({
|
|
"shot_type": "first_person_pov",
|
|
"pov_cue": "doorway_invite",
|
|
"action_tags": "arms_out, inviting_hug, smirk, half-lidded_eyes",
|
|
"environment_tags": "doorway, nighttime, outdoor",
|
|
"scene_description": (
|
|
"Carrie stands in her doorway at night, arms outstretched toward you with a mischievous smirk."
|
|
),
|
|
})
|
|
hybrid = sp.build_positive_prompt_hybrid(
|
|
scene, PERSONA_CARRIE, "crop_top, ripped_jeans, black_jeans, jeans"
|
|
)
|
|
assert "outdoor" not in hybrid.lower() or "doorway" in hybrid
|
|
assert ", jeans," not in f", {hybrid},"
|
|
assert "someone" not in hybrid.lower()
|
|
|
|
|
|
def test_long_first_mes_uses_final_beat(anima):
|
|
carrie_tail = (
|
|
"About an hour later...\n\n"
|
|
"Carrie stood at her front door, arms out, smirking. "
|
|
'"Come on, hug me. Now." It\'s getting cold out.'
|
|
)
|
|
long = ("She shops for clothes.\n\n" * 5) + carrie_tail
|
|
excerpt = sp._extract_illustrate_content(long)
|
|
assert "front door" in excerpt or "hug me" in excerpt
|
|
assert "shops for clothes" not in excerpt
|
|
|
|
|
|
def test_hybrid_gets_fallback_when_no_scene_description(anima):
|
|
scene = sp._sanitize_scene_fields({
|
|
"shot_type": "first_person_pov",
|
|
"pov_cue": "walking_together",
|
|
"action_tags": "walking, smiling",
|
|
"environment_tags": "outdoor, daylight",
|
|
"scene_description": "",
|
|
})
|
|
tags_only = sp.build_positive_prompt_tags_only(scene, PERSONA_WOLF, "")
|
|
hybrid = sp.build_positive_prompt_hybrid(scene, PERSONA_WOLF, "")
|
|
assert hybrid != tags_only
|
|
assert "afternoon" in hybrid.lower() or "laughter" in hybrid.lower()
|
|
|
|
|
|
def test_yuki_pov_drops_lifting_and_nose_rub(anima):
|
|
scene = sp._sanitize_scene_fields({
|
|
"shot_type": "first_person_pov",
|
|
"pov_cue": "face_to_face",
|
|
"action_tags": "arms_out, lifting, nose_rub, smiling",
|
|
"environment_tags": "indoors, warm_lighting",
|
|
"scene_description": "Her golden eyes soften with warmth toward the camera.",
|
|
})
|
|
hybrid = sp.build_positive_prompt_hybrid(scene, {**PERSONA_WOLF, "appearance_tags": "fox_girl, golden_eyes"}, "pink_sweater")
|
|
assert "lifting" not in hybrid
|
|
assert "nose_rub" not in hybrid
|
|
assert "golden" in hybrid.lower()
|
|
|
|
|
|
def test_bundle_tags_only_alt_when_dual_compare(anima):
|
|
scene = {
|
|
"shot_type": "first_person_pov",
|
|
"pov_cue": "dialogue_close",
|
|
"action_tags": "smiling",
|
|
"environment_tags": "indoors",
|
|
"scene_description": "Soft light on her face.",
|
|
}
|
|
with patch.object(sp, "anima_dual_enabled", return_value=True):
|
|
bundle = sp._bundle_from_scene(scene, PERSONA_WOLF, "")
|
|
assert bundle.desc_full is not None
|
|
assert bundle.desc_full != bundle.tag_full
|
|
assert "Soft light" in bundle.tag_full
|
|
assert "Soft light" not in bundle.desc_full.split(sp.NEGATIVE_PROMPT_SEPARATOR)[0]
|