"""검열 대체가 주 제공자 기록을 **안 덮는다** (Codex BLOCK 1·2 · 2026-09-09).

## 무엇이 결함이었나

`_cine_moderation_fallback` 첫 판은 주와 대체가 **같은 저장 슬롯**을 썼다.
`cine_transform` 의 저장 키는 `{tag}::cine`, 파일은 `{tag}_cine.png` 로
고정이라 `multiroll_tag` 를 바꿔도 저장 신원에는 아무 영향이 없었다.

    MAI declined → Grok 성공이 MAI 거절 기록을 덮음
      → 같은 샷 재방문 때 다시 MAI 부터 호출
      → Grok 지문은 MAI 와 안 맞아 재사용도 못 함 → **둘 다 다시 산다**
      → 제공자별 거절 누적(`moderation_refusals`)도 사라져
        「둘 다 거절 후 확정」이 안 쌓인다

그리고 영속부는 `still_{tag}_cine` 만 조회해서, 대체 성공을 못 잇거나 그 샷의
옛 주 제공자 성공을 집었다 — 그러면 **Grok 그림이 MAI 모델로 기록된다.**

## 여기서 재는 것

    ① 주 경로는 **종전과 완전히 같다** (byte-identical)
    ② 대체는 저장 키·파일·호출 태그가 **전부** 갈린다
    ③ 승자 태그를 record 에서 읽는다 — 재사용 갈래에서도
    ④ 옛 record(이 필드가 없던 것)는 주 태그로 되돌아간다
"""
from __future__ import annotations

from pathlib import Path


def test_1_주_경로는_종전과_같다():
    """★슬롯을 안 주면 한 글자도 안 바뀌어야 한다."""
    from app.modules.pipeline.cine_transform import cine_output_path
    from app.services.still_recipe_service import _cine_tag

    assert cine_output_path(Path("/d"), "S1sh4").name == "S1sh4_cine.png"
    assert _cine_tag("S1sh4") == "still_S1sh4_cine"


def test_2_대체는_저장_키_파일_태그가_전부_갈린다():
    """★슬롯은 **제공자 이름**이라 여럿을 차례로 태워도 서로 안 덮는다."""
    from app.modules.pipeline.cine_transform import cine_output_path
    from app.services.still_recipe_service import _cine_tag

    slot = "fb_grok"
    main_f = cine_output_path(Path("/d"), "S1sh4").name
    fb_f = cine_output_path(Path("/d"), "S1sh4", slot).name
    assert fb_f != main_f, "대체가 주 파일을 덮는다"
    assert fb_f == "S1sh4_cine_fb_grok.png"

    assert _cine_tag("S1sh4", slot) != _cine_tag("S1sh4")

    # ★제공자가 다르면 슬롯도 달라야 한다 — grok 결과를 seedream 이 덮으면
    #  「둘 다 거절」과 「grok 만 거절」을 못 가른다.
    other = cine_output_path(Path("/d"), "S1sh4", "fb_seedream").name
    assert other != fb_f, "제공자가 달라도 같은 파일을 쓴다"

    # 저장 키도 갈려야 한다. ★규칙이 **한 자리**(`cine_record_key`)로
    #  나왔으니 소스 문자열이 아니라 **함수로** 본다 — 기록을 쓰는 쪽과
    #  발송 맥락을 씌우는 소유자가 같은 것을 쓴다(2026-09-20 Codex BLOCK 2).
    import inspect

    from app.modules.pipeline import cine_transform as CT
    from app.modules.pipeline.cine_transform import cine_record_key

    assert cine_record_key("S1sh4", slot) != cine_record_key("S1sh4"), (
        "저장 키가 슬롯을 안 탄다 — 대체가 주 기록을 덮는다")
    assert cine_record_key("S1sh4", slot) == f"S1sh4::cine::{slot}"
    assert cine_record_key("S1sh4") == "S1sh4::cine"
    # ★기록부가 그 함수를 **실제로** 쓴다 — 두 곳에 적으면 한쪽만 고쳐진다
    src = inspect.getsource(CT.resolve_or_run_cine_transform)
    assert "rec_key = cine_record_key(tag, slot)" in src, (
        "기록부가 키를 손으로 다시 적는다")


def test_3_승자_태그를_record_에서_읽는다():
    from app.services.still_recipe_service import _winner_cine_tag

    assert _winner_cine_tag(
        {"multiroll_tag": "still_S1sh4_cine_fb"}, "S1sh4"
    ) == "still_S1sh4_cine_fb"
    assert _winner_cine_tag(
        {"multiroll_tag": "still_S1sh4_cine"}, "S1sh4"
    ) == "still_S1sh4_cine"


def test_4_옛_record_는_주_태그로_되돌아간다():
    """이 필드가 없던 시절의 record 호환."""
    from app.services.still_recipe_service import _winner_cine_tag

    assert _winner_cine_tag({}, "S1sh4") == "still_S1sh4_cine"
    assert _winner_cine_tag(None, "S1sh4") == "still_S1sh4_cine"
    assert _winner_cine_tag({"multiroll_tag": "  "}, "S1sh4") == "still_S1sh4_cine"


def test_5_record_가_제_호출_태그를_담는다():
    """★영속부가 이 값으로 자산의 호출·모델을 찾는다. 안 담으면 못 잇는다."""
    import inspect

    from app.modules.pipeline import cine_transform as CT

    src = inspect.getsource(CT.resolve_or_run_cine_transform)
    assert '"multiroll_tag": str((context or {}).get("multiroll_tag") or "")' in src
    assert '"slot": slot,' in src


def test_6_영속부가_주_태그를_손으로_안_적는다():
    """★두 곳에 적으면 한쪽만 고쳐진다 — 실제로 그래서 BLOCK 이 났다."""
    import inspect

    from app.services import still_recipe_service as S

    src = inspect.getsource(S)
    # 승자 태그 helper 를 지나지 않는 손 조립이 남아 있으면 안 된다.
    assert 'multiroll_tag=f"still_{tag}_cine"' not in src, (
        "영속부가 주 태그를 손으로 적는다 — 대체 산출을 못 잇는다")
    assert '"multiroll_tag": f"still_{tag}_cine"' not in src, (
        "호출부가 태그를 손으로 적는다 — helper 를 지나야 한다")
