"""합성·상태변형 단계가 **제 팩을 지문에 접는가** (2026-09-19).

## 무엇이 났나

`CompositeImageGenStep` 과 `CharacterStateVariantStep` 에는 `_config_hash`
가 **아예 없었다**. 둘 다 빈 dict 지문(`99914b932bd37a50`)을 그대로 썼다.

그래서 `ref_image_prompts` 팩을 고쳐도 스텝이 무효화되지 않는다 —
**고친 문안이 나가는 프롬프트까지 못 내려간다.** `#94`(cine_stage_direction)
와 `1-E`(prev_usage_clause) 가 같은 모양이었고, 하루에 두 번 났던 부류다.

실측으로 그 자리를 밟았다. 옷 참조의 마네킹이 합성에 같이 그려지는 결함을
팩 v7 으로 고쳤는데, 지문이 안 움직여서 보통 resume 으로는 옛 그림이 그대로
남는다.

## 이 시험이 보는 것

「함수가 있나」가 아니라 **값이 실제로 움직이나**를 본다. 있으면서 늘 같은
값을 내면 없는 것과 같다.
"""
from __future__ import annotations

from app.core.steps.image_steps import (CharacterStateVariantStep,
                                        CompositeImageGenStep,
                                        _fold_prompt_packs)

#: 빈 project_config 의 지문 — 접기 전 두 스텝이 쓰던 값 (실측).
_EMPTY = "99914b932bd37a50"


def _hash(cls) -> str:
    s = object.__new__(cls)
    s.project_config = {}
    return s._config_hash()


def test_neither_step_still_uses_the_empty_hash():
    for cls in (CompositeImageGenStep, CharacterStateVariantStep):
        got = _hash(cls)
        assert got != _EMPTY, (
            f"{cls.__name__} 이 아직 빈 dict 지문을 쓴다 — 팩을 고쳐도 "
            f"스텝이 stale 로 안 잡힌다")


def test_the_two_steps_do_not_share_a_hash():
    """서로 다른 팩을 쓰는데 같은 값이면 한쪽 수리가 다른 쪽을 끌고 돈다."""
    assert _hash(CompositeImageGenStep) != _hash(CharacterStateVariantStep)


def test_a_different_pack_version_moves_the_hash():
    """★핵심 — **버전이 달라지면 값이 달라져야** 접은 뜻이 있다."""
    a = _fold_prompt_packs("BASE", "ref_image_prompts",
                           ("character_composite_ref",))
    b = _fold_prompt_packs("BASE", "ref_image_prompts",
                           ("character_composite_derive_ref",))
    assert a != b
    # 같은 입력이면 같은 값 (결정적)
    assert a == _fold_prompt_packs("BASE", "ref_image_prompts",
                                   ("character_composite_ref",))
    # base 가 다르면 값도 다르다 — 팩만 보고 project_config 를 버리면 안 된다
    assert a != _fold_prompt_packs("OTHER", "ref_image_prompts",
                                   ("character_composite_ref",))


def test_an_unresolvable_stem_does_not_silently_match():
    """★못 읽은 것을 「없다」로 읽으면 지문이 조용히 옛 값과 같아진다."""
    good = _fold_prompt_packs("BASE", "ref_image_prompts",
                              ("character_composite_ref",))
    bad = _fold_prompt_packs("BASE", "ref_image_prompts",
                             ("그런_스템은_없다",))
    assert bad != good
    assert bad != "BASE"


def test_the_composite_step_folds_all_three_stems():
    """합성이 쓰는 세 문안 중 하나만 바뀌어도 잡혀야 한다."""
    three = ("character_outlook_ref", "character_composite_ref",
             "character_composite_derive_ref")
    full = _fold_prompt_packs("BASE", "ref_image_prompts", three)
    for drop in three:
        part = tuple(x for x in three if x != drop)
        assert full != _fold_prompt_packs("BASE", "ref_image_prompts", part), (
            f"{drop} 이 지문에 안 접혔다")
