"""tests/regression/test_bot_merge_identity_2522.py

회귀 테스트 — task-2522 GitHub App bot merge identity hardening.

회장 §본질:
  task-2522의 목표는 "머지가 되게 하는 것"이 **아니다.** 이미 머지는 된다.
  목표는 **"누가 머지했는가"**를 자동화 기준에 맞게 고치는 것.
  owner_pat로 머지되면 functional success여도 autonomy success로 인정 X.

5 연속 mergedBy=JonghyukJeon (PR #68/#69/#70/#71/#72) 종결 박제 + token source 4 enum 분류.

영역 A — Token source 분류 (4건)
영역 B — owner_pat fallback fixture replay (PR #68~#72, 5건)
영역 C — capability probe + autonomy_score 변화 (5건)

총 14건. 회장 §6 단일 파일.

회장 §보안: raw token value를 어떤 형태로도 출력/기록 X. fixture token도 prefix만 사용.
gh api 실호출 금지 — 모두 runner mock / dataclass 직접 주입.
"""
from __future__ import annotations

import json
import subprocess
import sys
from pathlib import Path
from typing import Any, Dict, List

# ---------------------------------------------------------------------------
# Worktree root → sys.path (force position 0 to shadow /home/jay/workspace/utils)
# ---------------------------------------------------------------------------
_WORKTREE_ROOT = Path(__file__).resolve().parent.parent.parent
if str(_WORKTREE_ROOT) in sys.path:
    sys.path.remove(str(_WORKTREE_ROOT))
sys.path.insert(0, str(_WORKTREE_ROOT))

from utils.bot_merge_identity import (  # noqa: E402  # pyright: ignore[reportMissingImports]
    DEFAULT_AUDIT_JSONL_PATH,
    MergeIdentityAuditRecord,
    TokenSource,
    TOKEN_SOURCE_GITHUB_APP,
    TOKEN_SOURCE_GITHUB_ACTIONS,
    TOKEN_SOURCE_OWNER_PAT,
    TOKEN_SOURCE_UNKNOWN,
    append_audit_jsonl,
    build_audit_record,
    classify_token_source,
    compute_autonomy_score_delta,
    decide_autonomy_capability_gap,
    expected_bot_identity_for_actor,
    fingerprint_token_for_audit,
    probe_token_source_from_env,
)
from utils.merge_queue_executor import (  # noqa: E402  # pyright: ignore[reportMissingImports]
    OWNER_PAT_FALLBACK_BLOCKED,
    select_merge_token_decision,
)
from utils.repository_policy_adapter import (  # noqa: E402  # pyright: ignore[reportMissingImports]
    BlockedReason,
    RepositoryCapability,
    classify_capability_gap,
    probe_bot_merge_identity,
    reevaluate_bot_can_merge_with_token_source,
    select_merge_path,
)


# ===========================================================================
# Helper builders
# ===========================================================================

def cp(returncode: int = 0, stdout: str = "", stderr: str = "") -> subprocess.CompletedProcess:
    return subprocess.CompletedProcess(args=[], returncode=returncode, stdout=stdout, stderr=stderr)


def make_pr_view_runner(pr_payloads: Dict[int, Any]):
    """gh pr view <N> --json number,mergedBy 호출에 응답하는 runner factory."""

    def runner(args: List[str], cwd: Any = None) -> subprocess.CompletedProcess:
        del cwd
        if len(args) < 4 or args[0:3] != ["gh", "pr", "view"]:
            return cp(1, "", "unexpected call")
        try:
            n = int(args[3])
        except ValueError:
            return cp(1, "", "bad pr number")
        payload = pr_payloads.get(n)
        if payload is None:
            return cp(1, "", "Not Found")
        return cp(0, json.dumps(payload), "")

    return runner


