106 lines
3.9 KiB
Python
106 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}}
|
||
|
|
)
|