"""require_owner=True 가 모든 transition path 에 적용 — AC-C7 (Block C B9)."""
from unittest.mock import MagicMock

import pytest

from app.core.errors import AppError
from app.core.step_runner import StepRunner


def _make_runner() -> StepRunner:
    runner = StepRunner.__new__(StepRunner)
    runner.step_id = "test"
    runner.project_id = "p1"
    runner.episode_id = "e1"
    runner.run_id = "r-self"
    runner.db = MagicMock()
    runner._now = lambda: "2026-05-07T00:00:00+00:00"
    runner._resolve_model = lambda: "test-model"
    return runner


def test_update_step_run_includes_run_id_when_require_owner():
    """require_owner=True 시 SQL 의 WHERE 절에 run_id 포함."""
    runner = _make_runner()
    result_mock = MagicMock()
    result_mock.fetchone.return_value = ("some-id",)
    runner.db.execute.return_value = result_mock

    ok = runner._update_step_run("completed", require_owner=True)

    assert ok is True
    sql_text = str(runner.db.execute.call_args[0][0])
    assert "run_id" in sql_text
    assert "AND step_run.run_id = :rid" in sql_text or "WHERE step_run.run_id = :rid" in sql_text


def test_update_step_run_omits_run_id_when_not_require_owner():
    """default require_owner=False 시 owner WHERE 절 제외."""
    runner = _make_runner()
    result_mock = MagicMock()
    result_mock.fetchone.return_value = ("some-id",)
    runner.db.execute.return_value = result_mock

    runner._update_step_run("running", require_owner=False)

    sql_text = str(runner.db.execute.call_args[0][0])
    assert "step_run.run_id = :rid" not in sql_text


def test_update_step_run_strict_raises_when_owner_lost():
    """V2 patch 추가 d: fetchone 이 None (owner mismatch) 시 step.owner_lost AppError."""
    runner = _make_runner()
    result_mock = MagicMock()
    result_mock.fetchone.return_value = None  # owner mismatch
    runner.db.execute.return_value = result_mock

    with pytest.raises(AppError) as exc_info:
        runner._update_step_run_strict("completed")

    assert exc_info.value.code == "step.owner_lost"
    assert exc_info.value.status_code == 409