def make_capability(**overrides) -> RepositoryCapability:
    base: dict[str, Any] = dict(
        can_squash_merge=True,
        requires_approval=False,
        requires_thread_resolution=False,
        auto_merge_enabled=True,
        bot_can_merge=True,
        admin_override_required=False,
    )
    base.update(overrides)
    return RepositoryCapability(**base)


# ---------------------------------------------------------------------------
# Fixtures: PR #68 ~ #72 (회장 §6 영역 B 5건)
#
# 5 연속 mergedBy=JonghyukJeon — owner_pat fallback 박제.
# raw token value는 fixture에 포함되지 않는다 (회장 §보안).
# ---------------------------------------------------------------------------
PR_68_FIXTURE: Dict[str, Any] = {
    "number": 68,
    "mergedBy": {"login": "JonghyukJeon", "type": "User"},
    "_meta": {"task_id": "task-2517", "mergedAt": "2026-05-09T00:53:24Z"},
}
PR_69_FIXTURE: Dict[str, Any] = {
    "number": 69,
    "mergedBy": {"login": "JonghyukJeon", "type": "User"},
    "_meta": {"task_id": "task-2519", "mergedAt": "2026-05-09T02:04:50Z"},
}
PR_70_FIXTURE: Dict[str, Any] = {
    "number": 70,
    "mergedBy": {"login": "JonghyukJeon", "type": "User"},
    "_meta": {"task_id": "task-2518", "mergedAt": "2026-05-09T04:50:46Z"},
}
PR_71_FIXTURE: Dict[str, Any] = {
    "number": 71,
    "mergedBy": {"login": "JonghyukJeon", "type": "User"},
    "_meta": {"task_id": "task-2520", "mergedAt": "2026-05-09T06:31:34Z"},
}
PR_72_FIXTURE: Dict[str, Any] = {
    "number": 72,
    "mergedBy": {"login": "JonghyukJeon", "type": "User"},
    "_meta": {"task_id": "task-2521", "mergedAt": "2026-05-09T13:30:00Z"},
}

# raw token 값은 prefix 5자만 사용 (회장 §보안 — 절대 실제 token X)
_FIXTURE_INSTALLATION_TOKEN_PREFIX = "ghs_aaaa"   # GitHub App installation
_FIXTURE_OWNER_PAT_CLASSIC_PREFIX = "ghp_zzzz"    # classic PAT
_FIXTURE_OWNER_PAT_FINEGRAINED_PREFIX = "github_pat_yyy"  # fine-grained PAT
# fixture 값들은 실제 GitHub token이 아니며 길이/엔트로피가 부족한 dummy.


# ===========================================================================
# 영역 A — Token source 분류 (4건)
# ===========================================================================


def test_a1_ghs_prefix_classified_as_github_app_installation_token():
    """(1) ``ghs_`` prefix token + actions runner 신호 없음 → GITHUB_APP_INSTALLATION_TOKEN."""
    probe = classify_token_source(
        token_value=_FIXTURE_INSTALLATION_TOKEN_PREFIX,
        env={"GITHUB_APP_INSTALLATION_TOKEN": _FIXTURE_INSTALLATION_TOKEN_PREFIX},
    )
    assert probe.token_source == TOKEN_SOURCE_GITHUB_APP
    assert probe.installation_signal is True
    assert probe.actions_runner_signal is False
    assert probe.token_prefix_observed == _FIXTURE_INSTALLATION_TOKEN_PREFIX[:5]
    # 회장 §보안: prefix는 5자, raw value는 어디에도 없음
    assert probe.token_fingerprint_sha256_8 is not None
    assert len(probe.token_fingerprint_sha256_8) == 8
    # raw 값 절대 없음
    serialised = json.dumps(probe.to_dict())
    assert _FIXTURE_INSTALLATION_TOKEN_PREFIX[5:] not in serialised, (
        "raw token suffix leaked into serialised probe"
    )


