test(036): backend schemas (22 tests) + frontend AgentRunModel (13 tests)
This commit is contained in:
204
backend/tests/services/agent_runs/test_schemas.py
Normal file
204
backend/tests/services/agent_runs/test_schemas.py
Normal file
@@ -0,0 +1,204 @@
|
||||
# backend/tests/services/agent_runs/test_schemas.py
|
||||
# #region Test.AgentRuns.Schemas [C:3] [TYPE Module] [SEMANTICS test,agent-run,schema,validation]
|
||||
# @BRIEF L1 unit tests for AgentRun Pydantic schemas — no DB dependency.
|
||||
# @RELATION BINDS_TO -> [Schemas.AgentRun]
|
||||
# @TEST_EDGE invalid_v2_context -> 422
|
||||
# @TEST_EDGE scenario_intent_with_v1 -> invalid
|
||||
# @TEST_EDGE unknown_intent -> invalid
|
||||
import pytest
|
||||
from pydantic import ValidationError as PydanticValidationError
|
||||
|
||||
from src.schemas.agent_run import (
|
||||
AgentRunSnapshot,
|
||||
AppendEventRequest,
|
||||
ApprovalDecisionRequest,
|
||||
ApprovalRequest,
|
||||
CreateAgentRunRequest,
|
||||
DraftArtifactRef,
|
||||
EventStatus,
|
||||
RegisterDraftRequest,
|
||||
StageEnum,
|
||||
UIContextV2,
|
||||
ValidationStatus,
|
||||
)
|
||||
|
||||
|
||||
class TestUIContextV2:
|
||||
def test_valid_v1_context_passes(self):
|
||||
ctx = UIContextV2(
|
||||
objectType="dashboard", objectId="42", envId="dev",
|
||||
route="/dashboards/42", contextVersion=1, intent=None,
|
||||
)
|
||||
assert ctx.objectId == "42"
|
||||
assert ctx.contextVersion == 1
|
||||
|
||||
def test_valid_v2_scenario_context_passes(self):
|
||||
ctx = UIContextV2(
|
||||
objectType="dashboard", objectId="42", envId="dev",
|
||||
route="/dashboards/42", contextVersion=2,
|
||||
intent="build_dashboard_test_scenario",
|
||||
)
|
||||
assert ctx.intent == "build_dashboard_test_scenario"
|
||||
|
||||
def test_scenario_intent_with_v1_is_invalid(self):
|
||||
with pytest.raises(PydanticValidationError):
|
||||
UIContextV2(
|
||||
objectType="dashboard", objectId="42", envId="dev",
|
||||
route="/dashboards/42", contextVersion=1,
|
||||
intent="build_dashboard_test_scenario",
|
||||
)
|
||||
|
||||
def test_v2_with_non_dashboard_rejected(self):
|
||||
with pytest.raises(PydanticValidationError):
|
||||
UIContextV2(
|
||||
objectType="dataset", objectId="1", envId="dev",
|
||||
route="/datasets/1", contextVersion=2,
|
||||
intent="build_dashboard_test_scenario",
|
||||
)
|
||||
|
||||
def test_unknown_intent_rejected(self):
|
||||
with pytest.raises(PydanticValidationError):
|
||||
UIContextV2(
|
||||
objectType="dashboard", objectId="42", envId="dev",
|
||||
route="/dashboards/42", contextVersion=2,
|
||||
intent="run_sql",
|
||||
)
|
||||
|
||||
def test_objectId_must_be_numeric(self):
|
||||
with pytest.raises(PydanticValidationError):
|
||||
UIContextV2(
|
||||
objectType="dashboard", objectId="abc", envId="dev",
|
||||
route="/dashboards/42", contextVersion=1,
|
||||
)
|
||||
|
||||
def test_route_must_start_with_dashboards_for_v2(self):
|
||||
ctx = UIContextV2(
|
||||
objectType="dashboard", objectId="42", envId="dev",
|
||||
route="/dashboards/42", contextVersion=2,
|
||||
intent="build_dashboard_test_scenario",
|
||||
)
|
||||
assert ctx.route.startswith("/dashboards/")
|
||||
|
||||
|
||||
class TestCreateAgentRunRequest:
|
||||
def test_valid_request(self):
|
||||
ctx = UIContextV2(
|
||||
objectType="dashboard", objectId="42", envId="dev",
|
||||
route="/dashboards/42", contextVersion=2,
|
||||
intent="build_dashboard_test_scenario",
|
||||
)
|
||||
req = CreateAgentRunRequest(context=ctx)
|
||||
assert req.context.intent == "build_dashboard_test_scenario"
|
||||
|
||||
def test_with_conversation_id(self):
|
||||
ctx = UIContextV2(
|
||||
objectType="dashboard", objectId="42", envId="dev",
|
||||
route="/dashboards/42", contextVersion=2,
|
||||
intent="build_dashboard_test_scenario",
|
||||
)
|
||||
req = CreateAgentRunRequest(context=ctx, conversation_id="conv-123")
|
||||
assert req.conversation_id == "conv-123"
|
||||
|
||||
|
||||
class TestAppendEventRequest:
|
||||
def test_valid_progress_event(self):
|
||||
req = AppendEventRequest(
|
||||
event_type="progress", stage=StageEnum.inspect,
|
||||
status=EventStatus.active, sequence=1,
|
||||
)
|
||||
assert req.event_type == "progress"
|
||||
assert req.stage == StageEnum.inspect
|
||||
|
||||
def test_sequence_must_be_positive(self):
|
||||
with pytest.raises(PydanticValidationError):
|
||||
AppendEventRequest(event_type="progress", sequence=0)
|
||||
|
||||
def test_event_type_required(self):
|
||||
with pytest.raises(PydanticValidationError):
|
||||
AppendEventRequest(sequence=1)
|
||||
|
||||
|
||||
class TestApprovalRequest:
|
||||
def test_valid_repository_write_gate(self):
|
||||
req = ApprovalRequest(
|
||||
operation="repository_write",
|
||||
request_hash="a" * 64,
|
||||
target_paths=["dashboard-tests/FI-0080/scenario.yaml"],
|
||||
required_permission="dashboard:testing:WRITE",
|
||||
)
|
||||
assert req.operation == "repository_write"
|
||||
assert len(req.target_paths) == 1
|
||||
|
||||
def test_reason_required_for_baseline_approval(self):
|
||||
req = ApprovalRequest(
|
||||
operation="baseline_approval",
|
||||
request_hash="b" * 64,
|
||||
target_paths=["baselines.yaml"],
|
||||
required_permission="dashboard:testing:APPROVE",
|
||||
reason_required=True,
|
||||
)
|
||||
assert req.reason_required is True
|
||||
|
||||
def test_empty_target_paths_rejected(self):
|
||||
with pytest.raises(PydanticValidationError):
|
||||
ApprovalRequest(
|
||||
operation="repository_write",
|
||||
request_hash="c" * 64,
|
||||
target_paths=[],
|
||||
required_permission="dashboard:testing:WRITE",
|
||||
)
|
||||
|
||||
|
||||
class TestApprovalDecisionRequest:
|
||||
def test_confirm_with_reason(self):
|
||||
req = ApprovalDecisionRequest(decision="confirm", reason="Initial QA baseline")
|
||||
assert req.decision == "confirm"
|
||||
|
||||
def test_deny_without_reason(self):
|
||||
req = ApprovalDecisionRequest(decision="deny")
|
||||
assert req.decision == "deny"
|
||||
|
||||
def test_invalid_decision_rejected(self):
|
||||
with pytest.raises(PydanticValidationError):
|
||||
ApprovalDecisionRequest(decision="maybe")
|
||||
|
||||
|
||||
class TestAgentRunSnapshot:
|
||||
def test_minimal_snapshot(self):
|
||||
snap = AgentRunSnapshot(
|
||||
id="run-1", user_id="user-1", intent="dashboard_scenario_build",
|
||||
trigger="manual", dashboard_id="42", environment_id="dev",
|
||||
context_snapshot={}, status="CREATED", last_sequence=0,
|
||||
created_at="2026-01-01T00:00:00Z", updated_at="2026-01-01T00:00:00Z",
|
||||
)
|
||||
assert snap.id == "run-1"
|
||||
assert snap.stages == []
|
||||
assert snap.drafts == []
|
||||
|
||||
|
||||
class TestDraftArtifactRef:
|
||||
def test_draft_ref(self):
|
||||
ref = DraftArtifactRef(
|
||||
id="draft-1", kind="scenario", name="test.yaml",
|
||||
intended_path="test.yaml", sha256="a" * 64,
|
||||
validation_status="pending",
|
||||
)
|
||||
assert ref.kind == "scenario"
|
||||
|
||||
|
||||
class TestRegisterDraftRequest:
|
||||
def test_valid_draft(self):
|
||||
req = RegisterDraftRequest(
|
||||
kind="scenario", name="scenario.yaml",
|
||||
intended_path="dashboard-tests/scenario.yaml",
|
||||
sha256="a" * 64,
|
||||
)
|
||||
assert req.intended_path == "dashboard-tests/scenario.yaml"
|
||||
|
||||
def test_invalid_sha256_length_rejected(self):
|
||||
with pytest.raises(PydanticValidationError):
|
||||
RegisterDraftRequest(
|
||||
kind="scenario", name="scenario.yaml",
|
||||
intended_path="test.yaml", sha256="too_short",
|
||||
)
|
||||
# #endregion Test.AgentRuns.Schemas
|
||||
Reference in New Issue
Block a user