Files
GEWU/tests/application/test_runs.py
2026-10-14 19:21:35 +08:00

107 lines
3.9 KiB
Python

from pathlib import Path
import pytest
from pydantic import ValidationError
from gewu.application.acceptance import ActorContext
from gewu.application.runs import LocalRunService, RunError
from gewu.domain.contracts import (
RunCheckpointRequest,
RunCreateRequest,
RunModulePin,
RunState,
Scope,
)
def _create(run_id: str = "run-pilot") -> RunCreateRequest:
return RunCreateRequest(
run_id=run_id,
objective="Compare audience response",
scope=Scope(projects=("dome-performance",), include_shared=False),
lens="Audience behavior",
pack_id="gewu",
pack_version="0.3.0",
pack_snapshot_digest="a" * 64,
module_pins=(
RunModulePin(
module_id="profile",
implementation_version="1.0.0",
sha256="b" * 64,
),
),
)
def _checkpoint(run_id: str = "run-pilot", **overrides: object) -> RunCheckpointRequest:
values: dict[str, object] = {
"run_id": run_id,
"expected_revision": 0,
"stage": "source-review",
"state": RunState.ACTIVE,
"budgets": {"source_count": 3},
}
values.update(overrides)
return RunCheckpointRequest.model_validate(values)
def test_run_create_and_checkpoint_are_idempotent_and_revision_checked(tmp_path: Path) -> None:
current = True
actor = ActorContext("agent-pilot", "pilot-key")
service = LocalRunService(
workflow_root=tmp_path / "workflow",
actor_is_current=lambda _: current,
)
created = service.create(_create(), actor=actor)
assert created.revision == 0
assert service.create(_create(), actor=actor) == created
with pytest.raises(RunError, match="different input"):
service.create(_create().model_copy(update={"objective": "Different"}), actor=actor)
checkpoint = _checkpoint()
updated = service.checkpoint(checkpoint, actor=actor)
assert updated.revision == 1
assert service.checkpoint(checkpoint, actor=actor) == updated
with pytest.raises(RunError, match="changed content"):
service.checkpoint(_checkpoint(stage="different-stage"), actor=actor)
service.checkpoint(_checkpoint(expected_revision=1, stage="next-stage"), actor=actor)
with pytest.raises(RunError, match="stale"):
service.checkpoint(_checkpoint(expected_revision=0, stage="different-stage"), actor=actor)
assert service.status("run-pilot", actor=actor).revision == 2
def test_run_owner_revocation_and_terminal_state_are_enforced(tmp_path: Path) -> None:
active = True
owner = ActorContext("agent-pilot", "pilot-key")
other = ActorContext("agent-other", "other-key")
service = LocalRunService(
workflow_root=tmp_path / "workflow",
actor_is_current=lambda actor: active and actor.credential_id in {"pilot-key", "other-key"},
)
service.create(_create(), actor=owner)
with pytest.raises(RunError, match="unavailable"):
service.status("run-pilot", actor=other)
completed = service.checkpoint(_checkpoint(state=RunState.COMPLETED), actor=owner)
assert completed.state is RunState.COMPLETED
with pytest.raises(RunError, match="cannot be updated"):
service.checkpoint(_checkpoint(expected_revision=1), actor=owner)
active = False
with pytest.raises(RunError, match="no longer active"):
service.checkpoint(_checkpoint(expected_revision=1), actor=owner)
with pytest.raises(RunError, match="no longer active"):
service.status("run-pilot", actor=owner)
def test_run_contract_rejects_global_scope_and_negative_budgets() -> None:
with pytest.raises(ValidationError):
RunCreateRequest.model_validate(
_create().model_dump() | {"scope": Scope(projects=("all",), all_projects=True)}
)
with pytest.raises(ValidationError):
RunCheckpointRequest.model_validate(
{"run_id": "run-pilot", "expected_revision": 0, "stage": "x", "budgets": {"tokens": -1}}
)