feat(host-protocol): define internal API models
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import UTC, datetime
|
||||
|
||||
import pytest
|
||||
from pydantic import ValidationError
|
||||
|
||||
from cloud.internal_api.models import (
|
||||
AssignmentModel,
|
||||
ClaimRequest,
|
||||
ClaimResponse,
|
||||
DeviceSnapshotModel,
|
||||
HeartbeatRequest,
|
||||
LeaseRenewalRequest,
|
||||
TerminalResultRequest,
|
||||
)
|
||||
|
||||
|
||||
def test_heartbeat_models_complete_device_snapshot() -> None:
|
||||
heartbeat = HeartbeatRequest(
|
||||
host_id="host-a",
|
||||
address="10.0.0.1:9000",
|
||||
devices=[
|
||||
DeviceSnapshotModel(
|
||||
device_id="device-a",
|
||||
driver_type="wda",
|
||||
status="idle",
|
||||
capability_tags=["ios", "physical"],
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
assert heartbeat.host_id == "host-a"
|
||||
assert heartbeat.devices[0].capability_tags == ["ios", "physical"]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("status", ["unreachable", "unknown", ""])
|
||||
def test_device_snapshot_rejects_invalid_host_status(status: str) -> None:
|
||||
with pytest.raises(ValidationError):
|
||||
DeviceSnapshotModel(
|
||||
device_id="device-a",
|
||||
driver_type="wda",
|
||||
status=status,
|
||||
)
|
||||
|
||||
|
||||
def test_claim_response_supports_assignment_and_normal_timeout() -> None:
|
||||
assignment = AssignmentModel(
|
||||
task_id="task-a",
|
||||
attempt=1,
|
||||
lease_id="lease-a",
|
||||
lease_expires_at=datetime(2026, 7, 12, tzinfo=UTC),
|
||||
host_id="host-a",
|
||||
device_id="device-a",
|
||||
goal="open settings",
|
||||
)
|
||||
|
||||
assert ClaimResponse(assignment=assignment).assignment == assignment
|
||||
assert ClaimResponse(timed_out=True).model_dump() == {
|
||||
"assignment": None,
|
||||
"timed_out": True,
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize("timeout_seconds", [-1, 61])
|
||||
def test_claim_timeout_is_bounded(timeout_seconds: float) -> None:
|
||||
with pytest.raises(ValidationError):
|
||||
ClaimRequest(host_id="host-a", timeout_seconds=timeout_seconds)
|
||||
|
||||
|
||||
def test_renewal_and_result_require_attempt_and_lease_identity() -> None:
|
||||
renewal = LeaseRenewalRequest(
|
||||
host_id="host-a",
|
||||
task_id="task-a",
|
||||
attempt=2,
|
||||
lease_id="lease-a",
|
||||
)
|
||||
result = TerminalResultRequest(
|
||||
host_id="host-a",
|
||||
task_id="task-a",
|
||||
attempt=2,
|
||||
lease_id="lease-a",
|
||||
status="failed",
|
||||
failure_reason="device offline",
|
||||
result={"step": 3},
|
||||
)
|
||||
|
||||
assert renewal.attempt == result.attempt == 2
|
||||
assert result.failure_reason == "device offline"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"payload",
|
||||
[
|
||||
{"host_id": "", "task_id": "task-a", "attempt": 1, "lease_id": "lease"},
|
||||
{"host_id": "host-a", "task_id": "", "attempt": 1, "lease_id": "lease"},
|
||||
{"host_id": "host-a", "task_id": "task-a", "attempt": 0, "lease_id": "lease"},
|
||||
{"host_id": "host-a", "task_id": "task-a", "attempt": 1, "lease_id": ""},
|
||||
],
|
||||
)
|
||||
def test_renewal_rejects_incomplete_identity(payload: dict[str, object]) -> None:
|
||||
with pytest.raises(ValidationError):
|
||||
LeaseRenewalRequest.model_validate(payload)
|
||||
Reference in New Issue
Block a user