def test_a2_ghp_and_github_pat_prefix_classified_as_owner_pat():
    """(2) ``ghp_`` 또는 ``github_pat_`` prefix → OWNER_PAT."""
    # case A: classic PAT (ghp_)
    probe_a = classify_token_source(
        token_value=_FIXTURE_OWNER_PAT_CLASSIC_PREFIX,
        env={"GITHUB_PAT": _FIXTURE_OWNER_PAT_CLASSIC_PREFIX},
    )
    assert probe_a.token_source == TOKEN_SOURCE_OWNER_PAT
    assert probe_a.owner_pat_signal is True

    # case B: fine-grained PAT (github_pat_)
    probe_b = classify_token_source(
        token_value=_FIXTURE_OWNER_PAT_FINEGRAINED_PREFIX,
        env={"GH_PAT": _FIXTURE_OWNER_PAT_FINEGRAINED_PREFIX},
    )
    assert probe_b.token_source == TOKEN_SOURCE_OWNER_PAT
    assert probe_b.owner_pat_signal is True


def test_a3_github_actions_env_classified_as_actions_token():
    """(3) GITHUB_ACTIONS=true env (token 값 없음) → GITHUB_ACTIONS_TOKEN.

    runner GITHUB_TOKEN(ghs_)가 있어도 actions runner 환경에서는
    ACTIONS_TOKEN으로 분류된다 (출처가 명확).
    """
    # case A: env-only (token 값 없이 actions 신호만)
    probe_a = classify_token_source(
        token_value=None,
        env={"GITHUB_ACTIONS": "true", "RUNNER_NAME": "ubuntu-runner-1"},
    )
    assert probe_a.token_source == TOKEN_SOURCE_GITHUB_ACTIONS
    assert probe_a.actions_runner_signal is True

    # case B: ghs_ token + actions runner → ACTIONS_TOKEN (runner의 GITHUB_TOKEN)
    probe_b = classify_token_source(
        token_value=_FIXTURE_INSTALLATION_TOKEN_PREFIX,
        env={"GITHUB_ACTIONS": "true", "GITHUB_TOKEN": _FIXTURE_INSTALLATION_TOKEN_PREFIX},
    )
    assert probe_b.token_source == TOKEN_SOURCE_GITHUB_ACTIONS
    assert probe_b.actions_runner_signal is True


def test_a4_no_signals_classified_as_unknown():
    """(4) env 미설정, token 값 없음 → UNKNOWN."""
    probe = classify_token_source(token_value=None, env={})
    assert probe.token_source == TOKEN_SOURCE_UNKNOWN
    assert probe.actions_runner_signal is False
    assert probe.installation_signal is False
    assert probe.owner_pat_signal is False
    assert probe.token_prefix_observed is None
    assert probe.token_fingerprint_sha256_8 is None
    # 회장 §보안: enum 4종 정확
    assert TokenSource(probe.token_source) is TokenSource.UNKNOWN


# ===========================================================================
# 영역 B — owner_pat fallback fixture replay (PR #68~#72, 5건)
# ===========================================================================


def _replay_pr_owner_pat_fallback(pr_number: int, fixture: Dict[str, Any]) -> None:
    """공통 검증: PR fixture replay → owner_pat fallback 박제 + autonomy gap True."""
    runner = make_pr_view_runner({pr_number: fixture})
    identity = probe_bot_merge_identity(
        "Jeon-Jonghyuk", "dev_workspace", [pr_number], runner=runner,
    )
    # 회장 §본질 박제
    assert identity.fallback_to_owner_token_detected is True
    assert identity.token_source == "owner_pat"
    assert identity.bot_can_merge_as_app is False
    assert identity.merge_actor_login == "JonghyukJeon"
    assert identity.merge_actor_is_bot is False
    # classify → AUTOMATION_CAPABILITY_GAP
    blocked = classify_capability_gap(identity)
    assert blocked == BlockedReason.AUTOMATION_CAPABILITY_GAP

    # bot_merge_identity audit record로도 같은 결론
    record = build_audit_record(
        pr_number=pr_number,
        token_probe=classify_token_source(
            token_value=_FIXTURE_OWNER_PAT_CLASSIC_PREFIX,
            env={"GITHUB_PAT": _FIXTURE_OWNER_PAT_CLASSIC_PREFIX},
        ),
        mergedBy_login=fixture["mergedBy"]["login"],
        mergedBy_type=fixture["mergedBy"]["type"],
        merge_commit_sha=fixture.get("_meta", {}).get("mergeCommit"),
        task_id=fixture.get("_meta", {}).get("task_id"),
    )
    assert record.token_source_used == TOKEN_SOURCE_OWNER_PAT
    assert record.mergedBy_login == "JonghyukJeon"
    assert record.mergedBy_is_bot is False
    assert record.expected_bot_identity is False
    assert record.autonomy_capability_gap is True


