import json import threading from types import SimpleNamespace from fastapi import FastAPI from fastapi.testclient import TestClient import pytest from k1link.fleet import board_layout from k1link.fleet.trust import PairingError from k1link.web.fleet_api import router, local_operator def test_layout_api_persists_empty_and_isolates_vehicles(tmp_path): app = FastAPI() app.include_router(router) def find(identifier): if identifier not in ("rover-a", "rover-b"): raise PairingError("Unknown") fleet = SimpleNamespace(root=tmp_path, lock=threading.RLock(), find=find) app.dependency_overrides[local_operator] = lambda: fleet client = TestClient(app) for section in board_layout.SECTIONS: response = client.patch('/api/v1/fleet/rover-a/board-layout', json={"section": section, "open": False}) assert response.status_code == 200 assert client.get('/api/v1/fleet/rover-a/board-layout').json()['open_sections'] == [] assert client.get('/api/v1/fleet/rover-b/board-layout').json()['open_sections'] == list(board_layout.SECTIONS) assert board_layout.read(tmp_path, 'rover-a')['revision'] == 3 assert board_layout.path(tmp_path, 'rover-a').stat().st_mode & 0o777 == 0o600 for body in ({"section":"motor","open":True},{"section":"computer","open":"true"},{"section":"computer","open":True,"extra":0}): assert client.patch('/api/v1/fleet/rover-a/board-layout', json=body).status_code == 422 assert client.get('/api/v1/fleet/missing/board-layout').status_code == 404 def test_corrupt_layout_is_preserved(tmp_path): board_layout.update(tmp_path, "a", "computer", False) target = board_layout.path(tmp_path, "a") raw = json.dumps({"schema":board_layout.SCHEMA,"revision":3,"open_sections":["motor"]}) target.write_text(raw) with pytest.raises(ValueError): board_layout.update(tmp_path, "a", "devices", False) assert target.read_text() == raw