This commit is contained in:
@@ -25,6 +25,7 @@ def _assignment(**overrides) -> AssignmentModel:
|
||||
|
||||
def test_goal_assignment_executes_through_task_runner() -> None:
|
||||
received: list[Task] = []
|
||||
created: list[tuple[str, str | None, int | None]] = []
|
||||
|
||||
class FakeTaskRunner:
|
||||
def run(self, task: Task) -> Task:
|
||||
@@ -32,10 +33,21 @@ def test_goal_assignment_executes_through_task_runner() -> None:
|
||||
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:
|
||||
created.append((task.id, source_task_id, source_attempt))
|
||||
|
||||
factories = 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]
|
||||
)
|
||||
|
||||
result = AssignmentExecutor(factories).execute(_assignment())
|
||||
@@ -44,6 +56,7 @@ def test_goal_assignment_executes_through_task_runner() -> None:
|
||||
assert received[0].goal == "open settings"
|
||||
assert received[0].device_id == "device-a"
|
||||
assert result.metadata["runtime_task_id"] == received[0].id
|
||||
assert created == [(received[0].id, "cloud-task", 1)]
|
||||
|
||||
|
||||
def test_goal_assignment_preserves_runtime_failure_reason() -> None:
|
||||
|
||||
@@ -191,9 +191,6 @@ def test_dependency_supervisor_defaults_to_disabled() -> None:
|
||||
assert config.appium_supervised is False
|
||||
assert config.appium_host == "127.0.0.1"
|
||||
assert config.appium_port == 4723
|
||||
assert config.runtime_supervised is False
|
||||
assert config.runtime_host == "127.0.0.1"
|
||||
assert config.runtime_port == 8000
|
||||
assert config.dependency_restart_max_attempts == 5
|
||||
|
||||
|
||||
@@ -204,9 +201,6 @@ def test_dependency_supervisor_env_vars_parse_bool_and_numeric_fields() -> None:
|
||||
"HOST_AGENT_APPIUM_SUPERVISED": "1",
|
||||
"HOST_AGENT_APPIUM_HOST": "0.0.0.0",
|
||||
"HOST_AGENT_APPIUM_PORT": "4724",
|
||||
"HOST_AGENT_RUNTIME_SUPERVISED": "true",
|
||||
"HOST_AGENT_RUNTIME_HOST": "localhost",
|
||||
"HOST_AGENT_RUNTIME_PORT": "8001",
|
||||
"HOST_AGENT_DEPENDENCY_RESTART_MAX_ATTEMPTS": "8",
|
||||
}
|
||||
)
|
||||
@@ -215,9 +209,6 @@ def test_dependency_supervisor_env_vars_parse_bool_and_numeric_fields() -> None:
|
||||
assert config.appium_supervised is True
|
||||
assert config.appium_host == "0.0.0.0"
|
||||
assert config.appium_port == 4724
|
||||
assert config.runtime_supervised is True
|
||||
assert config.runtime_host == "localhost"
|
||||
assert config.runtime_port == 8001
|
||||
assert config.dependency_restart_max_attempts == 8
|
||||
|
||||
|
||||
@@ -225,7 +216,6 @@ def test_dependency_supervisor_env_vars_parse_bool_and_numeric_fields() -> None:
|
||||
"overrides",
|
||||
[
|
||||
{"HOST_AGENT_APPIUM_PORT": "0"},
|
||||
{"HOST_AGENT_RUNTIME_PORT": "not-a-number"},
|
||||
{"HOST_AGENT_DEPENDENCY_RESTART_MAX_ATTEMPTS": "-1"},
|
||||
],
|
||||
)
|
||||
@@ -234,3 +224,19 @@ def test_dependency_supervisor_numeric_fields_reject_invalid_values(
|
||||
) -> None:
|
||||
with pytest.raises(HostAgentConfigurationError):
|
||||
load_host_agent_config(overrides)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"setting,value",
|
||||
[
|
||||
("HOST_AGENT_RUNTIME_SUPERVISED", "true"),
|
||||
("HOST_AGENT_RUNTIME_HOST", "127.0.0.1"),
|
||||
("HOST_AGENT_RUNTIME_PORT", "8000"),
|
||||
],
|
||||
)
|
||||
def test_removed_runtime_supervision_settings_are_rejected(
|
||||
setting: str,
|
||||
value: str,
|
||||
) -> None:
|
||||
with pytest.raises(HostAgentConfigurationError, match="standalone Runtime service"):
|
||||
load_host_agent_config({setting: value})
|
||||
|
||||
@@ -16,8 +16,6 @@ from host_agent.dependency_supervisor import (
|
||||
_SupervisorKnobs,
|
||||
appium_argv_factory,
|
||||
probe_appium,
|
||||
probe_runtime,
|
||||
runtime_argv_factory,
|
||||
)
|
||||
from host_agent.config import HostAgentConfig
|
||||
|
||||
@@ -175,7 +173,6 @@ def test_probe_returns_no_listener_when_port_is_closed(monkeypatch) -> None:
|
||||
_raise_connection_refused,
|
||||
)
|
||||
assert probe_appium("127.0.0.1", 4723) is ProbeResult.NO_LISTENER
|
||||
assert probe_runtime("127.0.0.1", 8000) is ProbeResult.NO_LISTENER
|
||||
|
||||
|
||||
def test_probe_returns_healthy_on_appium_status_endpoint(monkeypatch) -> None:
|
||||
@@ -190,18 +187,6 @@ def test_probe_returns_healthy_on_appium_status_endpoint(monkeypatch) -> None:
|
||||
assert probe_appium("127.0.0.1", 4723) is ProbeResult.HEALTHY
|
||||
|
||||
|
||||
def test_probe_returns_healthy_on_runtime_devices_endpoint(monkeypatch) -> None:
|
||||
monkeypatch.setattr(
|
||||
"host_agent.dependency_supervisor.socket.create_connection",
|
||||
_ok_connection,
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"host_agent.dependency_supervisor.httpx.get",
|
||||
lambda url, timeout=2.0: httpx.Response(200, json=[]),
|
||||
)
|
||||
assert probe_runtime("127.0.0.1", 8000) is ProbeResult.HEALTHY
|
||||
|
||||
|
||||
def test_probe_returns_unhealthy_when_listener_returns_non_200(monkeypatch) -> None:
|
||||
monkeypatch.setattr(
|
||||
"host_agent.dependency_supervisor.socket.create_connection",
|
||||
@@ -223,7 +208,7 @@ def test_probe_returns_unhealthy_when_listener_returns_non_json(monkeypatch) ->
|
||||
"host_agent.dependency_supervisor.httpx.get",
|
||||
lambda url, timeout=2.0: httpx.Response(200, text="not json"),
|
||||
)
|
||||
assert probe_runtime("127.0.0.1", 8000) is ProbeResult.UNHEALTHY_LISTENER
|
||||
assert probe_appium("127.0.0.1", 4723) is ProbeResult.UNHEALTHY_LISTENER
|
||||
|
||||
|
||||
def test_probe_returns_unhealthy_on_http_transport_error(monkeypatch) -> None:
|
||||
@@ -335,38 +320,6 @@ def test_spawn_uses_appium_argv_factory() -> None:
|
||||
asyncio.run(scenario())
|
||||
|
||||
|
||||
def test_spawn_uses_runtime_argv_factory() -> None:
|
||||
async def scenario() -> None:
|
||||
captured: list[list[str]] = []
|
||||
|
||||
def recording_popen(argv, **kwargs):
|
||||
captured.append(list(argv))
|
||||
return _FakePopen(argv)
|
||||
|
||||
dep, _ = _dep(
|
||||
name="runtime",
|
||||
port=8000,
|
||||
probe_responses=(ProbeResult.NO_LISTENER, ProbeResult.HEALTHY),
|
||||
argv_factory=runtime_argv_factory,
|
||||
)
|
||||
sup, _ = _build_supervisor([dep], popen_factory=recording_popen)
|
||||
await sup.start()
|
||||
|
||||
assert captured == [
|
||||
[
|
||||
"uvicorn",
|
||||
"api.rest:create_app",
|
||||
"--factory",
|
||||
"--host",
|
||||
"127.0.0.1",
|
||||
"--port",
|
||||
"8000",
|
||||
]
|
||||
]
|
||||
|
||||
asyncio.run(scenario())
|
||||
|
||||
|
||||
def test_readiness_timeout_leaves_process_running_without_restart_loop() -> None:
|
||||
async def scenario() -> None:
|
||||
# NO_LISTENER for initial probe; probe never becomes HEALTHY → startup
|
||||
@@ -529,7 +482,7 @@ def test_from_host_agent_config_builds_empty_supervisor_when_no_dep_selected() -
|
||||
assert sup.dependencies == []
|
||||
|
||||
|
||||
def test_from_host_agent_config_includes_appium_and_runtime_when_selected() -> None:
|
||||
def test_from_host_agent_config_includes_appium_when_selected() -> None:
|
||||
config = HostAgentConfig(
|
||||
control_plane_url="https://control.example",
|
||||
host_id="host-a",
|
||||
@@ -538,17 +491,12 @@ def test_from_host_agent_config_includes_appium_and_runtime_when_selected() -> N
|
||||
appium_supervised=True,
|
||||
appium_host="10.0.0.5",
|
||||
appium_port=4724,
|
||||
runtime_supervised=True,
|
||||
runtime_host="10.0.0.5",
|
||||
runtime_port=8001,
|
||||
dependency_restart_max_attempts=7,
|
||||
)
|
||||
sup = DependencySupervisor.from_host_agent_config(config)
|
||||
names = [dep.name for dep in sup.dependencies]
|
||||
assert names == ["appium", "runtime"]
|
||||
assert names == ["appium"]
|
||||
appium = sup.dependencies[0]
|
||||
assert appium.host == "10.0.0.5"
|
||||
assert appium.port == 4724
|
||||
runtime = sup.dependencies[1]
|
||||
assert runtime.port == 8001
|
||||
assert sup._max_attempts == 7
|
||||
|
||||
Reference in New Issue
Block a user