Add internal KVM switch dashboard and service
This commit is contained in:
@@ -0,0 +1,169 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
import importlib
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
PROJECT_ROOT = Path(__file__).resolve().parents[1]
|
||||
if str(PROJECT_ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(PROJECT_ROOT))
|
||||
|
||||
from app.config import AppConfig, ConfigStore, DEVICE_ROLE_ENV_VAR
|
||||
|
||||
|
||||
def _require_module(module_name: str):
|
||||
try:
|
||||
return importlib.import_module(module_name)
|
||||
except ModuleNotFoundError as exc:
|
||||
pytest.skip(f"Module '{module_name}' is not available yet: {exc}")
|
||||
|
||||
|
||||
def test_config_store_missing_file_and_roundtrip_save(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.setenv(DEVICE_ROLE_ENV_VAR, "tower")
|
||||
config_path = Path(tempfile.mkdtemp()) / "config.json"
|
||||
store = ConfigStore(config_path)
|
||||
|
||||
assert store.get() == AppConfig(device_role="tower", device_port="DP1")
|
||||
assert not config_path.exists()
|
||||
|
||||
saved = store.save(AppConfig(device_role="tower", device_port="HDMI"))
|
||||
assert saved.device_role == "tower"
|
||||
assert saved.device_port == "HDMI"
|
||||
assert config_path.exists()
|
||||
|
||||
reloaded = ConfigStore(config_path).get()
|
||||
assert reloaded.device_role == "tower"
|
||||
assert reloaded.device_port == "HDMI"
|
||||
|
||||
|
||||
def test_api_status_and_settings_endpoints_with_fake_service() -> None:
|
||||
app_main = _require_module("app.main")
|
||||
create_app = getattr(app_main, "create_app", None)
|
||||
if create_app is None:
|
||||
pytest.skip("app.main.create_app is not available yet.")
|
||||
|
||||
class FakeService:
|
||||
def __init__(self) -> None:
|
||||
self.payload = {
|
||||
"config": {"device_role": "tower", "device_port": "DP1"},
|
||||
"samsung_present": False,
|
||||
"samsung_connected_session_active": False,
|
||||
"samsung_session_attempted": False,
|
||||
"samsung_session_successful": False,
|
||||
"samsung_session_attempt_count": 0,
|
||||
"waiting_for_samsung_disconnect": False,
|
||||
"trigger_input_code": None,
|
||||
"alienware_detected": False,
|
||||
"alienware_input_code": None,
|
||||
"resolved_target": None,
|
||||
"ddm_slot": None,
|
||||
"ddm_ready": False,
|
||||
"last_switch_result": "idle",
|
||||
"last_switch_at": None,
|
||||
"errors": [],
|
||||
}
|
||||
|
||||
def start(self) -> None:
|
||||
return None
|
||||
|
||||
def stop(self) -> None:
|
||||
return None
|
||||
|
||||
def get_status(self) -> dict[str, object]:
|
||||
return self.payload
|
||||
|
||||
def save_settings(self, device_role: str, device_port: str) -> dict[str, object]:
|
||||
self.payload["config"] = {
|
||||
"device_role": device_role,
|
||||
"device_port": device_port,
|
||||
}
|
||||
self.payload["last_switch_result"] = "updated"
|
||||
return self.payload
|
||||
|
||||
app = create_app(service=FakeService(), manage_lifecycle=False)
|
||||
with TestClient(app) as client:
|
||||
status_response = client.get("/api/status")
|
||||
assert status_response.status_code == 200
|
||||
assert status_response.json()["config"]["device_role"] == "tower"
|
||||
|
||||
save_response = client.post(
|
||||
"/api/settings",
|
||||
json={"device_role": "laptop", "device_port": "HDMI"},
|
||||
)
|
||||
assert save_response.status_code == 200
|
||||
assert save_response.json()["config"]["device_role"] == "laptop"
|
||||
assert save_response.json()["config"]["device_port"] == "HDMI"
|
||||
|
||||
|
||||
def test_polling_switches_when_trigger_matches_device_role(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.setenv(DEVICE_ROLE_ENV_VAR, "laptop")
|
||||
service_module = _require_module("app.service")
|
||||
hardware_module = _require_module("app.hardware")
|
||||
ddm_module = _require_module("app.ddm")
|
||||
|
||||
KvmSwitcherService = getattr(service_module, "KvmSwitcherService", None)
|
||||
HardwareScan = getattr(hardware_module, "HardwareScan", None)
|
||||
DDMCommandResult = getattr(ddm_module, "DDMCommandResult", None)
|
||||
if KvmSwitcherService is None or HardwareScan is None or DDMCommandResult is None:
|
||||
pytest.skip("Service backend interfaces are not available yet.")
|
||||
|
||||
class FakeMonitorBackend:
|
||||
def __init__(self) -> None:
|
||||
self.call_count = 0
|
||||
|
||||
def scan(self):
|
||||
self.call_count += 1
|
||||
if self.call_count == 1:
|
||||
return HardwareScan(
|
||||
samsung_present=True,
|
||||
trigger_input_code=19,
|
||||
alienware_detected=True,
|
||||
alienware_input_code=15,
|
||||
errors=[],
|
||||
)
|
||||
return HardwareScan(
|
||||
samsung_present=True,
|
||||
trigger_input_code=19,
|
||||
alienware_detected=True,
|
||||
alienware_input_code=17,
|
||||
errors=[],
|
||||
)
|
||||
|
||||
class FakeDDMBackend:
|
||||
def __init__(self) -> None:
|
||||
self.calls: list[tuple[int, str]] = []
|
||||
self.slot = 1
|
||||
|
||||
def is_available(self) -> bool:
|
||||
return True
|
||||
|
||||
def resolve_alienware_slot(self, force: bool = False) -> int | None:
|
||||
return self.slot
|
||||
|
||||
def invalidate_slot(self) -> None:
|
||||
self.slot = None
|
||||
|
||||
def switch_to_port(self, slot: int, port_name: str):
|
||||
self.calls.append((slot, port_name))
|
||||
return DDMCommandResult(True, "ok")
|
||||
|
||||
config_path = Path(tempfile.mkdtemp()) / "config.json"
|
||||
service = KvmSwitcherService(
|
||||
config_store=ConfigStore(config_path),
|
||||
monitor_backend=FakeMonitorBackend(),
|
||||
ddm_backend=FakeDDMBackend(),
|
||||
poll_interval_seconds=0.01,
|
||||
retry_wait_seconds=0.0,
|
||||
)
|
||||
status = service.save_settings(device_role="laptop", device_port="HDMI")
|
||||
assert status["resolved_target"] == "laptop"
|
||||
assert status["config"]["device_role"] == "laptop"
|
||||
assert status["config"]["device_port"] == "HDMI"
|
||||
assert status["samsung_session_attempted"] is True
|
||||
assert status["samsung_session_successful"] is True
|
||||
assert status["last_switch_result"] == "switched"
|
||||
Reference in New Issue
Block a user