def test_b1_pr_68_owner_pat_fallback_replay():
    """(5) PR #68 mergedBy=JonghyukJeon — task-2517 fixture replay."""
    _replay_pr_owner_pat_fallback(68, PR_68_FIXTURE)


def test_b2_pr_69_owner_pat_fallback_replay():
    """(6) PR #69 mergedBy=JonghyukJeon — task-2519 fixture replay."""
    _replay_pr_owner_pat_fallback(69, PR_69_FIXTURE)


def test_b3_pr_70_owner_pat_fallback_replay():
    """(7) PR #70 mergedBy=JonghyukJeon — task-2518 fixture replay."""
    _replay_pr_owner_pat_fallback(70, PR_70_FIXTURE)


def test_b4_pr_71_owner_pat_fallback_replay():
    """(8) PR #71 mergedBy=JonghyukJeon — task-2520 fixture replay."""
    _replay_pr_owner_pat_fallback(71, PR_71_FIXTURE)


def test_b5_pr_72_owner_pat_fallback_replay():
    """(9) PR #72 mergedBy=JonghyukJeon — task-2521 fixture replay (본 task의 직전 PR).

    회장 §본질: task-2521 자체가 owner_pat fallback으로 머지되어 5 연속 패턴 종결.
    select_merge_path → escalate_capability_gap. 회장 직접 머지 X (ops 채널만).
    """
    _replay_pr_owner_pat_fallback(72, PR_72_FIXTURE)

    # 추가 검증: 5 PR을 동시에 replay → audit 5건 모두 owner_pat
    runner = make_pr_view_runner({
        68: PR_68_FIXTURE, 69: PR_69_FIXTURE, 70: PR_70_FIXTURE,
        71: PR_71_FIXTURE, 72: PR_72_FIXTURE,
    })
    identity = probe_bot_merge_identity(
        "Jeon-Jonghyuk", "dev_workspace", [68, 69, 70, 71, 72], runner=runner,
    )
    assert len(identity.merge_identity_audit) == 5
    assert all(r.fallback_to_owner_token for r in identity.merge_identity_audit)
    assert identity.fallback_to_owner_token_detected is True
    assert identity.bot_can_merge_as_app is False

    plan = select_merge_path(
        {"number": 72},
        make_capability(),
        BlockedReason.AUTOMATION_CAPABILITY_GAP,
    )
    assert plan.action == "escalate_capability_gap"
    assert plan.requires_chair is False  # ops 채널만, 회장 직접 머지 X
    assert plan.capability_gap is True


# ===========================================================================
# 영역 C — capability probe + autonomy_score 변화 (5건)
# ===========================================================================


