Files
awoooi/apps/api/tests/test_paid_provider_canary_gate5_run.py
ogt 541a32a9cb
Some checks failed
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 2m38s
CD Pipeline / build-and-deploy (push) Failing after 24m19s
CD Pipeline / post-deploy-checks (push) Has been skipped
feat(sre): enforce typed controlled automation
2026-07-16 17:32:01 +08:00

409 lines
14 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from __future__ import annotations
import os
from contextlib import asynccontextmanager
from datetime import UTC, datetime, timedelta
from uuid import UUID
import pytest
from fastapi import HTTPException
from pydantic import ValidationError
os.environ.setdefault(
"DATABASE_URL",
"postgresql+asyncpg://test:test@localhost/test",
)
from src.api.v1.platform import operator_runs # noqa: E402
from src.api.v1.platform import runs as public_runs # noqa: E402
from src.core.awooop_operator_auth import AwoooPOperatorPrincipal # noqa: E402
from src.db.awooop_models import AwoooPRunState # noqa: E402
from src.services import platform_operator_service, platform_runtime # noqa: E402
from src.services.paid_provider_canary_authorization import ( # noqa: E402
PAID_CANARY_AGENT_ID,
PAID_CANARY_PROJECT_ID,
PAID_CANARY_TRIGGER_REF,
PAID_CANARY_TRIGGER_TYPE,
paid_provider_canary_authorization_input_sha256,
)
from src.services.paid_provider_canary_gate5_run import ( # noqa: E402
PAID_CANARY_AUTHORIZATION_SCOPE,
PaidProviderCanaryGate5RunRecord,
create_paid_provider_canary_gate5_run,
paid_provider_canary_approval_contract_errors,
)
_SELECTED_RUN_ID = UUID("267a3d26-3c29-470f-8fe3-388a2f408f39")
class _Repository:
def __init__(self, *, duplicate: bool = False, state: str = "waiting_approval") -> None:
self.duplicate = duplicate
self.state = state
self.calls: list[dict[str, object]] = []
async def create_or_get(self, **kwargs: object) -> tuple[
PaidProviderCanaryGate5RunRecord,
bool,
]:
self.calls.append(kwargs)
run_id = kwargs["run_id"]
timeout_at = kwargs["timeout_at"]
input_sha256 = kwargs["input_sha256"]
trace_id = kwargs["trace_id"]
assert isinstance(run_id, UUID)
assert isinstance(timeout_at, datetime)
assert isinstance(input_sha256, str)
assert isinstance(trace_id, str)
if self.duplicate:
run_id = _SELECTED_RUN_ID
return (
PaidProviderCanaryGate5RunRecord(
run_id=run_id,
project_id=PAID_CANARY_PROJECT_ID,
agent_id=PAID_CANARY_AGENT_ID,
state=self.state,
trigger_type=PAID_CANARY_TRIGGER_TYPE,
trigger_ref=PAID_CANARY_TRIGGER_REF,
is_shadow=False,
input_sha256=input_sha256,
timeout_at=timeout_at,
trace_id=trace_id,
),
self.duplicate,
)
@pytest.mark.asyncio
async def test_operator_service_creates_exact_bounded_non_shadow_gate5_run() -> None:
now = datetime(2026, 7, 16, 9, 0, tzinfo=UTC)
repository = _Repository()
audit_calls: list[dict[str, object]] = []
async def audit_writer(**kwargs: object) -> None:
audit_calls.append(kwargs)
response = await create_paid_provider_canary_gate5_run(
operator_id="telegram:123456",
repository=repository,
audit_writer=audit_writer,
now=now,
)
assert response["project_id"] == "awoooi"
assert response["work_item_id"] == "AIA-SRE-013"
assert response["agent_id"] == "awoooi-paid-provider-canary"
assert response["state"] == "waiting_approval"
assert response["is_shadow"] is False
assert response["trigger_type"] == "api"
assert response["trigger_ref"] == "paid-provider-canary:AIA-SRE-013"
assert response["input_sha256"] == paid_provider_canary_authorization_input_sha256()
assert response["authorization_scope"] == PAID_CANARY_AUTHORIZATION_SCOPE
assert response["provider_order"] == [
"ollama_gcp_a",
"ollama_gcp_b",
"ollama_local",
"claude",
"gemini",
]
assert response["paid_provider_canary_percent"] == 5
assert response["canary_bucket"] < 5
assert response["authorization_run_is_execution_run"] is True
assert response["max_output_tokens_per_paid_provider"] == 512
assert response["max_total_paid_cost_usd"] == "0.25"
assert response["timeout_at"] == now + timedelta(minutes=15)
assert response["approval_ttl_seconds"] == 900
assert response["provider_call_performed"] is False
assert response["route_change_performed"] is False
assert response["infrastructure_write_performed"] is False
assert response["raw_prompt_persistence_allowed"] is False
assert response["raw_response_persistence_allowed"] is False
assert response["next_action"] == "operator_gate5_decision_required"
assert UUID(response["authorization_ref"]) == UUID(response["run_id"])
assert "telegram:123456" not in str(response)
assert len(repository.calls) == 1
call = repository.calls[0]
assert call["now"] == now
assert call["timeout_at"] == now + timedelta(minutes=15)
assert call["input_sha256"] == paid_provider_canary_authorization_input_sha256()
assert len(audit_calls) == 1
assert audit_calls[0]["resource_id"] == response["run_id"]
assert audit_calls[0]["run_id"] == response["run_id"]
details = audit_calls[0]["details"]
assert isinstance(details, dict)
assert details["provider_call_performed"] is False
assert details["route_change_performed"] is False
@pytest.mark.asyncio
async def test_duplicate_active_authorization_is_reused_without_scope_expansion() -> None:
repository = _Repository(duplicate=True, state="running")
async def audit_writer(**_: object) -> None:
return None
response = await create_paid_provider_canary_gate5_run(
operator_id="ops@example.com",
repository=repository,
audit_writer=audit_writer,
now=datetime(2026, 7, 16, 9, 0, tzinfo=UTC),
)
assert response["is_duplicate"] is True
assert response["state"] == "running"
assert response["run_id"] == str(_SELECTED_RUN_ID)
assert response["max_total_paid_cost_usd"] == "0.25"
assert response["infrastructure_write_performed"] is False
assert response["next_action"] == (
"verify_authorization_then_run_bounded_canary"
)
def _run(
*,
now: datetime,
agent_id: str = PAID_CANARY_AGENT_ID,
trigger_ref: str = PAID_CANARY_TRIGGER_REF,
) -> AwoooPRunState:
return AwoooPRunState(
run_id=_SELECTED_RUN_ID,
project_id=PAID_CANARY_PROJECT_ID,
agent_id=agent_id,
state="waiting_approval",
trace_id="00-0123456789abcdef0123456789abcdef-0123456789abcdef-01",
trigger_type=PAID_CANARY_TRIGGER_TYPE,
trigger_ref=trigger_ref,
is_shadow=False,
input_sha256=paid_provider_canary_authorization_input_sha256(),
timeout_at=now + timedelta(minutes=15),
)
def test_paid_canary_approval_contract_is_exact_and_expiry_bound() -> None:
now = datetime(2026, 7, 16, 9, 0, tzinfo=UTC)
exact = _run(now=now)
expired = _run(now=now)
expired.timeout_at = now - timedelta(seconds=1)
shadow = _run(now=now)
shadow.is_shadow = True
drifted_hash = _run(now=now)
drifted_hash.input_sha256 = "0" * 64
assert paid_provider_canary_approval_contract_errors(exact, now=now) == []
assert paid_provider_canary_approval_contract_errors(expired, now=now) == [
"timeout_not_expired"
]
assert paid_provider_canary_approval_contract_errors(shadow, now=now) == [
"non_shadow"
]
assert paid_provider_canary_approval_contract_errors(
drifted_hash,
now=now,
) == ["input_sha256_exact"]
def test_unrelated_generic_run_does_not_enter_paid_canary_contract() -> None:
now = datetime(2026, 7, 16, 9, 0, tzinfo=UTC)
generic = _run(
now=now,
agent_id="generic-agent",
trigger_ref="generic:api:request",
)
assert paid_provider_canary_approval_contract_errors(generic, now=now) is None
@pytest.mark.asyncio
async def test_generic_approval_service_rejects_expired_paid_canary_before_transition(
monkeypatch: pytest.MonkeyPatch,
) -> None:
now = datetime.now(UTC)
expired = _run(now=now)
expired.timeout_at = now - timedelta(seconds=1)
class _Result:
def scalar_one_or_none(self) -> AwoooPRunState:
return expired
class _Db:
async def execute(self, *_: object, **__: object) -> _Result:
return _Result()
@asynccontextmanager
async def fake_db_context(project_id: str):
assert project_id == "awoooi"
yield _Db()
monkeypatch.setattr(
platform_operator_service,
"get_db_context",
fake_db_context,
)
with pytest.raises(HTTPException) as exc:
await platform_operator_service.decide_approval(
run_id=str(expired.run_id),
project_id="awoooi",
decision="approve",
approver_id="telegram:123456",
reason="bounded canary",
)
assert exc.value.status_code == 409
assert "timeout_not_expired" in str(exc.value.detail)
@pytest.mark.asyncio
async def test_operator_route_uses_authenticated_principal_and_fixed_body(
monkeypatch: pytest.MonkeyPatch,
) -> None:
captured: dict[str, object] = {}
async def fake_create(**kwargs: object) -> dict[str, object]:
captured.update(kwargs)
return {
"schema_version": "paid_provider_canary_gate5_run_v1",
"authorization_ref": "018f2d04-4c37-7a18-b764-df0df0cbe111",
"run_id": "018f2d04-4c37-7a18-b764-df0df0cbe111",
"project_id": "awoooi",
"work_item_id": "AIA-SRE-013",
"agent_id": "awoooi-paid-provider-canary",
"state": "waiting_approval",
"is_shadow": False,
"is_duplicate": False,
"trigger_type": "api",
"trigger_ref": "paid-provider-canary:AIA-SRE-013",
"input_sha256": "1" * 64,
"authorization_scope": PAID_CANARY_AUTHORIZATION_SCOPE,
"provider_order": [
"ollama_gcp_a",
"ollama_gcp_b",
"ollama_local",
"claude",
"gemini",
],
"paid_provider_canary_percent": 5,
"max_output_tokens_per_paid_provider": 512,
"max_total_paid_cost_usd": "0.25",
"timeout_at": datetime(2026, 7, 16, 9, 15, tzinfo=UTC),
"approval_ttl_seconds": 900,
"provider_call_performed": False,
"route_change_performed": False,
"infrastructure_write_performed": False,
"raw_prompt_persistence_allowed": False,
"raw_response_persistence_allowed": False,
"next_action": "operator_gate5_decision_required",
"approval_path": (
"/api/v1/platform/approvals/"
"018f2d04-4c37-7a18-b764-df0df0cbe111/decide"
),
}
monkeypatch.setattr(
operator_runs,
"create_paid_provider_canary_gate5_run_svc",
fake_create,
)
body = operator_runs.CreatePaidProviderCanaryAuthorizationRequest(
work_item_id="AIA-SRE-013",
authorization_scope=PAID_CANARY_AUTHORIZATION_SCOPE,
)
principal = AwoooPOperatorPrincipal(
operator_id="telegram:123456",
auth_method="operator_api_key",
)
response = await operator_runs.create_paid_provider_canary_authorization(
body,
principal,
)
validated = (
operator_runs.CreatePaidProviderCanaryAuthorizationResponse.model_validate(
response
)
)
assert response["state"] == "waiting_approval"
assert validated.provider_order[-2:] == ("claude", "gemini")
assert captured == {"operator_id": "telegram:123456"}
with pytest.raises(ValidationError):
operator_runs.CreatePaidProviderCanaryAuthorizationRequest.model_validate(
{
"work_item_id": "OTHER",
"authorization_scope": PAID_CANARY_AUTHORIZATION_SCOPE,
}
)
def test_paid_canary_route_declares_operator_auth_dependency() -> None:
route = next(
route
for route in operator_runs.router.routes
if getattr(route, "path", "")
== "/approvals/paid-provider-canary/authorize"
)
assert any(
dependency.call is operator_runs.verify_awooop_operator
for dependency in route.dependant.dependencies
)
@pytest.mark.asyncio
async def test_generic_runtime_service_persists_shadow_true(
monkeypatch: pytest.MonkeyPatch,
) -> None:
captured: list[AwoooPRunState] = []
class _Db:
def add(self, run: AwoooPRunState) -> None:
captured.append(run)
@asynccontextmanager
async def fake_db_context(project_id: str):
assert project_id == "awoooi"
yield _Db()
monkeypatch.setattr(platform_runtime, "get_db_context", fake_db_context)
_, is_duplicate = await platform_runtime.create_run(
project_id="awoooi",
agent_id="generic-agent",
trigger_type="api",
input_payload={"safe": True},
)
assert is_duplicate is False
assert len(captured) == 1
assert captured[0].is_shadow is True
assert captured[0].state == "pending"
@pytest.mark.asyncio
async def test_generic_public_run_creation_remains_shadow_only(
monkeypatch: pytest.MonkeyPatch,
) -> None:
async def fake_create_run(**_: object) -> tuple[UUID, bool]:
return UUID("018f2d04-4c37-7a18-b764-df0df0cbe222"), False
async def fake_write_audit(**kwargs: object) -> None:
details = kwargs["details"]
assert isinstance(details, dict)
assert details["is_shadow"] is True
monkeypatch.setattr(public_runs, "create_run", fake_create_run)
monkeypatch.setattr(public_runs, "write_audit", fake_write_audit)
response = await public_runs.create_platform_run(
public_runs.CreateRunRequest(
project_id="awoooi",
agent_id="generic-agent",
trigger_type="api",
)
)
assert response.is_shadow is True
assert response.message == "Run 已接受shadow mode"