"""지문 조회가 터져도 **세션을 부수고 가지 않는다**. ★유료 0.

실측 2026-09-01 (유료 canary 2판) —

    DB prompt load failed (entity_character_list/system):
      (psycopg2.errors.UndefinedTable) relation "prompt_template" does not exist
    DB schema load failed (...):
      (psycopg2.errors.InFailedSqlTransaction) current transaction is aborted

`prompt_template` 은 모델에도, `init_db` 의 raw 표 목록에도 **없다** — 새 DB 와
시험 DB(`theroad_test`)에 **아예 없다**. 그래서 `db=` 를 넘기는 스텝이 조회에서
터지고, PostgreSQL 이 그 세션의 **모든 뒤 문장을 거절**해 그 스텝이 제 일을
하다 죽었다. 그 뒤 30여 단계가 cascade 로 막혔다.

★「파일 fallback」은 **파일로 읽는다**는 뜻이지 **세션을 부수고 간다**는 뜻이
아니다.
"""
from __future__ import annotations

import pytest

from app.modules import prompt_loader as pl


class _Boom(RuntimeError):
    pass


class _DB:
    def __init__(self, *, fails=True):
        self.rolled_back = 0
        self._fails = fails

    def execute(self, *a, **k):
        if self._fails:
            raise _Boom("relation \"prompt_template\" does not exist")

        class _R:
            @staticmethod
            def fetchall():
                return []
        return _R()

    def rollback(self):
        self.rolled_back += 1


class TestABrokenLookupDoesNotPoisonTheSession:
    def test_it_rolls_back_before_raising(self):
        db = _DB()
        with pytest.raises(_Boom):
            pl._select_latest_active_row(db, "어떤모듈", "system")
        assert db.rolled_back == 1, "★되돌리지 않았다 — 세션이 깨진 채 남는다"

    def test_a_good_lookup_does_not_roll_back(self):
        """★멀쩡한 조회까지 되돌리면 부르는 쪽의 일이 사라진다."""
        db = _DB(fails=False)
        assert pl._select_latest_active_row(db, "어떤모듈", "system") is None
        assert db.rolled_back == 0

    def test_a_failing_rollback_is_not_swallowed_into_success(self):
        """★되돌리기가 실패해도 **원래 예외**가 올라간다."""
        class _Bad(_DB):
            def rollback(self):
                raise RuntimeError("되돌리기도 실패")

        with pytest.raises(_Boom):
            pl._select_latest_active_row(_Bad(), "어떤모듈", "system")

    @pytest.mark.parametrize("fn,name", [
        ("load_prompt", "system"),
        # ★입구마다 **그 입구가 읽는 이름**을 준다 — 아무 이름이나 주면
        #  파일이 없어 건너뛰고, 그러면 그 입구를 안 잰 것이다
        ("load_schema", "scene_detail_schema"),
        ("get_active_version", "system"),
    ])
    def test_every_public_entry_leaves_the_session_usable(self, fn, name):
        """★★공개 입구 셋 다 — 파일로 떨어지되 **세션은 살아 있다**.

        ★**진짜 있는** 모듈 이름을 쓴다. 없는 이름을 쓰면 파일 fallback 이
        `FileNotFoundError` 로 죽어 무엇을 재는지 흐려진다.
        """
        db = _DB()
        try:
            getattr(pl, fn)("scene_extractor_v2", name, db=db)
        except FileNotFoundError:
            pytest.skip("파일 지문이 없다 — 이 시험이 잴 것이 아니다")
        assert db.rolled_back >= 1, f"★{fn} 뒤 세션이 깨진 채다"