def test_c1_no_bot_app_token_classified_as_automation_capability_gap():
    """(10) bot/app token 없음 (UNKNOWN) → reevaluate가 bot_can_merge=False 강제.

    select_merge_token_decision → AUTOMATION_CAPABILITY_GAP (allow_merge=False).
    """
    probe = classify_token_source(token_value=None, env={})
    assert probe.token_source == TOKEN_SOURCE_UNKNOWN

    cap = make_capability(bot_can_merge=True)  # owner_pat probe 결과로 True가 나왔다고 가정
    reeval = reevaluate_bot_can_merge_with_token_source(cap, token_source=probe.token_source)
    assert reeval.bot_can_merge is False  # UNKNOWN → fail-closed

    decision = select_merge_token_decision(probe.token_source)
    assert decision["allow_merge"] is False
    assert decision["decision"] == "AUTOMATION_CAPABILITY_GAP"
    assert decision["capability_gap"] is True


def test_c2_app_token_with_ruleset_allows_bot_merge():
    """(11) bot/app token 있음 + ruleset 허용 → bot_can_merge 유지, allow_merge=True."""
    probe = classify_token_source(
        token_value=_FIXTURE_INSTALLATION_TOKEN_PREFIX,
        env={"GITHUB_APP_INSTALLATION_TOKEN": _FIXTURE_INSTALLATION_TOKEN_PREFIX},
    )
    assert probe.token_source == TOKEN_SOURCE_GITHUB_APP

    cap = make_capability(bot_can_merge=True)
    reeval = reevaluate_bot_can_merge_with_token_source(cap, token_source=probe.token_source)
    # GITHUB_APP_INSTALLATION_TOKEN이면 capability 유지
    assert reeval.bot_can_merge is True
    assert reeval == cap  # frozen dataclass equality (변경 없음)

    decision = select_merge_token_decision(probe.token_source)
    assert decision["allow_merge"] is True
    assert decision["decision"] == "APP_TOKEN_OK"
    assert decision["capability_gap"] is False


def test_c3_owner_merged_score_held_or_decreased():
    """(12) mergedBy = owner (사람) → autonomy_score 유지 또는 -1.

    회장 §본질: owner 직접 머지는 5 연속 패턴 → score 하락 신호.
    """
    probe = classify_token_source(
        token_value=_FIXTURE_OWNER_PAT_CLASSIC_PREFIX,
        env={"GITHUB_PAT": _FIXTURE_OWNER_PAT_CLASSIC_PREFIX},
    )
    record = build_audit_record(
        pr_number=72,
        token_probe=probe,
        mergedBy_login="JonghyukJeon",
        mergedBy_type="User",
        task_id="task-2521",
    )
    assert record.autonomy_capability_gap is True

    delta = compute_autonomy_score_delta(previous_score=7, record=record)
    assert delta.previous_score == 7
    assert delta.new_score == 6  # 7 → 6, owner_pat 감지 시 -1
    assert delta.delta == -1
    assert "owner_pat" in delta.reason.lower() or "owner direct" in delta.reason.lower()


def test_c4_bot_merged_with_app_token_score_increased():
    """(13) mergedBy = bot/app + token_source = GITHUB_APP_* → autonomy_score +1.

    회장 §본질: 본 task의 종결 목표 — 다음 PR이 bot으로 머지되면 7→8.
    """
    probe = classify_token_source(
        token_value=_FIXTURE_INSTALLATION_TOKEN_PREFIX,
        env={"GITHUB_APP_INSTALLATION_TOKEN": _FIXTURE_INSTALLATION_TOKEN_PREFIX},
    )
    record = build_audit_record(
        pr_number=999,
        token_probe=probe,
        mergedBy_login="github-actions[bot]",
        mergedBy_type="Bot",
        task_id="task-2522",
    )
    assert record.autonomy_capability_gap is False
    assert record.expected_bot_identity is True
    assert record.mergedBy_is_bot is True

    delta = compute_autonomy_score_delta(previous_score=7, record=record)
    assert delta.new_score == 8  # 7 → 8 (bot/app 머지 success)
    assert delta.delta == 1


