feat(host-agent): fail-fast cloud assignment when MCP holds device
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -180,6 +180,63 @@ def test_workflow_assignment_maps_cancellation_stop_to_cancelled_status() -> Non
|
||||
}
|
||||
|
||||
|
||||
def test_execute_fails_fast_when_mcp_session_holds_device() -> None:
|
||||
"""Cloud assignment arriving for a device currently held by an MCP
|
||||
session must fail immediately rather than fight for the device."""
|
||||
from host_agent.mcp_lock import McpBusyTracker
|
||||
|
||||
tracker = McpBusyTracker()
|
||||
tracker.acquire("phone-1", "sess-mcp")
|
||||
executor = AssignmentExecutor(
|
||||
_build_factories(),
|
||||
mcp_busy_tracker=tracker,
|
||||
)
|
||||
assignment = _assignment(device_id="phone-1")
|
||||
result = executor.execute(assignment)
|
||||
assert result.status == "failed"
|
||||
assert "MCP" in (result.failure_reason or "")
|
||||
|
||||
|
||||
def test_execute_skips_check_when_tracker_is_none() -> None:
|
||||
"""Default backward-compat: no tracker → no fail-fast."""
|
||||
executor = AssignmentExecutor(_build_factories())
|
||||
# Without a real workflow store / task runner this test verifies the
|
||||
# entry-point path doesn't raise on the mcp_busy check.
|
||||
# We use a goal + a mock runner factory so execute() runs through.
|
||||
assignment = _assignment()
|
||||
result = executor.execute(assignment)
|
||||
# Should run through normally (not fail on MCP check)
|
||||
assert result.status == "done"
|
||||
|
||||
|
||||
def _build_factories() -> ExecutionFactories:
|
||||
"""Shared factory fixture used by MCP-hold tests."""
|
||||
received: list[Task] = []
|
||||
|
||||
class FakeTaskRunner:
|
||||
def run(self, task: Task) -> Task:
|
||||
received.append(task)
|
||||
task.status = "completed"
|
||||
return task
|
||||
|
||||
class FakeMetadataStore:
|
||||
def create_task(
|
||||
self,
|
||||
task: Task,
|
||||
*,
|
||||
source_task_id: str | None = None,
|
||||
source_attempt: int | None = None,
|
||||
) -> None:
|
||||
pass
|
||||
|
||||
return ExecutionFactories(
|
||||
task_runner_factory=lambda: FakeTaskRunner(), # type: ignore[arg-type,return-value]
|
||||
workflow_runner_factory=lambda: object(), # type: ignore[arg-type,return-value]
|
||||
workflow_store=object(), # type: ignore[arg-type]
|
||||
metadata_store=FakeMetadataStore(), # type: ignore[arg-type]
|
||||
)
|
||||
|
||||
|
||||
def test_unknown_workflow_fails_without_running() -> None:
|
||||
class FakeWorkflowStore:
|
||||
def get_definition(self, definition_id: str):
|
||||
|
||||
Reference in New Issue
Block a user