Files
wehub-resource-sync 3e779be6f3
CI / lint (push) Failing after 13m4s
CI / test (3.11, ubuntu-latest) (push) Failing after 2m4s
CI / test (3.13, ubuntu-latest) (push) Successful in 13m30s
CI / test (3.14, ubuntu-latest) (push) Successful in 17m21s
CI / test (3.12, ubuntu-latest) (push) Successful in 17m55s
CI / discover-apps-ps (push) Successful in 1m56s
CI / test (3.9, ubuntu-latest) (push) Successful in 13m17s
CI / test (3.10, ubuntu-latest) (push) Successful in 26m21s
CI / audit (push) Successful in 13m38s
Deploy site / deploy (push) Has been cancelled
CI / test (3.14, ubuntu-24.04-arm) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:32:37 +08:00

169 lines
5.7 KiB
Python

# SPDX-License-Identifier: MIT
"""Tests for AgentTransport — wraps AgentClient through Transport ABC."""
from __future__ import annotations
from unittest.mock import patch
import pytest
from winpodx.core.agent import (
AgentAuthError,
AgentError,
AgentTimeoutError,
AgentUnavailableError,
)
from winpodx.core.agent import (
ExecResult as AgentExecResult,
)
from winpodx.core.config import Config
from winpodx.core.transport.agent import AgentTransport
from winpodx.core.transport.base import (
ExecResult,
HealthStatus,
TransportAuthError,
TransportError,
TransportTimeoutError,
TransportUnavailable,
)
@pytest.fixture
def cfg() -> Config:
return Config()
@pytest.fixture
def transport(cfg: Config) -> AgentTransport:
return AgentTransport(cfg)
class TestName:
def test_name_is_agent(self):
assert AgentTransport.name == "agent"
class TestHealth:
"""health() never raises on transient state — see Transport ABC rule."""
def test_returns_available_when_client_responds(self, transport):
with (
patch.object(
type(transport._client),
"health",
return_value={"version": "0.2.2-rev1", "ok": True},
),
patch.object(type(transport._client), "auth_ready", return_value=(True, "")),
):
status = transport.health()
assert isinstance(status, HealthStatus)
assert status.available is True
assert status.version == "0.2.2-rev1"
def test_returns_unavailable_when_token_missing(self, transport):
with (
patch.object(
type(transport._client),
"health",
return_value={"version": "0.2.2-rev1", "ok": True},
),
patch.object(
type(transport._client),
"auth_ready",
return_value=(False, "agent token file missing"),
),
):
status = transport.health()
assert status.available is False
assert "token" in (status.detail or "")
def test_returns_unavailable_on_unavailable_error(self, transport):
with patch.object(
type(transport._client),
"health",
side_effect=AgentUnavailableError("connection refused"),
):
status = transport.health()
assert status.available is False
assert "connection refused" in (status.detail or "")
def test_returns_unavailable_on_unexpected_exception(self, transport):
# Defensive: even unexpected exceptions must not propagate.
with patch.object(type(transport._client), "health", side_effect=ValueError("boom")):
status = transport.health()
assert status.available is False
assert "boom" in (status.detail or "")
def test_handles_non_dict_payload_gracefully(self, transport):
# AgentClient.health() returns a dict on success; if it ever
# returns something else, AgentTransport must not crash.
with (
patch.object(type(transport._client), "health", return_value=[]),
patch.object(type(transport._client), "auth_ready", return_value=(True, "")),
):
status = transport.health()
assert status.available is True
assert status.version is None
class TestExec:
def test_happy_path_maps_agent_result_to_exec_result(self, transport):
agent_result = AgentExecResult(rc=0, stdout="hi\n", stderr="")
with patch.object(type(transport._client), "exec", return_value=agent_result):
result = transport.exec("Write-Output hi")
assert isinstance(result, ExecResult)
assert result.rc == 0
assert result.stdout == "hi\n"
assert result.stderr == ""
assert result.ok
def test_auth_error_maps_to_transport_auth_error(self, transport):
with patch.object(
type(transport._client),
"exec",
side_effect=AgentAuthError("/exec auth failed (401)"),
):
with pytest.raises(TransportAuthError):
transport.exec("anything")
def test_timeout_error_maps_to_transport_timeout(self, transport):
with patch.object(
type(transport._client),
"exec",
side_effect=AgentTimeoutError("60s timeout"),
):
with pytest.raises(TransportTimeoutError):
transport.exec("Start-Sleep 120")
def test_unavailable_error_maps_to_transport_unavailable(self, transport):
with patch.object(
type(transport._client),
"exec",
side_effect=AgentUnavailableError("connection refused"),
):
with pytest.raises(TransportUnavailable):
transport.exec("anything")
def test_generic_agent_error_maps_to_transport_error(self, transport):
with patch.object(type(transport._client), "exec", side_effect=AgentError("malformed")):
with pytest.raises(TransportError):
transport.exec("anything")
def test_passes_timeout_through_as_float(self, transport):
captured: dict[str, object] = {}
def fake_exec(self_inner, script, *, timeout):
captured["script"] = script
captured["timeout"] = timeout
return AgentExecResult(rc=0, stdout="", stderr="")
with patch.object(type(transport._client), "exec", fake_exec):
transport.exec("ok", timeout=42)
assert captured["timeout"] == 42.0
class TestStream:
def test_stream_raises_transport_unavailable(self, transport):
with pytest.raises(TransportUnavailable):
transport.stream("Get-Process", lambda _line: None)