def test_c5_owner_pat_fallback_blocks_auto_merge_with_marker():
    """(14) owner_pat fallback → 자동 머지 거부, AUTOMATION_CAPABILITY_GAP marker로 박제.

    회장 §5 명시:
      - owner_pat fallback 발생 → Critical 7종 아님, AUTOMATION_CAPABILITY_GAP marker
      - select_merge_token_decision은 OWNER_PAT_FALLBACK_BLOCKED + capability_gap=True
      - audit JSONL append + 다음 score 산출
    """
    probe = classify_token_source(
        token_value=_FIXTURE_OWNER_PAT_CLASSIC_PREFIX,
        env={"GITHUB_PAT": _FIXTURE_OWNER_PAT_CLASSIC_PREFIX},
    )
    decision = select_merge_token_decision(probe.token_source)
    assert decision["allow_merge"] is False
    assert decision["decision"] == OWNER_PAT_FALLBACK_BLOCKED
    assert decision["capability_gap"] is True

    cap = make_capability(bot_can_merge=True)
    reeval = reevaluate_bot_can_merge_with_token_source(cap, token_source=probe.token_source)
    assert reeval.bot_can_merge is False  # owner_pat → 강제 False

    # audit record + JSONL append (tmp 파일)
    record = build_audit_record(
        pr_number=72,
        token_probe=probe,
        mergedBy_login="JonghyukJeon",
        mergedBy_type="User",
        task_id="task-2522",
    )
    tmp = _WORKTREE_ROOT / "memory" / "events" / "test_bot_merge_identity_2522_tmp.jsonl"
    if tmp.exists():
        tmp.unlink()
    written_path = append_audit_jsonl(record, audit_path=tmp)
    assert written_path == tmp
    assert tmp.exists()
    line = tmp.read_text(encoding="utf-8").strip().splitlines()[-1]
    payload = json.loads(line)
    # raw token 값 절대 없음
    assert _FIXTURE_OWNER_PAT_CLASSIC_PREFIX[5:] not in line, (
        "raw token suffix leaked into JSONL audit"
    )
    assert payload["autonomy_capability_gap"] is True
    assert payload["token_source_used"] == TOKEN_SOURCE_OWNER_PAT
    tmp.unlink(missing_ok=True)

    # owner_pat fallback 시 score -1 박제
    delta = compute_autonomy_score_delta(previous_score=7, record=record)
    assert delta.new_score == 6


# ===========================================================================
# Sanity & helper tests (감사 추가 검증)
# ===========================================================================


def test_sanity_token_source_enum_exact_4_values():
    """sanity: TokenSource enum 정확히 4종 — 회장 §1 추가 금지."""
    values = {member.value for member in TokenSource}
    assert values == {
        TOKEN_SOURCE_OWNER_PAT,
        TOKEN_SOURCE_GITHUB_APP,
        TOKEN_SOURCE_GITHUB_ACTIONS,
        TOKEN_SOURCE_UNKNOWN,
    }


def test_sanity_default_audit_jsonl_path_inside_workspace():
    """sanity: default audit path가 memory/orchestration-audit/bot-merge-identity.jsonl."""
    p = str(DEFAULT_AUDIT_JSONL_PATH)
    assert p.endswith("bot-merge-identity.jsonl"), p


def test_sanity_no_raw_token_in_record_serialisation():
    """sanity: build_audit_record 결과 직렬화 시 raw token 5자 이후 부분 노출 X."""
    fake_token = "ghs_aaaaSECRETSUFFIX1234567890"
    probe = classify_token_source(
        token_value=fake_token,
        env={"GITHUB_APP_INSTALLATION_TOKEN": fake_token},
    )
    record = build_audit_record(
        pr_number=1,
        token_probe=probe,
        mergedBy_login="github-actions[bot]",
        mergedBy_type="Bot",
    )
    serialised = json.dumps(record.to_dict(), ensure_ascii=False)
    assert "SECRETSUFFIX" not in serialised
    # prefix 5자만 노출
    assert "ghs_a" in serialised


def test_sanity_expected_bot_identity_helper():
    """sanity: expected_bot_identity_for_actor — bot suffix or type==Bot → True."""
    assert expected_bot_identity_for_actor("github-actions[bot]", "Bot") is True
    assert expected_bot_identity_for_actor("dependabot[bot]", "User") is True
    assert expected_bot_identity_for_actor("renovate", "Bot") is True
    assert expected_bot_identity_for_actor("JonghyukJeon", "User") is False
    assert expected_bot_identity_for_actor("", "Bot") is False


def test_sanity_decide_autonomy_capability_gap_unknown_is_failclosed():
    """sanity: token_source=UNKNOWN → 무조건 GAP=True (fail-closed)."""
    assert decide_autonomy_capability_gap(
        token_source=TOKEN_SOURCE_UNKNOWN,
        mergedBy_login="github-actions[bot]",
        mergedBy_is_bot=True,
    ) is True


def test_sanity_fingerprint_truncation_to_8_hex_chars():
    """sanity: token fingerprint는 sha256의 첫 8 hex 문자만."""
    fp = fingerprint_token_for_audit("ghs_test_value_xyz")
    assert fp is not None
    assert len(fp) == 8
    int(fp, 16)  # hex 검증
    assert fingerprint_token_for_audit(None) is None
    assert fingerprint_token_for_audit("") is None


def test_sanity_probe_token_source_from_env_no_raw_value_returned():
    """sanity: probe_token_source_from_env() 결과는 raw token 값을 보유하지 않음."""
    probe = probe_token_source_from_env(env={"GITHUB_PAT": "ghp_abcdefghijk0123456789"})
    assert probe.token_source == TOKEN_SOURCE_OWNER_PAT
    serialised = json.dumps(probe.to_dict())
    assert "abcdefghijk" not in serialised
    assert "0123456789" not in serialised


def test_sanity_merge_identity_audit_record_dataclass_7_required_fields():
    """sanity: MergeIdentityAuditRecord 회장 §3 필수 7 field 존재."""
    from dataclasses import fields as _fields
    field_names = {f.name for f in _fields(MergeIdentityAuditRecord)}
    expected_required = {
        "pr_number",
        "token_source_used",
        "mergedBy_login",
        "mergedBy_is_bot",
        "expected_bot_identity",
        "autonomy_capability_gap",
        "timestamp",
    }
    assert expected_required.issubset(field_names), (
        f"required fields missing: {expected_required - field_names}"
    )


# ===========================================================================
# 회장 §본질 통합 — 5 PR fixture 동시 replay → score 7→2 (모두 owner_pat)
# ===========================================================================


def test_integration_5pr_consecutive_owner_pat_fallback_pattern():
    """integration: PR #68~#72 5건 모두 owner_pat fallback → score 7→2 (5회 -1).

    회장 §본질 5 연속 패턴 박제. score floor=0 검증.
    """
    fixtures = [
        (68, PR_68_FIXTURE),
        (69, PR_69_FIXTURE),
        (70, PR_70_FIXTURE),
        (71, PR_71_FIXTURE),
        (72, PR_72_FIXTURE),
    ]
    score = 7
    for pr_num, fix in fixtures:
        probe = classify_token_source(
            token_value=_FIXTURE_OWNER_PAT_CLASSIC_PREFIX,
            env={"GITHUB_PAT": _FIXTURE_OWNER_PAT_CLASSIC_PREFIX},
        )
        record = build_audit_record(
            pr_number=pr_num,
            token_probe=probe,
            mergedBy_login=fix["mergedBy"]["login"],
            mergedBy_type=fix["mergedBy"]["type"],
            task_id=fix.get("_meta", {}).get("task_id"),
        )
        delta = compute_autonomy_score_delta(previous_score=score, record=record)
        assert delta.delta == -1, f"PR #{pr_num}: 기대 -1, 실제 {delta.delta}"
        score = delta.new_score
    # 7 - 5 = 2
    assert score == 2
