from __future__ import annotations import asyncio import json import logging import threading from collections.abc import AsyncIterator from contextlib import asynccontextmanager from pathlib import Path from typing import Any import pytest from pydantic import SecretStr, ValidationError import k1link.device_plugins.xgrids_k1.facade as facade_module from k1link.device_plugins.xgrids_k1.facade import ( DEFAULT_LIVE_STREAMS, XGRIDS_K1_COMPATIBILITY_PROFILE_ID, AbortAcquisitionRequest, CameraPreviewSelectRequest, CompatibilityAttestationRequest, ConnectionVerifyRequest, ConnectRequest, OpenApplicationControlSessionRequest, OperatorPresenceRequest, PrepareAcquisitionRequest, ShadowApplicationControlArmRequest, StartAcquisitionRequest, StopAcquisitionRequest, XgridsK1CompatibilityService, ) from k1link.device_plugins.xgrids_k1.protocol.application_bootstrap import ( ApplicationControlAuthority, ) ATTESTATION = CompatibilityAttestationRequest( firmware_version="3.0.2", topology="direct-lan", verification="live-device-info", ) QUICK_CONNECT_ATTESTATION = CompatibilityAttestationRequest( firmware_version="3.0.2", topology="device-ap", verification="live-device-info", ) DIRECT_CONNECT_ATTESTATION = CompatibilityAttestationRequest( firmware_version="3.0.2", topology="controller-hotspot", verification="live-device-info", ) PRIMARY_TEST_CREDENTIAL = "x" * 24 SECONDARY_TEST_CREDENTIAL = "y" * 24 PROJECT_NAME = "K1 lifecycle test" PRIVATE_APPLICATION_AUTHORITY = "11111111-2222-3333-4444-555555555555" class FakeApplicationAuthorityLoader: def __init__(self) -> None: self.calls = 0 def load(self) -> ApplicationControlAuthority: self.calls += 1 return ApplicationControlAuthority(openapi_key=PRIVATE_APPLICATION_AUTHORITY) class FakeVisualizationRuntime: def __init__(self) -> None: self.phase = "idle" self.source_mode = "idle" self.source_ready = False self.pcl_frames = 0 self.start_calls: list[tuple[str, Path, float | None, str]] = [] self.stop_calls = 0 self.stop_error: Exception | None = None def snapshot(self) -> dict[str, Any]: return { "phase": self.phase, "message": "test runtime", "source_mode": self.source_mode, "source_ready": self.source_ready, "foxglove_ws_url": None, "foxglove_viewer_url": None, "rerun_grpc_url": None, "viewer_settings": {}, "metrics": { "messages_received": self.pcl_frames, "payload_bytes": 0, "pcl_frames": self.pcl_frames, "pose_frames": 0, "points_published": 0, "last_point_count": 0, "decode_errors": 0, "preview_dropped": 0, "pcl_fps": 0.0, "pose_fps": 0.0, "mqtt_to_publish_ms": None, "mqtt_to_publish_p50_ms": None, "mqtt_to_publish_p95_ms": None, "decode_publish_ms": None, "trajectory_poses": 0, }, } def start_live( self, host: str, out_dir: Path, *, duration_seconds: float | None, project_name: str, ) -> None: self.start_calls.append((host, out_dir, duration_seconds, project_name)) self.phase = "starting_live" self.source_mode = "live" def mark_ready(self) -> None: self.phase = "live" self.source_ready = True def stop(self) -> None: self.stop_calls += 1 if self.stop_error is not None: raise self.stop_error self.phase = "idle" self.source_mode = "idle" self.source_ready = False def close(self) -> None: self.stop() class FakeInteractiveControlSession: def __init__(self) -> None: self.state = "workspace-ready" self.start_projects: list[str] = [] self.stop_calls = 0 def snapshot(self) -> dict[str, object]: return { "state": self.state, "can_confirm_standby": False, } def open_project_prompt(self) -> dict[str, object]: assert self.state == "workspace-ready" self.state = "project-ready" return self.snapshot() def request_start(self, *, project_name: str, confirmation: object) -> dict[str, object]: assert confirmation is not None assert self.state == "project-ready" self.start_projects.append(project_name) self.state = "scanning" return self.snapshot() def request_stop(self, *, confirmation: object) -> dict[str, object]: assert confirmation is not None assert self.state == "scanning" self.stop_calls += 1 self.state = "awaiting-standby-confirmation" return self.snapshot() def close_prestart(self) -> dict[str, object]: self.state = "closed" return self.snapshot() def close(self) -> None: self.state = "closed" PHYSICAL_ACCEPTANCE = OperatorPresenceRequest( operator_present=True, owner_controlled_device=True, lixelgo_closed=True, battery_storage_confirmed=True, expected_physical_state_confirmed=True, ) def test_control_session_transport_metadata_does_not_leak_into_confirmation() -> None: request = OpenApplicationControlSessionRequest( operator_present=True, owner_controlled_device=True, lixelgo_closed=True, battery_storage_confirmed=True, expected_physical_state_confirmed=True, timezone_name="Europe/Moscow", ) assert request.confirmation() == PHYSICAL_ACCEPTANCE.confirmation() def service_with_fake_runtime( tmp_path: Path, ) -> tuple[XgridsK1CompatibilityService, FakeVisualizationRuntime]: service = XgridsK1CompatibilityService(tmp_path) runtime = FakeVisualizationRuntime() service.runtime = runtime # type: ignore[assignment] return service, runtime def _wifi_status_read(ipv4: str | None) -> dict[str, Any]: return { "schema_version": 1, "profile_id": "xgrids-k1-fw3-wifi-v1", "observed_at_utc": "2026-07-20T12:00:00Z", "adapter": "CoreBluetooth", "bleak_version": "test", "device_macos_uuid": "test-ble-transport", "device_name": "XGR-K1", "service_uuid": "00007f00-0000-1000-8000-00805f9b34fb", "status_characteristic_uuid": "00007f02-0000-1000-8000-00805f9b34fb", "operation": "single_reviewed_wifi_status_read", "write_performed": False, "status": { "value_length": 54, "mode": "WIFI_CLIENT", "ipv4": ipv4, "status_code": 1, "reserved": 0, "trailer_hex": "", }, } def _set_scanned_devices( service: XgridsK1CompatibilityService, devices: list[dict[str, Any]], ) -> None: service._devices = devices # noqa: SLF001 observed_monotonic = facade_module.time.monotonic() service._ble_device_last_seen_monotonic = { # noqa: SLF001 str(item["device_id"]): observed_monotonic for item in devices } def _set_scanned_k1( service: XgridsK1CompatibilityService, *, device_id: str = "test-ble-transport", ) -> None: _set_scanned_devices(service, [ { "device_id": device_id, "name": "XGR-K1", "rssi": -44, "address": None, "connectable": True, "likely_k1": True, } ]) def test_ble_discovery_lease_expiry_hides_and_rejects_candidate( monkeypatch: pytest.MonkeyPatch, tmp_path: Path, ) -> None: service, _ = service_with_fake_runtime(tmp_path) _set_scanned_k1(service) service._ble_device_last_seen_monotonic["test-ble-transport"] = ( # noqa: SLF001 facade_module.time.monotonic() - facade_module.BLE_DISCOVERY_LEASE_TTL_SECONDS - 0.001 ) async def forbidden_status_read(*_: object, **__: object) -> dict[str, Any]: raise AssertionError("an expired candidate must not be rediscovered by an action") async def forbidden_provision(*_: object, **__: object) -> dict[str, Any]: raise AssertionError("an expired candidate must not reach the Wi-Fi write boundary") monkeypatch.setattr(facade_module, "read_wifi_status_once", forbidden_status_read) monkeypatch.setattr(facade_module, "provision_wifi_once", forbidden_provision) stale_state = service.state() assert stale_state["devices"] == [] assert "устарели" in stale_state["message"] with pytest.raises(ValueError, match="найдите и выберите"): service.verify_connection( ConnectionVerifyRequest( device_id="test-ble-transport", compatibility_attestation=ATTESTATION, ) ) with pytest.raises(ValueError, match="найдите и выберите"): asyncio.run( service.connect( ConnectRequest( device_id="test-ble-transport", ssid="lab-network", password=SecretStr(PRIMARY_TEST_CREDENTIAL), compatibility_attestation=ATTESTATION, ) ) ) def test_new_ble_scan_generation_invalidates_old_candidates_before_io_and_on_failure( monkeypatch: pytest.MonkeyPatch, tmp_path: Path, ) -> None: service, _ = service_with_fake_runtime(tmp_path) _set_scanned_k1(service, device_id="old-scan-device") async def scenario() -> None: entered = asyncio.Event() release = asyncio.Event() async def failing_scan(_duration_seconds: float) -> dict[str, Any]: entered.set() await release.wait() raise RuntimeError("synthetic BLE scan failure") monkeypatch.setattr(facade_module, "scan", failing_scan) scan_task = asyncio.create_task(service.scan_ble(6.0)) await asyncio.wait_for(entered.wait(), timeout=1.0) assert service.state()["devices"] == [] release.set() with pytest.raises(RuntimeError, match="synthetic BLE scan failure"): await asyncio.wait_for(scan_task, timeout=1.0) asyncio.run(scenario()) assert service.state()["devices"] == [] assert service._devices == [] # noqa: SLF001 assert service._ble_device_last_seen_monotonic == {} # noqa: SLF001 def test_older_ble_scan_cannot_replace_a_newer_generation( monkeypatch: pytest.MonkeyPatch, tmp_path: Path, ) -> None: service, _ = service_with_fake_runtime(tmp_path) first_entered = asyncio.Event() release_first = asyncio.Event() call_count = 0 def scan_result(device_id: str) -> dict[str, Any]: return { "devices": [ { "macos_uuid": device_id, "name": "XGR-K1", "local_name": "XGR-K1", "rssi": -44, "k1_name_candidate": True, } ] } async def overlapping_scan(_duration_seconds: float) -> dict[str, Any]: nonlocal call_count call_count += 1 if call_count == 1: first_entered.set() await release_first.wait() return scan_result("older-generation") return scan_result("newer-generation") async def scenario() -> dict[str, Any]: monkeypatch.setattr(facade_module, "scan", overlapping_scan) older_task = asyncio.create_task(service.scan_ble(6.0)) await asyncio.wait_for(first_entered.wait(), timeout=1.0) newer_state = await service.scan_ble(6.0) release_first.set() await asyncio.wait_for(older_task, timeout=1.0) return newer_state newer_state = asyncio.run(scenario()) assert [item["device_id"] for item in newer_state["devices"]] == ["newer-generation"] assert [item["device_id"] for item in service.state()["devices"]] == [ "newer-generation" ] def test_connect_stops_before_ble_write_when_a_new_scan_replaces_its_generation( monkeypatch: pytest.MonkeyPatch, tmp_path: Path, ) -> None: service, _ = service_with_fake_runtime(tmp_path) _set_scanned_k1(service) preflight_entered = threading.Event() release_preflight = threading.Event() def delayed_preflight(*_: object, **__: object) -> dict[str, Any]: preflight_entered.set() if not release_preflight.wait(timeout=1.0): raise RuntimeError("test preflight release timed out") return { "schema_version": 1, "adapter": "macOS Keychain", "available": True, "profile_enrolled": False, "credential_source": "exact-firmware-profile", } async def replacement_scan(_duration_seconds: float) -> dict[str, Any]: return { "devices": [ { "macos_uuid": "replacement-device", "name": "XGR-NEW", "local_name": "XGR-NEW", "rssi": -40, "k1_name_candidate": True, } ] } @asynccontextmanager async def forbidden_activation( *_args: object, **_kwargs: object, ) -> AsyncIterator[dict[str, Any]]: raise AssertionError("changed discovery generation must stop before BLE write") yield {} monkeypatch.setattr( facade_module, "ensure_wifi_profile_from_credential_source", delayed_preflight, ) monkeypatch.setattr(facade_module, "scan", replacement_scan) monkeypatch.setattr(facade_module, "device_ap_activation_session", forbidden_activation) async def scenario() -> None: connect_task = asyncio.create_task( service.connect( ConnectRequest( device_id="test-ble-transport", connection_mode="quick-connect", compatibility_attestation=QUICK_CONNECT_ATTESTATION, ) ) ) entered = await asyncio.wait_for( asyncio.to_thread(preflight_entered.wait, 1.0), timeout=1.5, ) assert entered is True replacement_state = await service.scan_ble(6.0) assert [item["device_id"] for item in replacement_state["devices"]] == [ "replacement-device" ] release_preflight.set() with pytest.raises(ValueError, match="изменились или устарели"): await asyncio.wait_for(connect_task, timeout=1.0) try: asyncio.run(scenario()) finally: release_preflight.set() def test_verify_connection_adopts_scanned_existing_lan_without_device_write( monkeypatch: pytest.MonkeyPatch, tmp_path: Path, ) -> None: service, _ = service_with_fake_runtime(tmp_path) _set_scanned_k1(service) status_reads: list[tuple[str, float, bool]] = [] async def fake_status_read( device_id: str, *, timeout_seconds: float, rediscover: bool, ) -> dict[str, Any]: status_reads.append((device_id, timeout_seconds, rediscover)) return _wifi_status_read("10.255.254.77") async def forbidden_provision(*_: object, **__: object) -> dict[str, Any]: raise AssertionError("connection.verify must never call Wi-Fi provisioning") monkeypatch.setattr(facade_module, "read_wifi_status_once", fake_status_read) monkeypatch.setattr(facade_module, "provision_wifi_once", forbidden_provision) monkeypatch.setattr(facade_module, "_target_is_local_ipv4", lambda _target: False) monkeypatch.setattr(facade_module, "_host_route_class", lambda _target: "direct-or-routed") monkeypatch.setattr(facade_module, "_control_endpoint_reachable", lambda _target: True) state = service.verify_connection( ConnectionVerifyRequest( device_id="test-ble-transport", compatibility_attestation=ATTESTATION, ) ) assert status_reads == [("test-ble-transport", 20.0, True)] assert state["selected_device_id"] == "test-ble-transport" assert state["k1_ip"] == "10.255.254.77" assert state["connection_mode"] == "bridge" assert state["device_ref"]["transport_alias"] == "test-ble-transport" assert state["device_session"]["connectivity"] == "connected" assert state["compatibility"]["attestation"]["topology"] == "direct-lan" assert state["connection_verification"] == { "status": "adopted", "lease_state": "reachable", "lease_generation": 1, "endpoint_validation": "ble-wifi-status-read+mqtt-tcp-connect", "network_reachability": "reachable", "host_route_class": "direct-or-routed", "address_source": "ble-wifi-status-read", "connection_origin": "external-existing-network", "admission_source": "connection.verify", "address_changed": True, "previous_address_present": False, "write_performed": False, "observed_at": "2026-07-20T12:00:00Z", } def test_verify_connection_adoption_requires_current_scan_candidate( monkeypatch: pytest.MonkeyPatch, tmp_path: Path, ) -> None: service, _ = service_with_fake_runtime(tmp_path) async def forbidden_status_read(*_: object, **__: object) -> dict[str, Any]: raise AssertionError("an unscanned device must not be probed") async def forbidden_provision(*_: object, **__: object) -> dict[str, Any]: raise AssertionError("connection.verify must never call Wi-Fi provisioning") monkeypatch.setattr(facade_module, "read_wifi_status_once", forbidden_status_read) monkeypatch.setattr(facade_module, "provision_wifi_once", forbidden_provision) with pytest.raises(ValueError, match="найдите и выберите"): service.verify_connection( ConnectionVerifyRequest( device_id="not-in-current-scan", compatibility_attestation=ATTESTATION, ) ) @pytest.mark.parametrize( ("ipv4", "is_local", "route_class", "endpoint_reachable", "message"), [ (None, False, "direct-or-routed", True, "не сообщил актуальный DHCP-адрес"), ("192.168.56.1", False, "device-ap", True, "не сообщил актуальный DHCP-адрес"), ("10.255.254.77", True, "direct-or-routed", True, "этому компьютеру"), ("10.255.254.77", False, "tunnel", True, "прямой локальный маршрут"), ("10.255.254.77", False, "default-route", True, "прямой локальный маршрут"), ("10.255.254.77", False, "direct-or-routed", False, "1883 недоступен"), ], ) def test_verify_connection_adoption_fails_closed_before_establishing_lease( monkeypatch: pytest.MonkeyPatch, tmp_path: Path, ipv4: str | None, is_local: bool, route_class: str, endpoint_reachable: bool, message: str, ) -> None: service, _ = service_with_fake_runtime(tmp_path) _set_scanned_k1(service) async def fake_status_read(*_: object, **__: object) -> dict[str, Any]: return _wifi_status_read(ipv4) async def forbidden_provision(*_: object, **__: object) -> dict[str, Any]: raise AssertionError("connection.verify must never call Wi-Fi provisioning") monkeypatch.setattr(facade_module, "read_wifi_status_once", fake_status_read) monkeypatch.setattr(facade_module, "provision_wifi_once", forbidden_provision) monkeypatch.setattr(facade_module, "_target_is_local_ipv4", lambda _target: is_local) monkeypatch.setattr(facade_module, "_host_route_class", lambda _target: route_class) monkeypatch.setattr( facade_module, "_control_endpoint_reachable", lambda _target: endpoint_reachable, ) with pytest.raises(RuntimeError, match=message): service.verify_connection( ConnectionVerifyRequest( device_id="test-ble-transport", compatibility_attestation=ATTESTATION, ) ) state = service.state() assert state["selected_device_id"] is None assert state["k1_ip"] is None assert state["connection_mode"] is None assert state["device_session"] is None assert state["connection_verification"].get("write_performed") is not True def test_connection_verify_request_requires_exact_bridge_attestation() -> None: with pytest.raises(ValidationError, match="provided together"): ConnectionVerifyRequest(device_id="test-ble-transport") with pytest.raises(ValidationError, match="topology=direct-lan"): ConnectionVerifyRequest( device_id="test-ble-transport", compatibility_attestation=QUICK_CONNECT_ATTESTATION, ) def test_verify_connection_refreshes_dynamic_dhcp_address_and_rotates_session( monkeypatch: pytest.MonkeyPatch, tmp_path: Path, ) -> None: service, _ = service_with_fake_runtime(tmp_path) service._selected_device_id = "test-ble-transport" # noqa: SLF001 service._connection_mode = "bridge" # noqa: SLF001 service._k1_ip = "10.255.254.54" # noqa: SLF001 service._device_id = "known-k1" # noqa: SLF001 service._device_session_id = "old-device-session" # noqa: SLF001 service._device_session_opened_at = "2026-07-20T10:00:00Z" # noqa: SLF001 service._device_calibration = {"status": "available"} # noqa: SLF001 async def fake_status_read(*_: object, **__: object) -> dict[str, Any]: return _wifi_status_read("10.255.254.77") monkeypatch.setattr(facade_module, "read_wifi_status_once", fake_status_read) monkeypatch.setattr(facade_module, "_target_is_local_ipv4", lambda _target: False) state = service.verify_connection() assert state["k1_ip"] == "10.255.254.77" assert state["device_session"]["device_session_id"] != "old-device-session" assert state["connection_verification"] == { "status": "live-address-observed", "lease_state": "configured", "lease_generation": 1, "endpoint_validation": "ble-wifi-status-read", "network_reachability": "not-probed", "address_changed": True, "previous_address_present": True, "write_performed": False, "observed_at": "2026-07-20T12:00:00Z", } assert state["device_calibration"]["status"] == "unavailable" def test_implicit_acquisition_target_uses_current_ble_dhcp_address( monkeypatch: pytest.MonkeyPatch, tmp_path: Path, ) -> None: service, _ = service_with_fake_runtime(tmp_path) service._selected_device_id = "test-ble-transport" # noqa: SLF001 service._connection_mode = "bridge" # noqa: SLF001 service._k1_ip = "10.255.254.54" # noqa: SLF001 async def fake_status_read(*_: object, **__: object) -> dict[str, Any]: return _wifi_status_read("10.255.254.77") monkeypatch.setattr(facade_module, "read_wifi_status_once", fake_status_read) monkeypatch.setattr(facade_module, "_target_is_local_ipv4", lambda _target: False) state = service.prepare_acquisition( PrepareAcquisitionRequest( project_name=PROJECT_NAME, compatibility_attestation=ATTESTATION, ) ) assert state["k1_ip"] == "10.255.254.77" assert state["acquisition"]["target_host"] == "10.255.254.77" def test_control_session_reuses_reachable_process_owned_connection_lease( monkeypatch: pytest.MonkeyPatch, tmp_path: Path, ) -> None: service, _ = service_with_fake_runtime(tmp_path) service._selected_device_id = "test-ble-transport" # noqa: SLF001 service._connection_mode = "bridge" # noqa: SLF001 service._k1_ip = "10.255.254.54" # noqa: SLF001 service._device_id = "known-k1" # noqa: SLF001 service._device_session_id = "known-session" # noqa: SLF001 service._compatibility_attestation = {"profile": "exact"} # noqa: SLF001 opened_hosts: list[str] = [] class FakeOpenControlSession: def snapshot(self) -> dict[str, object]: return {"state": "idle", "can_confirm_standby": False} def open(self, *, host: str, **_: object) -> dict[str, object]: opened_hosts.append(host) return self.snapshot() async def forbidden_status_read(*_: object, **__: object) -> dict[str, Any]: raise AssertionError("reachable connection lease must not reopen BLE") service._application_control_session = FakeOpenControlSession() # type: ignore[assignment] # noqa: SLF001 monkeypatch.setattr(facade_module, "read_wifi_status_once", forbidden_status_read) monkeypatch.setattr(facade_module, "_control_endpoint_reachable", lambda _target: True) state = service.open_application_control_session( OpenApplicationControlSessionRequest( operator_present=True, owner_controlled_device=True, lixelgo_closed=True, battery_storage_confirmed=True, expected_physical_state_confirmed=True, timezone_name="Europe/Moscow", ) ) assert opened_hosts == ["10.255.254.54"] assert state["k1_ip"] == "10.255.254.54" assert state["connection_verification"]["lease_state"] == "reachable" assert state["connection_verification"]["network_reachability"] == "reachable" open_operation = next( operation for operation in state["operations"] if operation["action"] == "application-control.session.open" ) assert open_operation["status"] == "succeeded" assert open_operation["result"] == { "lease_generation": 0, "connection_lease_reused": True, "recovery_performed": False, "address_changed": False, "device_write_performed": False, } def test_reachable_connection_lease_supports_repeated_independent_control_sessions( monkeypatch: pytest.MonkeyPatch, tmp_path: Path, ) -> None: service, _ = service_with_fake_runtime(tmp_path) service._selected_device_id = "test-ble-transport" # noqa: SLF001 service._connection_mode = "bridge" # noqa: SLF001 service._k1_ip = "10.255.254.54" # noqa: SLF001 service._device_id = "known-k1" # noqa: SLF001 service._device_session_id = "known-session" # noqa: SLF001 service._connection_lease_generation = 7 # noqa: SLF001 service._compatibility_attestation = {"profile": "exact"} # noqa: SLF001 opened_hosts: list[str] = [] class FakeCompletedControlSession: def snapshot(self) -> dict[str, object]: return { "state": "completed", "can_open": True, "can_confirm_standby": False, } def open(self, *, host: str, **_: object) -> dict[str, object]: opened_hosts.append(host) return self.snapshot() async def forbidden_status_read(*_: object, **__: object) -> dict[str, Any]: raise AssertionError("repeated scans must not repeat BLE or Wi-Fi setup") service._application_control_session = FakeCompletedControlSession() # type: ignore[assignment] # noqa: SLF001 monkeypatch.setattr(facade_module, "read_wifi_status_once", forbidden_status_read) monkeypatch.setattr(facade_module, "_control_endpoint_reachable", lambda _target: True) request = OpenApplicationControlSessionRequest( operator_present=True, owner_controlled_device=True, lixelgo_closed=True, battery_storage_confirmed=True, expected_physical_state_confirmed=True, timezone_name="Europe/Moscow", ) service.open_application_control_session(request) state = service.open_application_control_session(request) assert opened_hosts == ["10.255.254.54", "10.255.254.54"] assert state["device_session"]["device_session_id"] == "known-session" assert state["connection_verification"]["lease_generation"] == 7 open_operations = [ operation for operation in state["operations"] if operation["action"] == "application-control.session.open" ] assert len(open_operations) == 2 assert {operation["status"] for operation in open_operations} == {"succeeded"} assert all( operation["result"]["connection_lease_reused"] is True for operation in open_operations ) def test_control_session_recovers_changed_bridge_address_without_wifi_write( monkeypatch: pytest.MonkeyPatch, tmp_path: Path, ) -> None: service, _ = service_with_fake_runtime(tmp_path) service._selected_device_id = "test-ble-transport" # noqa: SLF001 service._connection_mode = "bridge" # noqa: SLF001 service._k1_ip = "10.255.254.54" # noqa: SLF001 service._device_id = "known-k1" # noqa: SLF001 service._device_session_id = "old-session" # noqa: SLF001 service._compatibility_attestation = {"profile": "exact"} # noqa: SLF001 opened_hosts: list[str] = [] status_calls: list[dict[str, object]] = [] class FakeOpenControlSession: def snapshot(self) -> dict[str, object]: return {"state": "idle", "can_confirm_standby": False} def open(self, *, host: str, **_: object) -> dict[str, object]: opened_hosts.append(host) return self.snapshot() async def fake_status_read(*_: object, **kwargs: object) -> dict[str, Any]: status_calls.append(kwargs) return _wifi_status_read("10.255.254.77") service._application_control_session = FakeOpenControlSession() # type: ignore[assignment] # noqa: SLF001 monkeypatch.setattr(facade_module, "read_wifi_status_once", fake_status_read) monkeypatch.setattr(facade_module, "_target_is_local_ipv4", lambda _target: False) monkeypatch.setattr(facade_module, "_host_route_class", lambda _target: "direct-or-routed") monkeypatch.setattr( facade_module, "_control_endpoint_reachable", lambda target: target == "10.255.254.77", ) state = service.open_application_control_session( OpenApplicationControlSessionRequest( operator_present=True, owner_controlled_device=True, lixelgo_closed=True, battery_storage_confirmed=True, expected_physical_state_confirmed=True, timezone_name="Europe/Moscow", ) ) assert status_calls == [{"timeout_seconds": 20.0, "rediscover": True}] assert opened_hosts == ["10.255.254.77"] assert state["k1_ip"] == "10.255.254.77" assert state["device_session"]["device_session_id"] != "old-session" assert state["connection_verification"]["status"] == "recovered" assert state["connection_verification"]["write_performed"] is False open_operation = next( operation for operation in state["operations"] if operation["action"] == "application-control.session.open" ) assert open_operation["result"]["recovery_performed"] is True assert open_operation["result"]["address_changed"] is True assert open_operation["result"]["device_write_performed"] is False def test_control_session_prestart_failure_is_journaled_and_marks_lease_offline( monkeypatch: pytest.MonkeyPatch, tmp_path: Path, ) -> None: service, _ = service_with_fake_runtime(tmp_path) service._selected_device_id = "test-ble-transport" # noqa: SLF001 service._connection_mode = "bridge" # noqa: SLF001 service._k1_ip = "10.255.254.54" # noqa: SLF001 service._device_id = "known-k1" # noqa: SLF001 service._device_session_id = "known-session" # noqa: SLF001 service._compatibility_attestation = {"profile": "exact"} # noqa: SLF001 async def failed_status_read(*_: object, **__: object) -> dict[str, Any]: raise TimeoutError("synthetic BLE recovery timeout") monkeypatch.setattr(facade_module, "read_wifi_status_once", failed_status_read) monkeypatch.setattr(facade_module, "_control_endpoint_reachable", lambda _target: False) monkeypatch.setattr(facade_module, "_host_route_class", lambda _target: "direct-or-routed") with pytest.raises( facade_module.ConnectionLeaseUnavailable, match="повторное чтение состояния", ): service.open_application_control_session( OpenApplicationControlSessionRequest( operator_present=True, owner_controlled_device=True, lixelgo_closed=True, battery_storage_confirmed=True, expected_physical_state_confirmed=True, timezone_name="Europe/Moscow", ) ) state = service.state() assert state["application_control_session"]["state"] == "idle" assert state["device_session"]["connectivity"] == "offline" assert state["connection_verification"]["lease_state"] == "disconnected" open_operation = next( operation for operation in state["operations"] if operation["action"] == "application-control.session.open" ) assert open_operation["status"] == "failed" assert open_operation["error"] == { "category": "connection", "code": "connection_lease_ble_recovery_failed", "retryable": False, "safe_to_retry": True, "side_effect_status": "none", } def test_bridge_route_mismatch_stops_before_ble_recovery_and_vendor_commands( monkeypatch: pytest.MonkeyPatch, tmp_path: Path, ) -> None: service, _ = service_with_fake_runtime(tmp_path) service._selected_device_id = "test-ble-transport" # noqa: SLF001 service._connection_mode = "bridge" # noqa: SLF001 service._k1_ip = "192.168.68.50" # noqa: SLF001 service._device_id = "known-k1" # noqa: SLF001 service._device_session_id = "known-session" # noqa: SLF001 service._compatibility_attestation = {"profile": "exact"} # noqa: SLF001 async def forbidden_status_read(*_: object, **__: object) -> dict[str, Any]: raise AssertionError("host route mismatch must stop before BLE recovery") monkeypatch.setattr(facade_module, "_control_endpoint_reachable", lambda _target: False) monkeypatch.setattr(facade_module, "_host_route_class", lambda _target: "tunnel") monkeypatch.setattr(facade_module, "read_wifi_status_once", forbidden_status_read) with pytest.raises( facade_module.ConnectionLeaseUnavailable, match="другой локальной сети", ): service.open_application_control_session( OpenApplicationControlSessionRequest( operator_present=True, owner_controlled_device=True, lixelgo_closed=True, battery_storage_confirmed=True, expected_physical_state_confirmed=True, timezone_name="Europe/Moscow", ) ) state = service.state() assert state["connection_verification"]["reason_code"] == ( "connection_lease_host_route_mismatch" ) assert state["connection_verification"]["endpoint_validation"] == "host-route" assert state["connection_verification"]["host_route_class"] == "tunnel" assert state["application_control_session"]["state"] == "idle" def test_prepare_creates_provisional_device_session_and_profiled_acquisition( tmp_path: Path, ) -> None: service, runtime = service_with_fake_runtime(tmp_path) state = service.prepare_acquisition( PrepareAcquisitionRequest( project_name=PROJECT_NAME, host="192.168.1.20", duration_seconds=60, compatibility_attestation=ATTESTATION, ) ) assert runtime.start_calls == [] assert state["device_ref"]["identity_stability"] == "provisional" assert state["device_ref"]["device_id"] != state["device_session"]["device_session_id"] assert state["acquisition"]["state"] == "prepared" assert state["acquisition"]["project_name"] == PROJECT_NAME assert state["acquisition"]["mount_type"] == "handheld" assert state["acquisition"]["gnss_mode"] == "none" assert state["acquisition"]["compatibility_profile_id"] == (XGRIDS_K1_COMPATIBILITY_PROFILE_ID) assert state["compatibility"]["vendor_writes_enabled"] is False def test_project_name_is_normalized_and_control_characters_are_rejected() -> None: request = PrepareAcquisitionRequest( project_name=" K1 Lab ", host="192.168.1.20", compatibility_attestation=ATTESTATION, ) assert request.project_name == "K1 Lab" for invalid in (" ", "line\nbreak", "\ud800", "x" * 97): with pytest.raises(ValidationError): PrepareAcquisitionRequest( project_name=invalid, host="192.168.1.20", compatibility_attestation=ATTESTATION, ) def test_acquisition_is_unbounded_by_default_and_accepts_ten_hour_hint() -> None: unbounded = PrepareAcquisitionRequest( project_name=PROJECT_NAME, host="192.168.1.20", compatibility_attestation=ATTESTATION, ) ten_hours = PrepareAcquisitionRequest( project_name=PROJECT_NAME, host="192.168.1.20", duration_seconds=10 * 60 * 60, compatibility_attestation=ATTESTATION, ) assert unbounded.duration_seconds is None assert ten_hours.duration_seconds == 36_000 def test_connection_modes_require_their_exact_topology_attestation() -> None: assert ( ConnectRequest( device_id="synthetic-device", connection_mode="quick-connect", compatibility_attestation=QUICK_CONNECT_ATTESTATION, ).connection_mode == "quick-connect" ) assert ( ConnectRequest( device_id="synthetic-device", ssid="synthetic-network", password=SecretStr(PRIMARY_TEST_CREDENTIAL), connection_mode="direct-connect", compatibility_attestation=DIRECT_CONNECT_ATTESTATION, ).connection_mode == "direct-connect" ) with pytest.raises(ValidationError): ConnectRequest( device_id="synthetic-device", connection_mode="quick-connect", compatibility_attestation=ATTESTATION, ) with pytest.raises(ValidationError, match="host Wi-Fi profile"): ConnectRequest( device_id="synthetic-device", ssid="synthetic-network", password=SecretStr(PRIMARY_TEST_CREDENTIAL), connection_mode="quick-connect", compatibility_attestation=QUICK_CONNECT_ATTESTATION, ) with pytest.raises(ValidationError, match="32 UTF-8 bytes"): ConnectRequest( device_id="synthetic-device", ssid="🛰️" * 9, password=SecretStr(PRIMARY_TEST_CREDENTIAL), compatibility_attestation=ATTESTATION, ) with pytest.raises(ValidationError, match="64 UTF-8 bytes"): ConnectRequest( device_id="synthetic-device", ssid="synthetic-network", password=SecretStr("🔒" * 17), compatibility_attestation=ATTESTATION, ) def test_only_physically_accepted_mount_and_gnss_values_are_admitted() -> None: with pytest.raises(ValidationError): PrepareAcquisitionRequest( project_name=PROJECT_NAME, host="192.168.1.20", mount_type="uav", # type: ignore[arg-type] compatibility_attestation=ATTESTATION, ) with pytest.raises(ValidationError): PrepareAcquisitionRequest( project_name=PROJECT_NAME, host="192.168.1.20", gnss_mode="rtk", # type: ignore[arg-type] compatibility_attestation=ATTESTATION, ) def test_facade_arms_bounded_shadow_lease_without_installing_publish_transport( tmp_path: Path, ) -> None: loader = FakeApplicationAuthorityLoader() service = XgridsK1CompatibilityService( tmp_path, application_authority_loader=loader, ) runtime = FakeVisualizationRuntime() service.runtime = runtime # type: ignore[assignment] service._selected_device_id = "test-ble-transport" # noqa: SLF001 service._k1_ip = "192.168.1.20" # noqa: SLF001 service._compatibility_attestation = { # noqa: SLF001 "firmware_version": "3.0.2", "topology": "direct-lan", "verification": "live-device-info", "basis": "selected-profile-live-device-info-required", "observed_at": "2026-07-18T00:00:00Z", } state = service.arm_application_control_shadow( ShadowApplicationControlArmRequest( operator_confirmed=True, lease_seconds=60.0, timezone_name="Europe/Moscow", ) ) execution = state["application_control_execution"] assert loader.calls == 1 assert execution["state"] == "armed-shadow-only" assert execution["lease"]["authority_cached"] is True assert execution["can_emit_requests"] is False assert execution["live_transport_installed"] is False assert execution["publisher"]["vendor_writes_enabled"] is False assert execution["publisher"]["transport_calls"] == 0 assert PRIVATE_APPLICATION_AUTHORITY not in str(state) disarmed = service.disarm_application_control_shadow() assert disarmed["application_control_execution"]["state"] == "disarmed" assert disarmed["application_control_execution"]["lease"] is None def test_shadow_arm_requires_idle_connected_profile_selected_device_before_keychain_read( tmp_path: Path, ) -> None: loader = FakeApplicationAuthorityLoader() service = XgridsK1CompatibilityService( tmp_path, application_authority_loader=loader, ) service.runtime = FakeVisualizationRuntime() # type: ignore[assignment] with pytest.raises(RuntimeError, match="подключите K1"): service.arm_application_control_shadow( ShadowApplicationControlArmRequest( operator_confirmed=True, timezone_name="Europe/Moscow", ) ) assert loader.calls == 0 def test_shadow_arm_contract_requires_explicit_operator_confirmation() -> None: with pytest.raises(ValidationError): ShadowApplicationControlArmRequest.model_validate({"timezone_name": "Europe/Moscow"}) def test_prepare_acquisition_revokes_existing_shadow_authority_lease(tmp_path: Path) -> None: loader = FakeApplicationAuthorityLoader() service = XgridsK1CompatibilityService( tmp_path, application_authority_loader=loader, ) service.runtime = FakeVisualizationRuntime() # type: ignore[assignment] service._selected_device_id = "test-ble-transport" # noqa: SLF001 service._k1_ip = "192.168.1.20" # noqa: SLF001 service._compatibility_attestation = {"profile": "exact"} # noqa: SLF001 service.arm_application_control_shadow( ShadowApplicationControlArmRequest( operator_confirmed=True, timezone_name="Europe/Moscow", ) ) prepared = service.prepare_acquisition( PrepareAcquisitionRequest( project_name=PROJECT_NAME, host="192.168.1.20", duration_seconds=60, compatibility_attestation=ATTESTATION, ) ) assert prepared["application_control_execution"]["state"] == "disarmed" assert prepared["application_control_execution"]["lease"] is None def test_operator_manual_start_is_confirmed_only_by_real_point_data(tmp_path: Path) -> None: service, runtime = service_with_fake_runtime(tmp_path) prepared = service.prepare_acquisition( PrepareAcquisitionRequest( project_name=PROJECT_NAME, host="192.168.1.20", duration_seconds=60, compatibility_attestation=ATTESTATION, ) ) acquisition_id = prepared["acquisition"]["acquisition_id"] starting = service.start_acquisition(StartAcquisitionRequest(acquisition_id=acquisition_id)) assert starting["acquisition"]["state"] == "starting" assert starting["last_operation"]["status"] == "running" runtime.mark_ready() awaiting = service.state() assert awaiting["acquisition"]["state"] == "awaiting_external_start" assert awaiting["last_operation"]["status"] == "operator_action_required" assert len(runtime.start_calls) == 1 assert runtime.start_calls[0][3] == PROJECT_NAME runtime.pcl_frames = 1 acquiring = service.state() assert acquiring["acquisition"]["state"] == "acquiring" assert acquiring["last_operation"]["status"] == "succeeded" assert acquiring["last_operation"]["result"]["confirmation"] == "point-frame" def test_plugin_commanded_acquisition_keeps_start_and_stop_as_explicit_actions( tmp_path: Path, ) -> None: service, runtime = service_with_fake_runtime(tmp_path) control = FakeInteractiveControlSession() service._application_control_session = control # type: ignore[assignment] # noqa: SLF001 service._k1_ip = "192.168.1.20" # noqa: SLF001 service._compatibility_attestation = {"profile": "exact"} # noqa: SLF001 prepared = service.prepare_acquisition( PrepareAcquisitionRequest( project_name="TEST001", host="192.168.1.20", compatibility_attestation=ATTESTATION, ) ) acquisition_id = prepared["acquisition"]["acquisition_id"] assert prepared["acquisition"]["control_mode"] == "plugin-commanded" assert control.state == "project-ready" assert runtime.start_calls == [] with pytest.raises(ValueError, match="подтверждения присутствия"): service.start_acquisition(StartAcquisitionRequest(acquisition_id=acquisition_id)) assert control.start_projects == [] service.start_acquisition( StartAcquisitionRequest( acquisition_id=acquisition_id, physical_acceptance=PHYSICAL_ACCEPTANCE, ) ) assert control.start_projects == ["TEST001"] runtime.mark_ready() runtime.pcl_frames = 1 acquiring = service.state() assert acquiring["acquisition"]["state"] == "acquiring" stopping = service.stop_acquisition( StopAcquisitionRequest( acquisition_id=acquisition_id, mode="graceful", physical_acceptance=PHYSICAL_ACCEPTANCE, ) ) assert control.stop_calls == 1 assert stopping["acquisition"]["state"] == "awaiting_external_stop" assert stopping["last_operation"]["status"] == "running" control.state = "completed" completed = service.state() assert control.stop_calls == 1 assert completed["acquisition"]["state"] == "completed" assert completed["acquisition"]["result"]["device_state"] == "ready" assert completed["last_operation"]["status"] == "succeeded" assert runtime.stop_calls == 1 assert completed["live_perception_shadow"]["active"] is False def test_next_scan_retires_stale_terminal_live_perception_ingress_before_start( tmp_path: Path, ) -> None: service, runtime = service_with_fake_runtime(tmp_path) service.live_perception_ingress.begin_session("stale-completed-session") prepared = service.prepare_acquisition( PrepareAcquisitionRequest( project_name=PROJECT_NAME, host="192.168.1.20", compatibility_attestation=ATTESTATION, ) ) state = service.start_acquisition( StartAcquisitionRequest(acquisition_id=prepared["acquisition"]["acquisition_id"]) ) assert runtime.start_calls assert state["acquisition"]["state"] == "starting" assert state["live_perception_shadow"]["active"] is True assert state["live_perception_shadow"]["session_id"] != "stale-completed-session" def test_confirmed_scanning_activates_and_records_right_camera( tmp_path: Path, monkeypatch: pytest.MonkeyPatch, ) -> None: service, runtime = service_with_fake_runtime(tmp_path) control = FakeInteractiveControlSession() service._application_control_session = control # type: ignore[assignment] # noqa: SLF001 service._k1_ip = "192.168.1.20" # noqa: SLF001 service._compatibility_attestation = {"profile": "exact"} # noqa: SLF001 prepared = service.prepare_acquisition( PrepareAcquisitionRequest( project_name="TEST001", host="192.168.1.20", compatibility_attestation=ATTESTATION, ) ) acquisition_id = prepared["acquisition"]["acquisition_id"] service.start_acquisition( StartAcquisitionRequest( acquisition_id=acquisition_id, physical_acceptance=PHYSICAL_ACCEPTANCE, ) ) out_dir = service._acquisition_out_dir # noqa: SLF001 assert out_dir is not None out_dir.mkdir(parents=True) events: list[tuple[str, object]] = [] camera_state: dict[str, object] = { "phase": "idle", "active_source_id": None, "recording": {"active": False}, } def select_camera(source_id: str, target: str) -> dict[str, object]: events.append(("select", (source_id, target))) camera_state["phase"] = "selected" camera_state["active_source_id"] = source_id return dict(camera_state) def start_recording(session_dir: Path) -> dict[str, object]: events.append(("record", session_dir)) camera_state["recording"] = {"active": True} camera_state["phase"] = "connecting" return dict(camera_state) monkeypatch.setattr(service.camera_preview, "snapshot", lambda: dict(camera_state)) monkeypatch.setattr(service.camera_preview, "select", select_camera) monkeypatch.setattr(service.camera_preview, "start_recording", start_recording) service._activate_default_acquisition_camera() # noqa: SLF001 assert events == [ ("select", ("sensor.camera.right", "192.168.1.20")), ("record", out_dir), ] assert camera_state["active_source_id"] == "sensor.camera.right" assert camera_state["recording"] == {"active": True} assert runtime.source_mode == "live" def test_device_standby_retires_sources_after_terminal_local_stop_failure( tmp_path: Path, ) -> None: service, runtime = service_with_fake_runtime(tmp_path) control = FakeInteractiveControlSession() service._application_control_session = control # type: ignore[assignment] # noqa: SLF001 service._k1_ip = "192.168.1.20" # noqa: SLF001 service._compatibility_attestation = {"profile": "exact"} # noqa: SLF001 prepared = service.prepare_acquisition( PrepareAcquisitionRequest( project_name="TEST001", host="192.168.1.20", compatibility_attestation=ATTESTATION, ) ) acquisition_id = prepared["acquisition"]["acquisition_id"] service.start_acquisition( StartAcquisitionRequest( acquisition_id=acquisition_id, physical_acceptance=PHYSICAL_ACCEPTANCE, ) ) runtime.mark_ready() runtime.pcl_frames = 1 service.state() stopping = service.stop_acquisition( StopAcquisitionRequest( acquisition_id=acquisition_id, mode="graceful", physical_acceptance=PHYSICAL_ACCEPTANCE, ) ) stop_operation = stopping["last_operation"] with service._lock: # noqa: SLF001 assert service._acquisition is not None # noqa: SLF001 service._acquisition.transition( # noqa: SLF001 "failed", message_code="acquisition.camera_failed", result={"camera_failure_code": "camera-source-ended"}, ) service._operations.transition( # noqa: SLF001 stop_operation["operation_id"], "failed", stage_code="runtime-failed", message_code="acquisition.stop.runtime_failed", error={ "category": "stream", "code": "runtime-failed", "retryable": False, "safe_to_retry": False, "side_effect_status": "unknown", }, ) control.state = "completed" recovered = service.state() assert control.stop_calls == 1 assert control.state == "completed" assert recovered["acquisition"]["state"] == "failed" assert recovered["application_control_session"]["state"] == "completed" assert runtime.stop_calls == 1 def test_second_start_conflict_does_not_tear_down_start_owner(tmp_path: Path) -> None: service, runtime = service_with_fake_runtime(tmp_path) prepared = service.prepare_acquisition( PrepareAcquisitionRequest( project_name=PROJECT_NAME, host="192.168.1.20", compatibility_attestation=ATTESTATION, ) ) acquisition_id = prepared["acquisition"]["acquisition_id"] service.start_acquisition(StartAcquisitionRequest(acquisition_id=acquisition_id)) with pytest.raises(RuntimeError, match="состояния starting"): service.start_acquisition(StartAcquisitionRequest(acquisition_id=acquisition_id)) state = service.state() start_operations = [ item for item in state["operations"] if item["action"] == "acquisition.start" ] assert state["acquisition"]["state"] == "starting" assert runtime.stop_calls == 0 assert [item["status"] for item in start_operations] == ["running", "failed"] assert start_operations[-1]["error"]["category"] == "conflict" def test_state_waits_for_atomic_start_handoff( tmp_path: Path, monkeypatch: pytest.MonkeyPatch, ) -> None: service, runtime = service_with_fake_runtime(tmp_path) prepared = service.prepare_acquisition( PrepareAcquisitionRequest( project_name=PROJECT_NAME, host="192.168.1.20", compatibility_attestation=ATTESTATION, ) ) acquisition_id = prepared["acquisition"]["acquisition_id"] entered_start = threading.Event() release_start = threading.Event() state_started = threading.Event() state_finished = threading.Event() worker_errors: list[BaseException] = [] snapshots: list[dict[str, Any]] = [] original_start_live = runtime.start_live def blocked_start_live(*args: object, **kwargs: object) -> None: entered_start.set() if not release_start.wait(timeout=2): raise TimeoutError("test did not release start handoff") original_start_live(*args, **kwargs) # type: ignore[arg-type] def start_worker() -> None: try: service.start_acquisition(StartAcquisitionRequest(acquisition_id=acquisition_id)) except BaseException as exc: # pragma: no cover - asserted below worker_errors.append(exc) def state_worker() -> None: state_started.set() try: snapshots.append(service.state()) except BaseException as exc: # pragma: no cover - asserted below worker_errors.append(exc) finally: state_finished.set() monkeypatch.setattr(runtime, "start_live", blocked_start_live) start_thread = threading.Thread(target=start_worker) state_thread = threading.Thread(target=state_worker) start_thread.start() assert entered_start.wait(timeout=2) state_thread.start() assert state_started.wait(timeout=2) assert not state_finished.wait(timeout=0.05) release_start.set() start_thread.join(timeout=2) state_thread.join(timeout=2) assert not start_thread.is_alive() assert not state_thread.is_alive() assert worker_errors == [] assert snapshots[0]["acquisition"]["state"] == "starting" assert runtime.source_mode == "live" assert service._acquisition_session_lease is not None # noqa: SLF001 def test_abort_waits_for_start_handoff_then_stops_owned_producers( tmp_path: Path, monkeypatch: pytest.MonkeyPatch, ) -> None: service, runtime = service_with_fake_runtime(tmp_path) prepared = service.prepare_acquisition( PrepareAcquisitionRequest( project_name=PROJECT_NAME, host="192.168.1.20", compatibility_attestation=ATTESTATION, ) ) acquisition_id = prepared["acquisition"]["acquisition_id"] entered_start = threading.Event() release_start = threading.Event() abort_finished = threading.Event() worker_errors: list[BaseException] = [] original_start_live = runtime.start_live def blocked_start_live(*args: object, **kwargs: object) -> None: entered_start.set() if not release_start.wait(timeout=2): raise TimeoutError("test did not release start handoff") original_start_live(*args, **kwargs) # type: ignore[arg-type] def start_worker() -> None: try: service.start_acquisition(StartAcquisitionRequest(acquisition_id=acquisition_id)) except BaseException as exc: # pragma: no cover - asserted below worker_errors.append(exc) def abort_worker() -> None: try: service.abort_acquisition(AbortAcquisitionRequest(acquisition_id=acquisition_id)) except BaseException as exc: # pragma: no cover - asserted below worker_errors.append(exc) finally: abort_finished.set() monkeypatch.setattr(runtime, "start_live", blocked_start_live) start_thread = threading.Thread(target=start_worker) abort_thread = threading.Thread(target=abort_worker) start_thread.start() assert entered_start.wait(timeout=2) abort_thread.start() assert not abort_finished.wait(timeout=0.05) release_start.set() start_thread.join(timeout=2) abort_thread.join(timeout=2) assert worker_errors == [] state = service.state() assert state["acquisition"]["state"] == "aborted" assert runtime.source_mode == "idle" assert service._acquisition_session_lease is None # noqa: SLF001 def test_camera_selection_finishes_before_serialized_acquisition_stop( tmp_path: Path, monkeypatch: pytest.MonkeyPatch, ) -> None: service, runtime = service_with_fake_runtime(tmp_path) prepared = service.prepare_acquisition( PrepareAcquisitionRequest( project_name=PROJECT_NAME, host="192.168.1.20", compatibility_attestation=ATTESTATION, ) ) acquisition_id = prepared["acquisition"]["acquisition_id"] device_session_id = prepared["device_session"]["device_session_id"] service.start_acquisition(StartAcquisitionRequest(acquisition_id=acquisition_id)) service._k1_ip = "192.168.1.20" # noqa: SLF001 entered_camera_arm = threading.Event() release_camera_arm = threading.Event() stop_finished = threading.Event() worker_errors: list[BaseException] = [] events: list[str] = [] def blocked_camera_arm(_out_dir: Path, *, require_session: bool = False) -> None: assert require_session is True events.append("arm") entered_camera_arm.set() if not release_camera_arm.wait(timeout=2): raise TimeoutError("test did not release camera arm") def select_worker() -> None: try: service.select_camera_preview( CameraPreviewSelectRequest( source_id="sensor.camera.left", device_session_id=device_session_id, ) ) except BaseException as exc: # pragma: no cover - asserted below worker_errors.append(exc) def stop_worker() -> None: try: service.stop_acquisition( StopAcquisitionRequest( acquisition_id=acquisition_id, mode="capture-only", ) ) except BaseException as exc: # pragma: no cover - asserted below worker_errors.append(exc) finally: stop_finished.set() monkeypatch.setattr(service, "_arm_camera_recording", blocked_camera_arm) monkeypatch.setattr( service.camera_preview, "select", lambda _source_id, _target: events.append("select"), ) monkeypatch.setattr( service.camera_preview, "stop_recording", lambda **_kwargs: events.append("camera-stop"), ) monkeypatch.setattr(runtime, "stop", lambda: events.append("runtime-stop")) select_thread = threading.Thread(target=select_worker) stop_thread = threading.Thread(target=stop_worker) select_thread.start() assert entered_camera_arm.wait(timeout=2) stop_thread.start() assert not stop_finished.wait(timeout=0.05) release_camera_arm.set() select_thread.join(timeout=2) stop_thread.join(timeout=2) assert worker_errors == [] assert events == ["arm", "select", "camera-stop", "runtime-stop"] assert service.state()["acquisition"]["state"] == "completed" assert service._acquisition_session_lease is None # noqa: SLF001 def test_camera_arm_failure_seals_stopped_session_before_releasing_lease( tmp_path: Path, monkeypatch: pytest.MonkeyPatch, ) -> None: service, runtime = service_with_fake_runtime(tmp_path) prepared = service.prepare_acquisition( PrepareAcquisitionRequest( project_name=PROJECT_NAME, host="192.168.1.20", compatibility_attestation=ATTESTATION, ) ) acquisition_id = prepared["acquisition"]["acquisition_id"] events: list[str] = [] def start_live( _host: str, out_dir: Path, *, duration_seconds: float | None, project_name: str, ) -> None: assert duration_seconds is None assert project_name == PROJECT_NAME events.append("start") out_dir.mkdir(parents=True) runtime.phase = "starting_live" runtime.source_mode = "live" def stop_runtime() -> None: events.append("runtime") runtime.phase = "idle" runtime.source_mode = "idle" monkeypatch.setattr(runtime, "start_live", start_live) monkeypatch.setattr(runtime, "stop", stop_runtime) monkeypatch.setattr( service, "_arm_camera_recording", lambda _out_dir: (_ for _ in ()).throw(RuntimeError("synthetic camera arm failure")), ) monkeypatch.setattr( service.camera_preview, "stop_recording", lambda **_kwargs: events.append("camera"), ) monkeypatch.setattr( facade_module, "seal_capture_clock", lambda _capture_root: events.append("seal"), ) with pytest.raises(RuntimeError, match="synthetic camera arm failure"): service.start_acquisition(StartAcquisitionRequest(acquisition_id=acquisition_id)) state = service.state() assert state["acquisition"]["state"] == "failed" assert state["last_operation"]["status"] == "failed" assert events == ["start", "camera", "runtime", "seal"] assert service._acquisition_session_lease is None # noqa: SLF001 def test_start_cleanup_failure_retains_lease_and_marks_side_effect_unknown( tmp_path: Path, monkeypatch: pytest.MonkeyPatch, ) -> None: service, runtime = service_with_fake_runtime(tmp_path) prepared = service.prepare_acquisition( PrepareAcquisitionRequest( project_name=PROJECT_NAME, host="192.168.1.20", compatibility_attestation=ATTESTATION, ) ) acquisition_id = prepared["acquisition"]["acquisition_id"] def fail_camera_arm(out_dir: Path) -> None: out_dir.mkdir() raise RuntimeError("synthetic camera arm failure") monkeypatch.setattr(service, "_arm_camera_recording", fail_camera_arm) monkeypatch.setattr(facade_module, "seal_capture_clock", lambda _capture_root: None) runtime.stop_error = RuntimeError("synthetic cleanup timeout") with pytest.raises(RuntimeError, match="synthetic camera arm failure"): service.start_acquisition(StartAcquisitionRequest(acquisition_id=acquisition_id)) state = service.state() operation = next(item for item in state["operations"] if item["action"] == "acquisition.start") assert state["acquisition"]["state"] == "failed" assert operation["status"] == "failed" assert operation["error"]["side_effect_status"] == "unknown" assert service._acquisition_session_lease is not None # noqa: SLF001 runtime.stop_error = None service.stop() assert service._acquisition_session_lease is None # noqa: SLF001 def test_receiver_completion_without_point_data_fails_start_operation(tmp_path: Path) -> None: service, runtime = service_with_fake_runtime(tmp_path) prepared = service.prepare_acquisition( PrepareAcquisitionRequest( project_name=PROJECT_NAME, host="192.168.1.20", duration_seconds=60, compatibility_attestation=ATTESTATION, ) ) acquisition_id = prepared["acquisition"]["acquisition_id"] service.start_acquisition(StartAcquisitionRequest(acquisition_id=acquisition_id)) runtime.phase = "idle" runtime.source_mode = "idle" failed = service.state() assert failed["acquisition"]["state"] == "failed" assert failed["last_operation"]["status"] == "failed" assert failed["acquisition"]["result"]["device_state"] == "unknown" def test_capture_only_stop_never_claims_that_physical_k1_stopped(tmp_path: Path) -> None: service, runtime = service_with_fake_runtime(tmp_path) prepared = service.prepare_acquisition( PrepareAcquisitionRequest( project_name=PROJECT_NAME, host="192.168.1.20", duration_seconds=60, compatibility_attestation=ATTESTATION, ) ) acquisition_id = prepared["acquisition"]["acquisition_id"] service.start_acquisition(StartAcquisitionRequest(acquisition_id=acquisition_id)) stopped = service.stop_acquisition( StopAcquisitionRequest(acquisition_id=acquisition_id, mode="capture-only") ) assert runtime.stop_calls == 1 assert stopped["acquisition"]["state"] == "completed" assert stopped["acquisition"]["result"] == { "receiver_stopped": True, "device_stop": "unknown", } operations = {item["action"]: item for item in stopped["operations"]} assert operations["acquisition.start"]["status"] == "cancelled" assert operations["acquisition.stop"]["status"] == "succeeded" def test_stop_seals_session_clock_after_camera_and_runtime_before_lease_release( tmp_path: Path, monkeypatch: pytest.MonkeyPatch, ) -> None: service, runtime = service_with_fake_runtime(tmp_path) out_dir = tmp_path / "evidence-session" out_dir.mkdir() service._acquisition_out_dir = out_dir # noqa: SLF001 events: list[object] = [] monkeypatch.setattr( service.camera_preview, "stop_recording", lambda **_kwargs: events.append("camera"), ) monkeypatch.setattr(runtime, "stop", lambda: events.append("runtime")) monkeypatch.setattr( facade_module, "seal_capture_clock", lambda capture_root: events.append(("seal", capture_root)), ) class Lease: def release(self) -> None: events.append("lease") service._acquisition_session_lease = Lease() # type: ignore[assignment] # noqa: SLF001 service._stop_acquisition_sources( # noqa: SLF001 camera_status="complete", camera_failure_code=None, ) assert events == [ "camera", "runtime", ("seal", out_dir / "captures" / "mqtt_live"), "lease", ] def test_stop_timeout_retains_lease_and_blocks_replacement_acquisition( tmp_path: Path, monkeypatch: pytest.MonkeyPatch, ) -> None: service, runtime = service_with_fake_runtime(tmp_path) prepared = service.prepare_acquisition( PrepareAcquisitionRequest( project_name=PROJECT_NAME, host="192.168.1.20", compatibility_attestation=ATTESTATION, ) ) acquisition_id = prepared["acquisition"]["acquisition_id"] service.start_acquisition(StartAcquisitionRequest(acquisition_id=acquisition_id)) out_dir = service._acquisition_out_dir # noqa: SLF001 assert out_dir is not None out_dir.mkdir() seal_calls: list[Path] = [] monkeypatch.setattr( facade_module, "seal_capture_clock", lambda capture_root: seal_calls.append(capture_root), ) runtime.stop_error = RuntimeError("synthetic stop timeout") with pytest.raises(RuntimeError, match="synthetic stop timeout"): service.stop_acquisition( StopAcquisitionRequest(acquisition_id=acquisition_id, mode="capture-only") ) assert seal_calls == [] assert service._acquisition_session_lease is not None # noqa: SLF001 assert service.state()["acquisition"]["cleanup_pending"] is True with pytest.raises(RuntimeError, match="evidence-сессия"): service.prepare_acquisition( PrepareAcquisitionRequest( project_name="replacement", host="192.168.1.20", compatibility_attestation=ATTESTATION, ) ) runtime.stop_error = None retried = service.stop() assert retried["acquisition"]["state"] == "failed" assert len(seal_calls) == 1 assert service._acquisition_session_lease is None # noqa: SLF001 assert retried["acquisition"]["cleanup_pending"] is False replacement = service.prepare_acquisition( PrepareAcquisitionRequest( project_name="replacement", host="192.168.1.20", compatibility_attestation=ATTESTATION, ) ) assert replacement["acquisition"]["state"] == "prepared" def test_replay_rejects_retained_failed_acquisition_lease( tmp_path: Path, ) -> None: service, runtime = service_with_fake_runtime(tmp_path) prepared = service.prepare_acquisition( PrepareAcquisitionRequest( project_name=PROJECT_NAME, host="192.168.1.20", compatibility_attestation=ATTESTATION, ) ) acquisition_id = prepared["acquisition"]["acquisition_id"] service.start_acquisition(StartAcquisitionRequest(acquisition_id=acquisition_id)) runtime.stop_error = RuntimeError("synthetic stop timeout") with pytest.raises(RuntimeError, match="synthetic stop timeout"): service.stop_acquisition( StopAcquisitionRequest(acquisition_id=acquisition_id, mode="capture-only") ) with pytest.raises(RuntimeError, match="не запечатана"): service.start_replay("sessions/fixture.k1mqtt", speed=1.0, loop=False) runtime.stop_error = None service.stop() def test_new_explicit_stop_retries_retained_terminal_cleanup( tmp_path: Path, monkeypatch: pytest.MonkeyPatch, ) -> None: service, runtime = service_with_fake_runtime(tmp_path) prepared = service.prepare_acquisition( PrepareAcquisitionRequest( project_name=PROJECT_NAME, host="192.168.1.20", compatibility_attestation=ATTESTATION, ) ) acquisition_id = prepared["acquisition"]["acquisition_id"] service.start_acquisition(StartAcquisitionRequest(acquisition_id=acquisition_id)) out_dir = service._acquisition_out_dir # noqa: SLF001 assert out_dir is not None out_dir.mkdir() seal_calls: list[Path] = [] monkeypatch.setattr( facade_module, "seal_capture_clock", lambda capture_root: seal_calls.append(capture_root), ) runtime.stop_error = RuntimeError("synthetic stop timeout") with pytest.raises(RuntimeError, match="synthetic stop timeout"): service.stop_acquisition( StopAcquisitionRequest(acquisition_id=acquisition_id, mode="capture-only") ) runtime.stop_error = None recovered = service.stop_acquisition( StopAcquisitionRequest( acquisition_id=acquisition_id, mode="capture-only", idempotency_key="retry-retained-cleanup", ) ) retry_operation = recovered["last_operation"] assert retry_operation["status"] == "succeeded" assert retry_operation["stage_code"] == "retained-cleanup-completed" assert retry_operation["result"]["device_stop"] == "unknown" assert len(seal_calls) == 1 assert service._acquisition_session_lease is None # noqa: SLF001 def test_natural_receiver_completion_fails_acquisition_when_session_clock_cannot_seal( tmp_path: Path, monkeypatch: pytest.MonkeyPatch, ) -> None: service, runtime = service_with_fake_runtime(tmp_path) prepared = service.prepare_acquisition( PrepareAcquisitionRequest( project_name=PROJECT_NAME, host="192.168.1.20", compatibility_attestation=ATTESTATION, ) ) acquisition_id = prepared["acquisition"]["acquisition_id"] service.start_acquisition(StartAcquisitionRequest(acquisition_id=acquisition_id)) runtime.pcl_frames = 1 service.state() out_dir = service._acquisition_out_dir # noqa: SLF001 assert out_dir is not None out_dir.mkdir() monkeypatch.setattr( facade_module, "seal_capture_clock", lambda _capture_root: (_ for _ in ()).throw(RuntimeError("synthetic seal failure")), ) runtime.phase = "idle" runtime.source_mode = "idle" with pytest.raises(RuntimeError, match="synthetic seal failure"): service.state() assert service._acquisition is not None # noqa: SLF001 assert service._acquisition.state == "failed" # noqa: SLF001 def test_natural_receiver_completion_reserves_finalization_before_reentrant_callback( tmp_path: Path, monkeypatch: pytest.MonkeyPatch, ) -> None: service, runtime = service_with_fake_runtime(tmp_path) prepared = service.prepare_acquisition( PrepareAcquisitionRequest( project_name=PROJECT_NAME, host="192.168.1.20", compatibility_attestation=ATTESTATION, ) ) acquisition_id = prepared["acquisition"]["acquisition_id"] service.start_acquisition(StartAcquisitionRequest(acquisition_id=acquisition_id)) runtime.pcl_frames = 1 service.state() out_dir = service._acquisition_out_dir # noqa: SLF001 assert out_dir is not None out_dir.mkdir() events: list[str] = [] def reentrant_camera_stop(**_kwargs: object) -> None: events.append("camera") nested = service.state() assert nested["acquisition"]["state"] == "finalizing" monkeypatch.setattr(service.camera_preview, "stop_recording", reentrant_camera_stop) monkeypatch.setattr( facade_module, "seal_capture_clock", lambda _capture_root: events.append("seal"), ) runtime.phase = "idle" runtime.source_mode = "idle" completed = service.state() assert completed["acquisition"]["state"] == "completed" assert events == ["camera", "seal"] def test_graceful_stop_waits_for_explicit_operator_confirmation(tmp_path: Path) -> None: service, runtime = service_with_fake_runtime(tmp_path) prepared = service.prepare_acquisition( PrepareAcquisitionRequest( project_name=PROJECT_NAME, host="192.168.1.20", duration_seconds=60, compatibility_attestation=ATTESTATION, ) ) acquisition_id = prepared["acquisition"]["acquisition_id"] service.start_acquisition(StartAcquisitionRequest(acquisition_id=acquisition_id)) runtime.mark_ready() service.state() runtime.pcl_frames = 1 acquiring = service.state() assert acquiring["acquisition"]["state"] == "acquiring" awaiting = service.stop_acquisition( StopAcquisitionRequest(acquisition_id=acquisition_id, mode="graceful") ) operation_id = awaiting["last_operation"]["operation_id"] assert runtime.stop_calls == 0 assert awaiting["acquisition"]["state"] == "awaiting_external_stop" assert awaiting["last_operation"]["status"] == "operator_action_required" retried = service.stop_acquisition( StopAcquisitionRequest( acquisition_id=acquisition_id, operation_id=operation_id, mode="graceful", ) ) assert retried["acquisition"]["state"] == "awaiting_external_stop" assert retried["last_operation"]["operation_id"] == operation_id assert retried["last_operation"]["status"] == "operator_action_required" with pytest.raises(ValueError, match="исходную stop-operation"): service.stop_acquisition( StopAcquisitionRequest( acquisition_id=acquisition_id, mode="graceful", operator_confirmed=True, ) ) completed = service.stop_acquisition( StopAcquisitionRequest( acquisition_id=acquisition_id, operation_id=operation_id, mode="graceful", operator_confirmed=True, ) ) assert runtime.stop_calls == 1 assert completed["acquisition"]["result"]["device_stop"] == "operator-confirmed" stop_operations = [ item for item in completed["operations"] if item["action"] == "acquisition.stop" ] assert len(stop_operations) == 1 assert stop_operations[0]["operation_id"] == operation_id assert stop_operations[0]["status"] == "succeeded" def test_graceful_stop_retry_by_idempotency_key_reuses_original_operation( tmp_path: Path, ) -> None: service, runtime = service_with_fake_runtime(tmp_path) prepared = service.prepare_acquisition( PrepareAcquisitionRequest( project_name=PROJECT_NAME, host="192.168.1.20", compatibility_attestation=ATTESTATION, ) ) acquisition_id = prepared["acquisition"]["acquisition_id"] service.start_acquisition(StartAcquisitionRequest(acquisition_id=acquisition_id)) runtime.mark_ready() service.state() runtime.pcl_frames = 1 service.state() first = service.stop_acquisition( StopAcquisitionRequest( acquisition_id=acquisition_id, idempotency_key="graceful-stop-once", mode="graceful", ) ) first_operation_id = first["last_operation"]["operation_id"] retried = service.stop_acquisition( StopAcquisitionRequest( acquisition_id=acquisition_id, idempotency_key="graceful-stop-once", mode="graceful", ) ) assert retried["last_operation"]["operation_id"] == first_operation_id assert retried["last_operation"]["status"] == "operator_action_required" assert ( len([item for item in retried["operations"] if item["action"] == "acquisition.stop"]) == 1 ) def test_unrelated_graceful_stop_is_rejected_while_confirmation_is_pending( tmp_path: Path, ) -> None: service, runtime = service_with_fake_runtime(tmp_path) prepared = service.prepare_acquisition( PrepareAcquisitionRequest( project_name=PROJECT_NAME, host="192.168.1.20", compatibility_attestation=ATTESTATION, ) ) acquisition_id = prepared["acquisition"]["acquisition_id"] service.start_acquisition(StartAcquisitionRequest(acquisition_id=acquisition_id)) runtime.mark_ready() service.state() runtime.pcl_frames = 1 service.state() first = service.stop_acquisition( StopAcquisitionRequest(acquisition_id=acquisition_id, mode="graceful") ) expected_operation_id = first["last_operation"]["operation_id"] with pytest.raises(ValueError, match="уже ожидает"): service.stop_acquisition( StopAcquisitionRequest( acquisition_id=acquisition_id, idempotency_key="unrelated-stop", mode="graceful", ) ) original = next( item for item in service.state()["operations"] if item["operation_id"] == expected_operation_id ) assert original["status"] == "operator_action_required" def test_graceful_stop_is_rejected_until_point_data_confirms_acquisition( tmp_path: Path, ) -> None: service, runtime = service_with_fake_runtime(tmp_path) prepared = service.prepare_acquisition( PrepareAcquisitionRequest( project_name=PROJECT_NAME, host="192.168.1.20", compatibility_attestation=ATTESTATION, ) ) acquisition_id = prepared["acquisition"]["acquisition_id"] service.start_acquisition(StartAcquisitionRequest(acquisition_id=acquisition_id)) with pytest.raises(ValueError, match="подтверждённого потока point cloud"): service.stop_acquisition( StopAcquisitionRequest(acquisition_id=acquisition_id, mode="graceful") ) state = service.state() assert runtime.stop_calls == 0 assert state["acquisition"]["state"] == "starting" assert state["last_operation"]["action"] == "acquisition.start" assert state["last_operation"]["status"] == "running" @pytest.mark.parametrize("evidence_policy", ["best-effort", "disabled"]) def test_prepare_rejects_unsupported_evidence_policies( tmp_path: Path, evidence_policy: str, ) -> None: service, _ = service_with_fake_runtime(tmp_path) with pytest.raises(ValueError, match="evidence_policy=required"): service.prepare_acquisition( PrepareAcquisitionRequest( project_name=PROJECT_NAME, host="192.168.1.20", evidence_policy=evidence_policy, # type: ignore[arg-type] compatibility_attestation=ATTESTATION, ) ) assert service.state()["compatibility"]["profile_id"] is None @pytest.mark.parametrize( "requested_streams", [ DEFAULT_LIVE_STREAMS[:-1], (*DEFAULT_LIVE_STREAMS, DEFAULT_LIVE_STREAMS[0]), ], ) def test_prepare_rejects_stream_subsets_and_duplicates( tmp_path: Path, requested_streams: tuple[str, ...], ) -> None: service, _ = service_with_fake_runtime(tmp_path) with pytest.raises(ValueError, match="полный проверенный набор"): service.prepare_acquisition( PrepareAcquisitionRequest( project_name=PROJECT_NAME, host="192.168.1.20", requested_streams=requested_streams, # type: ignore[arg-type] compatibility_attestation=ATTESTATION, ) ) def test_exact_profile_is_inactive_until_selected_for_live_device_info_verification( tmp_path: Path, ) -> None: service, _ = service_with_fake_runtime(tmp_path) initial = service.state() assert initial["compatibility"] == { "profile_id": None, "decision": "unknown", "permitted_mode": "evidence-only", "firmware_claim": "exact-3.0.2-profile-not-selected", "attestation": None, "vendor_writes_enabled": False, "camera_preview": "unverified", } assert initial["device_calibration"]["compatibility_profile_id"] is None attested = service.prepare_acquisition( PrepareAcquisitionRequest( project_name=PROJECT_NAME, host="192.168.1.20", compatibility_attestation=ATTESTATION, ) ) assert attested["compatibility"]["profile_id"] == XGRIDS_K1_COMPATIBILITY_PROFILE_ID assert attested["compatibility"]["decision"] == "limited" assert attested["compatibility"]["attestation"]["basis"] == ( "selected-profile-live-device-info-required" ) assert attested["device_session"]["compatibility_profile_id"] == ( XGRIDS_K1_COMPATIBILITY_PROFILE_ID ) def test_prepare_rejects_device_ap_without_completed_quick_connect(tmp_path: Path) -> None: service, _ = service_with_fake_runtime(tmp_path) with pytest.raises(ValueError, match="connection flow"): service.prepare_acquisition( PrepareAcquisitionRequest( project_name=PROJECT_NAME, host="192.168.56.1", compatibility_attestation=QUICK_CONNECT_ATTESTATION, ) ) state = service.state() assert state["device_ref"] is None assert state["compatibility"]["profile_id"] is None def test_runtime_error_terminalizes_pending_graceful_stop(tmp_path: Path) -> None: service, runtime = service_with_fake_runtime(tmp_path) prepared = service.prepare_acquisition( PrepareAcquisitionRequest( project_name=PROJECT_NAME, host="192.168.1.20", compatibility_attestation=ATTESTATION, ) ) acquisition_id = prepared["acquisition"]["acquisition_id"] service.start_acquisition(StartAcquisitionRequest(acquisition_id=acquisition_id)) runtime.mark_ready() service.state() runtime.pcl_frames = 1 service.state() awaiting = service.stop_acquisition( StopAcquisitionRequest(acquisition_id=acquisition_id, mode="graceful") ) stop_operation_id = awaiting["last_operation"]["operation_id"] runtime.phase = "error" failed = service.state() stop_operation = next( item for item in failed["operations"] if item["operation_id"] == stop_operation_id ) assert failed["acquisition"]["state"] == "failed" assert stop_operation["status"] == "failed" assert stop_operation["error"]["side_effect_status"] == "unknown" assert failed["source_mode"] == "idle" assert failed["phase"] != "error" assert runtime.stop_calls == 1 def test_receiver_completion_terminalizes_unconfirmed_graceful_stop( tmp_path: Path, ) -> None: service, runtime = service_with_fake_runtime(tmp_path) prepared = service.prepare_acquisition( PrepareAcquisitionRequest( project_name=PROJECT_NAME, host="192.168.1.20", compatibility_attestation=ATTESTATION, ) ) acquisition_id = prepared["acquisition"]["acquisition_id"] service.start_acquisition(StartAcquisitionRequest(acquisition_id=acquisition_id)) runtime.mark_ready() service.state() runtime.pcl_frames = 1 service.state() awaiting = service.stop_acquisition( StopAcquisitionRequest(acquisition_id=acquisition_id, mode="graceful") ) stop_operation_id = awaiting["last_operation"]["operation_id"] runtime.phase = "idle" runtime.source_mode = "idle" runtime.source_ready = False failed = service.state() stop_operation = next( item for item in failed["operations"] if item["operation_id"] == stop_operation_id ) assert failed["acquisition"]["state"] == "failed" assert failed["acquisition"]["result"] == { "receiver_stopped": True, "device_state": "unknown", } assert stop_operation["status"] == "failed" assert stop_operation["error"]["code"] == ("receiver-completed-before-device-stop-confirmation") def test_unconfirmed_stop_operation_terminalizes_before_clock_seal_error( tmp_path: Path, monkeypatch: pytest.MonkeyPatch, ) -> None: service, runtime = service_with_fake_runtime(tmp_path) prepared = service.prepare_acquisition( PrepareAcquisitionRequest( project_name=PROJECT_NAME, host="192.168.1.20", compatibility_attestation=ATTESTATION, ) ) acquisition_id = prepared["acquisition"]["acquisition_id"] service.start_acquisition(StartAcquisitionRequest(acquisition_id=acquisition_id)) runtime.pcl_frames = 1 service.state() awaiting = service.stop_acquisition( StopAcquisitionRequest(acquisition_id=acquisition_id, mode="graceful") ) stop_operation_id = awaiting["last_operation"]["operation_id"] out_dir = service._acquisition_out_dir # noqa: SLF001 assert out_dir is not None out_dir.mkdir() monkeypatch.setattr( facade_module, "seal_capture_clock", lambda _capture_root: (_ for _ in ()).throw(RuntimeError("synthetic seal failure")), ) runtime.phase = "idle" runtime.source_mode = "idle" with pytest.raises(RuntimeError, match="synthetic seal failure"): service.state() state = service.state() stop_operation = next( item for item in state["operations"] if item["operation_id"] == stop_operation_id ) assert state["acquisition"]["state"] == "failed" assert stop_operation["status"] == "failed" assert service._acquisition_session_lease is not None # noqa: SLF001 monkeypatch.setattr(facade_module, "seal_capture_clock", lambda _capture_root: None) service.stop() def test_abort_failure_terminalizes_acquisition_and_pending_start(tmp_path: Path) -> None: service, runtime = service_with_fake_runtime(tmp_path) prepared = service.prepare_acquisition( PrepareAcquisitionRequest( project_name=PROJECT_NAME, host="192.168.1.20", compatibility_attestation=ATTESTATION, ) ) acquisition_id = prepared["acquisition"]["acquisition_id"] service.start_acquisition(StartAcquisitionRequest(acquisition_id=acquisition_id)) runtime.stop_error = RuntimeError("synthetic receiver stop failure") with pytest.raises(RuntimeError, match="synthetic receiver stop failure"): service.abort_acquisition(AbortAcquisitionRequest(acquisition_id=acquisition_id)) state = service.state() operations = {item["action"]: item for item in state["operations"]} assert state["acquisition"]["state"] == "failed" assert state["acquisition"]["result"] == { "receiver_stopped": False, "device_state": "unknown", } assert operations["acquisition.start"]["status"] == "cancelled" assert operations["acquisition.abort"]["status"] == "failed" assert state["source_mode"] == "live" def test_abort_reserves_stopping_before_reentrant_runtime_callback( tmp_path: Path, monkeypatch: pytest.MonkeyPatch, ) -> None: service, runtime = service_with_fake_runtime(tmp_path) prepared = service.prepare_acquisition( PrepareAcquisitionRequest( project_name=PROJECT_NAME, host="192.168.1.20", compatibility_attestation=ATTESTATION, ) ) acquisition_id = prepared["acquisition"]["acquisition_id"] service.start_acquisition(StartAcquisitionRequest(acquisition_id=acquisition_id)) runtime.pcl_frames = 1 service.state() out_dir = service._acquisition_out_dir # noqa: SLF001 assert out_dir is not None out_dir.mkdir() events: list[str] = [] monkeypatch.setattr( service.camera_preview, "stop_recording", lambda **_kwargs: events.append("camera"), ) def reentrant_runtime_stop() -> None: events.append("runtime") runtime.phase = "idle" runtime.source_mode = "idle" nested = service.state() assert nested["acquisition"]["state"] == "stopping" monkeypatch.setattr(runtime, "stop", reentrant_runtime_stop) monkeypatch.setattr( facade_module, "seal_capture_clock", lambda _capture_root: events.append("seal"), ) aborted = service.abort_acquisition(AbortAcquisitionRequest(acquisition_id=acquisition_id)) assert aborted["acquisition"]["state"] == "aborted" assert events == ["camera", "runtime", "seal"] assert service._acquisition_session_lease is None # noqa: SLF001 def test_clean_close_seals_and_cancels_pending_start_operation( tmp_path: Path, monkeypatch: pytest.MonkeyPatch, ) -> None: service, runtime = service_with_fake_runtime(tmp_path) prepared = service.prepare_acquisition( PrepareAcquisitionRequest( project_name=PROJECT_NAME, host="192.168.1.20", compatibility_attestation=ATTESTATION, ) ) acquisition_id = prepared["acquisition"]["acquisition_id"] service.start_acquisition(StartAcquisitionRequest(acquisition_id=acquisition_id)) out_dir = service._acquisition_out_dir # noqa: SLF001 assert out_dir is not None out_dir.mkdir() events: list[str] = [] monkeypatch.setattr( service.camera_preview, "close", lambda: events.append("camera"), ) def close_runtime() -> None: events.append("runtime") runtime.phase = "idle" runtime.source_mode = "idle" monkeypatch.setattr(runtime, "close", close_runtime) monkeypatch.setattr( facade_module, "seal_capture_clock", lambda _capture_root: events.append("seal"), ) service.close() state = service.state() start_operation = next( item for item in state["operations"] if item["action"] == "acquisition.start" ) assert state["acquisition"]["state"] == "interrupted" assert start_operation["status"] == "cancelled" assert events == ["camera", "runtime", "seal"] assert service._acquisition_session_lease is None # noqa: SLF001 def test_clean_close_cancels_pending_external_stop_operation( tmp_path: Path, monkeypatch: pytest.MonkeyPatch, ) -> None: service, runtime = service_with_fake_runtime(tmp_path) prepared = service.prepare_acquisition( PrepareAcquisitionRequest( project_name=PROJECT_NAME, host="192.168.1.20", compatibility_attestation=ATTESTATION, ) ) acquisition_id = prepared["acquisition"]["acquisition_id"] service.start_acquisition(StartAcquisitionRequest(acquisition_id=acquisition_id)) runtime.pcl_frames = 1 service.state() awaiting = service.stop_acquisition( StopAcquisitionRequest(acquisition_id=acquisition_id, mode="graceful") ) stop_operation_id = awaiting["last_operation"]["operation_id"] monkeypatch.setattr(service.camera_preview, "close", lambda: None) monkeypatch.setattr(runtime, "close", lambda: runtime.stop()) service.close() state = service.state() stop_operation = next( item for item in state["operations"] if item["operation_id"] == stop_operation_id ) assert state["acquisition"]["state"] == "interrupted" assert stop_operation["status"] == "cancelled" assert all( item["status"] not in {"accepted", "running", "operator_action_required"} for item in state["operations"] if item["action"].startswith("acquisition.") ) def test_close_failure_fails_pending_operation_and_retains_lease( tmp_path: Path, monkeypatch: pytest.MonkeyPatch, ) -> None: service, runtime = service_with_fake_runtime(tmp_path) prepared = service.prepare_acquisition( PrepareAcquisitionRequest( project_name=PROJECT_NAME, host="192.168.1.20", compatibility_attestation=ATTESTATION, ) ) acquisition_id = prepared["acquisition"]["acquisition_id"] service.start_acquisition(StartAcquisitionRequest(acquisition_id=acquisition_id)) monkeypatch.setattr(service.camera_preview, "close", lambda: None) monkeypatch.setattr( runtime, "close", lambda: (_ for _ in ()).throw(RuntimeError("synthetic close timeout")), ) with pytest.raises(RuntimeError, match="synthetic close timeout"): service.close() state = service.state() start_operation = next( item for item in state["operations"] if item["action"] == "acquisition.start" ) assert state["acquisition"]["state"] == "failed" assert start_operation["status"] == "failed" assert start_operation["error"]["side_effect_status"] == "unknown" assert service._acquisition_session_lease is not None # noqa: SLF001 runtime.stop_error = None service.stop() assert service._acquisition_session_lease is None # noqa: SLF001 def test_replay_is_rejected_during_nonterminal_acquisition(tmp_path: Path) -> None: service, _ = service_with_fake_runtime(tmp_path) service.prepare_acquisition( PrepareAcquisitionRequest( project_name=PROJECT_NAME, host="192.168.1.20", compatibility_attestation=ATTESTATION, ) ) with pytest.raises(RuntimeError, match="активной acquisition-сессии"): service.start_replay("sessions/fixture.k1mqtt", speed=1.0, loop=False) assert service.state()["acquisition"]["state"] == "prepared" def test_prepare_rejects_active_replay_without_stopping_or_replacing_it( tmp_path: Path, ) -> None: service, runtime = service_with_fake_runtime(tmp_path) runtime.phase = "replay" runtime.source_mode = "replay" runtime.source_ready = True with pytest.raises(RuntimeError, match="активного live/replay"): service.prepare_acquisition( PrepareAcquisitionRequest( project_name=PROJECT_NAME, host="192.168.1.20", compatibility_attestation=ATTESTATION, ) ) state = service.state() assert state["acquisition"] is None assert state["source_mode"] == "replay" assert runtime.stop_calls == 0 assert service._acquisition_session_lease is None # noqa: SLF001 def test_network_provisioning_is_single_flight_and_secret_is_unwrapped_only_at_boundary( monkeypatch: pytest.MonkeyPatch, tmp_path: Path, ) -> None: service, _ = service_with_fake_runtime(tmp_path) _set_scanned_devices( service, [{"device_id": "k1-a"}, {"device_id": "k1-b"}], ) boundary_calls: list[tuple[str, str, str]] = [] async def scenario() -> dict[str, Any]: entered = asyncio.Event() release = asyncio.Event() async def fake_provision( device_id: str, ssid: str, password: str, **_: object, ) -> dict[str, Any]: boundary_calls.append((device_id, ssid, password)) entered.set() await release.wait() return { "started_at_utc": "2026-07-16T12:00:00Z", "completed_at_utc": "2026-07-16T12:00:01Z", "profile_id": "xgrids-k1-fw3-wifi-v1", "outcome": "lan_address_observed", "observations": [{"status": {"ipv4": "192.168.1.20"}}], } monkeypatch.setattr(facade_module, "provision_wifi_once", fake_provision) monkeypatch.setattr( facade_module, "_host_route_class", lambda _target: "direct-or-routed", ) first = asyncio.create_task( service.connect( ConnectRequest( device_id="k1-a", ssid="lab-network", password=SecretStr(PRIMARY_TEST_CREDENTIAL), compatibility_attestation=ATTESTATION, ) ) ) await asyncio.wait_for(entered.wait(), timeout=1.0) with pytest.raises(RuntimeError, match="уже выполняется"): await service.connect( ConnectRequest( device_id="k1-b", ssid="other-network", password=SecretStr(SECONDARY_TEST_CREDENTIAL), compatibility_attestation=ATTESTATION, ) ) release.set() return await asyncio.wait_for(first, timeout=1.0) connected = asyncio.run(scenario()) assert boundary_calls == [("k1-a", "lab-network", PRIMARY_TEST_CREDENTIAL)] assert connected["k1_ip"] == "192.168.1.20" assert connected["connection_mode"] == "bridge" assert PRIMARY_TEST_CREDENTIAL not in str(connected) provision_operations = [ item for item in connected["operations"] if item["action"] == "network.provision" ] assert {item["status"] for item in provision_operations} == {"succeeded", "failed"} def test_bridge_network_change_retires_terminal_acquisition_and_receiver_error( monkeypatch: pytest.MonkeyPatch, tmp_path: Path, ) -> None: service, runtime = service_with_fake_runtime(tmp_path) _set_scanned_devices(service, [{"device_id": "k1-a", "name": "XGR-TEST-A"}]) prepared = service.prepare_acquisition( PrepareAcquisitionRequest( project_name=PROJECT_NAME, host="192.168.1.20", compatibility_attestation=ATTESTATION, ) ) service.abort_acquisition( AbortAcquisitionRequest( acquisition_id=prepared["acquisition"]["acquisition_id"], ) ) runtime.phase = "error" runtime.source_mode = "live" stop_calls_before_connect = runtime.stop_calls async def fake_provision(*_: object, **__: object) -> dict[str, Any]: return { "started_at_utc": "2026-07-28T18:11:37Z", "completed_at_utc": "2026-07-28T18:11:44Z", "profile_id": "xgrids-k1-fw3-wifi-v1", "outcome": "lan_address_observed", "observations": [{"status": {"ipv4": "192.168.1.20"}}], } monkeypatch.setattr(facade_module, "provision_wifi_once", fake_provision) monkeypatch.setattr( facade_module, "_host_route_class", lambda _target: "direct-or-routed", ) connected = asyncio.run( service.connect( ConnectRequest( device_id="k1-a", ssid="lab-network", password=SecretStr(PRIMARY_TEST_CREDENTIAL), connection_mode="bridge", compatibility_attestation=ATTESTATION, ) ) ) assert connected["connection_mode"] == "bridge" assert connected["k1_ip"] == "192.168.1.20" assert connected["acquisition"] is None assert connected["source_mode"] == "idle" assert connected["phase"] == "connected" assert runtime.stop_calls == stop_calls_before_connect + 1 def test_bridge_provisioning_reports_host_route_mismatch_without_hiding_device_success( monkeypatch: pytest.MonkeyPatch, tmp_path: Path, ) -> None: service, _ = service_with_fake_runtime(tmp_path) _set_scanned_devices(service, [{"device_id": "k1-a", "name": "XGR-TEST-A"}]) async def fake_provision(*_: object, **__: object) -> dict[str, Any]: return { "started_at_utc": "2026-07-28T19:43:03Z", "completed_at_utc": "2026-07-28T19:43:16Z", "profile_id": "xgrids-k1-fw3-wifi-v1", "outcome": "lan_address_observed", "observations": [{"status": {"ipv4": "192.168.68.50"}}], } monkeypatch.setattr(facade_module, "provision_wifi_once", fake_provision) monkeypatch.setattr(facade_module, "_host_route_class", lambda _target: "tunnel") connected = asyncio.run( service.connect( ConnectRequest( device_id="k1-a", ssid="lab-router", password=SecretStr(PRIMARY_TEST_CREDENTIAL), connection_mode="bridge", compatibility_attestation=ATTESTATION, ) ) ) assert connected["k1_ip"] == "192.168.68.50" assert connected["phase"] == "device_selected" assert "компьютер подключён к другой сети" in connected["message"] assert connected["device_session"]["connectivity"] == "offline" assert connected["connection_verification"] == { "status": "host-route-mismatch", "lease_state": "disconnected", "lease_generation": 1, "endpoint_validation": "host-route", "network_reachability": "unreachable", "reason_code": "connection_lease_host_route_mismatch", "host_route_class": "tunnel", "write_performed": True, "observed_at": connected["connection_verification"]["observed_at"], } operation = next( item for item in connected["operations"] if item["action"] == "network.provision" ) assert operation["status"] == "succeeded" assert operation["result"]["host_route_ready"] is False assert operation["result"]["host_route_class"] == "tunnel" def test_quick_connect_activates_the_device_ap_then_associates_the_host( monkeypatch: pytest.MonkeyPatch, tmp_path: Path, ) -> None: service, _ = service_with_fake_runtime(tmp_path) _set_scanned_devices(service, [{"device_id": "k1-a", "name": "XGR-TEST-A"}]) activation_calls: list[str] = [] association_calls: list[tuple[Path, str, str]] = [] ble_session_open = False monkeypatch.setattr( facade_module, "ensure_wifi_profile_from_credential_source", lambda *_args, **_kwargs: { "schema_version": 1, "adapter": "macOS Keychain", "available": True, "profile_enrolled": True, "credential_source": "exact-firmware-profile", }, ) @asynccontextmanager async def fake_activation_session(device_id: str, **_: object) -> AsyncIterator[dict[str, Any]]: nonlocal ble_session_open activation_calls.append(device_id) ble_session_open = True try: yield { "profile_id": "xgrids-k1-fw3-quick-connect-ap-v1", "started_at_utc": "2026-07-19T15:00:00Z", "completed_at_utc": "2026-07-19T15:00:01Z", "outcome": "ap_ready_observed", "ready_observed": True, "write_performed": True, "write_mode": "with_response", } finally: ble_session_open = False def fake_associate( helper_path: Path, profile_id: str, expected_ssid: str, **_: object, ) -> dict[str, Any]: assert ble_session_open association_calls.append((helper_path, profile_id, expected_ssid)) return { "schema_version": 1, "adapter": "CoreWLAN", "outcome": "associated", "already_associated": False, "profile_enrolled": True, "scan_attempt_count": 2, "scan_elapsed_ms": 900, "credential_source": "system-wifi-keychain", } async def forbidden_provisioning_write(*_: object, **__: object) -> dict[str, Any]: raise AssertionError("Quick Connect must not send router credentials to the K1") monkeypatch.setattr( facade_module, "device_ap_activation_session", fake_activation_session, ) monkeypatch.setattr(facade_module, "associate_with_wifi_profile_once", fake_associate) monkeypatch.setattr(facade_module, "provision_wifi_once", forbidden_provisioning_write) monkeypatch.setattr(facade_module, "_target_is_local_ipv4", lambda _target: False) state = asyncio.run( service.connect( ConnectRequest( device_id="k1-a", connection_mode="quick-connect", compatibility_attestation=QUICK_CONNECT_ATTESTATION, ) ) ) assert activation_calls == ["k1-a"] assert not ble_session_open assert len(association_calls) == 1 assert association_calls[0][0].name == "associate_wifi.swift" assert association_calls[0][1] == facade_module.quick_connect_host_profile_id("XGR-TEST-A") assert association_calls[0][2] == "XGR-TEST-A" assert state["connection_mode"] == "quick-connect" assert state["k1_ip"] == "192.168.56.1" assert state["compatibility"]["attestation"]["topology"] == "device-ap" quick_sessions = sorted(service.evidence_root.glob("*viewer_k1_ap_association*")) assert len(quick_sessions) == 1 assert not (quick_sessions[0] / "provisioning.sensitive.json").exists() redacted_manifest = (quick_sessions[0] / "manifest.redacted.json").read_text(encoding="utf-8") assert PRIMARY_TEST_CREDENTIAL not in redacted_manifest assert "host_wifi_profile_id" in redacted_manifest assert '"host_wifi_profile_ready_before_device_write": true' in redacted_manifest assert '"credentials_resolved_by_plugin": true' in redacted_manifest assert "credential_provider_id" in redacted_manifest assert "device_ap_activation_profile_id" in redacted_manifest assert (quick_sessions[0] / "ap-activation.redacted.json").exists() assert ( service._camera_target_for_session( # noqa: SLF001 state["device_session"]["device_session_id"] ) == "192.168.56.1" ) prepared = service.prepare_acquisition( PrepareAcquisitionRequest( project_name=PROJECT_NAME, compatibility_attestation=QUICK_CONNECT_ATTESTATION, ) ) assert prepared["acquisition"]["target_host"] == "192.168.56.1" def test_direct_connect_reuses_the_reviewed_ble_provisioning_frame( monkeypatch: pytest.MonkeyPatch, tmp_path: Path, ) -> None: service, _ = service_with_fake_runtime(tmp_path) _set_scanned_devices(service, [{"device_id": "k1-a", "name": "XGR-TEST-A"}]) provisioning_calls: list[tuple[str, str, str]] = [] async def fake_provision( device_id: str, ssid: str, password: str, **_: object, ) -> dict[str, Any]: provisioning_calls.append((device_id, ssid, password)) return { "started_at_utc": "2026-07-19T01:00:00Z", "completed_at_utc": "2026-07-19T01:00:01Z", "profile_id": "xgrids-k1-fw3-wifi-v1", "outcome": "lan_address_observed", "observations": [{"status": {"ipv4": "172.20.10.2"}}], } def forbidden_host_association(*_: object, **__: object) -> dict[str, Any]: raise AssertionError("Direct Connect must not switch the host Wi-Fi network") monkeypatch.setattr(facade_module, "provision_wifi_once", fake_provision) monkeypatch.setattr( facade_module, "associate_with_wifi_profile_once", forbidden_host_association, ) monkeypatch.setattr(facade_module, "_target_is_local_ipv4", lambda _target: False) monkeypatch.setattr( facade_module, "_host_route_class", lambda _target: "direct-or-routed", ) state = asyncio.run( service.connect( ConnectRequest( device_id="k1-a", ssid="controller-hotspot", password=SecretStr(PRIMARY_TEST_CREDENTIAL), connection_mode="direct-connect", compatibility_attestation=DIRECT_CONNECT_ATTESTATION, ) ) ) assert provisioning_calls == [("k1-a", "controller-hotspot", PRIMARY_TEST_CREDENTIAL)] assert state["connection_mode"] == "direct-connect" assert state["k1_ip"] == "172.20.10.2" assert state["compatibility"]["attestation"]["topology"] == ("controller-hotspot") def test_quick_connect_missing_credential_provider_stops_before_ap_write( monkeypatch: pytest.MonkeyPatch, tmp_path: Path, ) -> None: service, _ = service_with_fake_runtime(tmp_path) _set_scanned_devices(service, [{"device_id": "k1-a", "name": "XGR-TEST-A"}]) service._selected_device_id = "previous-k1" # noqa: SLF001 service._k1_ip = "192.168.1.20" # noqa: SLF001 service._connection_mode = "bridge" # noqa: SLF001 monkeypatch.setattr( facade_module, "ensure_wifi_profile_from_credential_source", lambda *_args, **_kwargs: { "schema_version": 1, "adapter": "macOS Keychain", "available": False, "profile_enrolled": False, "credential_source": None, }, ) @asynccontextmanager async def forbidden_activation( *_args: object, **_kwargs: object ) -> AsyncIterator[dict[str, Any]]: raise AssertionError("missing host credential must stop before the K1 AP write") yield {} monkeypatch.setattr( facade_module, "device_ap_activation_session", forbidden_activation, ) with pytest.raises(RuntimeError, match="credential-source-unavailable"): asyncio.run( service.connect( ConnectRequest( device_id="k1-a", connection_mode="quick-connect", compatibility_attestation=QUICK_CONNECT_ATTESTATION, ) ) ) state = service.state() assert state["selected_device_id"] == "previous-k1" assert state["k1_ip"] == "192.168.1.20" assert state["connection_mode"] == "bridge" assert not list(service.evidence_root.glob("*viewer_k1_ap_association*")) operation = next(item for item in state["operations"] if item["action"] == "network.provision") assert operation["status"] == "failed" assert operation["error"]["side_effect_status"] == "none" assert operation["error"]["safe_to_retry"] is True def test_quick_connect_does_not_start_host_wifi_without_ap_ready_flag( monkeypatch: pytest.MonkeyPatch, tmp_path: Path, ) -> None: service, _ = service_with_fake_runtime(tmp_path) _set_scanned_devices(service, [{"device_id": "k1-a", "name": "XGR-TEST-A"}]) monkeypatch.setattr( facade_module, "ensure_wifi_profile_from_credential_source", lambda *_args, **_kwargs: { "schema_version": 1, "adapter": "macOS Keychain", "available": True, "profile_enrolled": False, "credential_source": "exact-firmware-profile", }, ) @asynccontextmanager async def not_ready_session(*_: object, **__: object) -> AsyncIterator[dict[str, Any]]: yield { "profile_id": "xgrids-k1-fw3-quick-connect-ap-v1", "started_at_utc": "2026-07-19T15:00:00Z", "completed_at_utc": "2026-07-19T15:00:15Z", "outcome": "no_status_change_before_timeout", "ready_observed": False, "write_performed": True, "write_mode": "with_response", } def forbidden_association(*_: object, **__: object) -> dict[str, Any]: raise AssertionError("host Wi-Fi must wait for the canonical AP-ready flag") monkeypatch.setattr( facade_module, "device_ap_activation_session", not_ready_session, ) monkeypatch.setattr( facade_module, "associate_with_wifi_profile_once", forbidden_association, ) with pytest.raises(RuntimeError, match="не подтвердил готовность"): asyncio.run( service.connect( ConnectRequest( device_id="k1-a", connection_mode="quick-connect", compatibility_attestation=QUICK_CONNECT_ATTESTATION, ) ) ) quick_sessions = sorted(service.evidence_root.glob("*viewer_k1_ap_association*")) assert len(quick_sessions) == 1 assert (quick_sessions[0] / "ap-activation.redacted.json").exists() assert not (quick_sessions[0] / "manifest.redacted.json").exists() def test_failed_connection_change_revokes_the_previous_route( monkeypatch: pytest.MonkeyPatch, tmp_path: Path, ) -> None: service, _ = service_with_fake_runtime(tmp_path) _set_scanned_devices(service, [{"device_id": "k1-a", "name": "XGR-TEST-A"}]) service._selected_device_id = "previous-k1" # noqa: SLF001 service._k1_ip = "192.168.1.20" # noqa: SLF001 service._connection_mode = "bridge" # noqa: SLF001 monkeypatch.setattr( facade_module, "ensure_wifi_profile_from_credential_source", lambda *_args, **_kwargs: { "schema_version": 1, "adapter": "macOS Keychain", "available": True, "profile_enrolled": False, "credential_source": "exact-firmware-profile", }, ) @asynccontextmanager async def fake_activation_session(*_: object, **__: object) -> AsyncIterator[dict[str, Any]]: yield { "profile_id": "xgrids-k1-fw3-quick-connect-ap-v1", "started_at_utc": "2026-07-19T15:00:00Z", "completed_at_utc": "2026-07-19T15:00:01Z", "outcome": "ap_ready_observed", "ready_observed": True, "write_performed": True, "write_mode": "with_response", } def failed_association(*_: object, **__: object) -> dict[str, Any]: raise facade_module.HostWifiProfileError( "network-not-found", scan_attempt_count=4, scan_elapsed_ms=15014, ) monkeypatch.setattr( facade_module, "device_ap_activation_session", fake_activation_session, ) monkeypatch.setattr(facade_module, "associate_with_wifi_profile_once", failed_association) with pytest.raises(RuntimeError, match="network-not-found"): asyncio.run( service.connect( ConnectRequest( device_id="k1-a", connection_mode="quick-connect", compatibility_attestation=QUICK_CONNECT_ATTESTATION, ) ) ) state = service.state() assert state["selected_device_id"] is None assert state["k1_ip"] is None assert state["connection_mode"] is None assert state["compatibility"]["attestation"] is None quick_sessions = sorted(service.evidence_root.glob("*viewer_k1_ap_association*")) failure_evidence = json.loads( (quick_sessions[0] / "host-wifi-association.redacted.json").read_text(encoding="utf-8") ) assert failure_evidence["reason_code"] == "network-not-found" assert failure_evidence["scan_attempt_count"] == 4 assert failure_evidence["scan_elapsed_ms"] == 15014 def test_network_provisioning_rejects_an_ipv4_owned_by_the_local_host( monkeypatch: pytest.MonkeyPatch, tmp_path: Path, caplog: pytest.LogCaptureFixture, ) -> None: service, _ = service_with_fake_runtime(tmp_path) _set_scanned_devices(service, [{"device_id": "k1-a"}]) async def fake_provision(*_: object, **__: object) -> dict[str, Any]: return { "started_at_utc": "2026-07-18T15:19:25Z", "completed_at_utc": "2026-07-18T15:19:26Z", "profile_id": "xgrids-k1-fw3-wifi-v1", "outcome": "lan_address_observed", "observations": [{"status": {"ipv4": "10.255.254.51"}}], } monkeypatch.setattr(facade_module, "provision_wifi_once", fake_provision) monkeypatch.setattr(facade_module, "_target_is_local_ipv4", lambda _target: True) with ( caplog.at_level(logging.ERROR, logger=facade_module.__name__), pytest.raises(RuntimeError, match="уже принадлежит этому компьютеру"), ): asyncio.run( service.connect( ConnectRequest( device_id="k1-a", ssid="lab-network", password=SecretStr(PRIMARY_TEST_CREDENTIAL), compatibility_attestation=ATTESTATION, ) ) ) state = service.state() assert state["selected_device_id"] is None assert state["k1_ip"] is None operation = next(item for item in state["operations"] if item["action"] == "network.provision") assert operation["status"] == "failed" assert operation["error"]["safe_to_retry"] is False assert operation["error"]["side_effect_status"] == "unknown" failure_log = next( record for record in caplog.records if getattr(record, "event_code", None) == "k1_network_provision_failed" ) assert failure_log.operation_stage == "ble-provisioning-write" assert failure_log.connection_mode == "bridge" assert failure_log.error_code == "RuntimeError" assert failure_log.safe_to_retry is False assert failure_log.side_effect_status == "unknown" assert failure_log.network_change_attempted is True assert PRIMARY_TEST_CREDENTIAL not in caplog.text assert "lab-network" not in caplog.text def test_provisioning_cannot_switch_device_during_active_acquisition( monkeypatch: pytest.MonkeyPatch, tmp_path: Path, ) -> None: service, _ = service_with_fake_runtime(tmp_path) _set_scanned_devices(service, [{"device_id": "k1-a"}]) service.prepare_acquisition( PrepareAcquisitionRequest( project_name=PROJECT_NAME, host="192.168.1.20", compatibility_attestation=ATTESTATION, ) ) called = False async def should_not_run(*_: object, **__: object) -> dict[str, Any]: nonlocal called called = True raise AssertionError("provisioning boundary must not be reached") monkeypatch.setattr(facade_module, "provision_wifi_once", should_not_run) with pytest.raises(RuntimeError, match="активной acquisition-сессии"): asyncio.run( service.connect( ConnectRequest( device_id="k1-a", ssid="lab-network", password=SecretStr(PRIMARY_TEST_CREDENTIAL), compatibility_attestation=ATTESTATION, ) ) ) assert called is False def test_sensor_catalog_exposes_two_browser_adapter_cameras_after_profile_attestation( tmp_path: Path, ) -> None: service, _ = service_with_fake_runtime(tmp_path) initial = service.state() initial_cameras = [ stream for stream in initial["sensor_catalog"]["streams"] if stream.get("semantic_channel_id") == "camera.preview.live" ] assert {stream["source_id"] for stream in initial_cameras} == { "sensor.camera.left", "sensor.camera.right", } assert all(stream["availability"] == "unverified" for stream in initial_cameras) state = service.prepare_acquisition( PrepareAcquisitionRequest( project_name=PROJECT_NAME, host="192.168.1.20", compatibility_attestation=ATTESTATION, ) ) cameras = [ stream for stream in state["sensor_catalog"]["streams"] if stream.get("semantic_channel_id") == "camera.preview.live" ] assert len(cameras) == 2 assert all(camera["availability"] == "available" for camera in cameras) assert all(camera["modality"] == "encoded-video" for camera in cameras) assert all(camera["decode_status"] == "rtsp-h264-observed-browser-remux" for camera in cameras) assert all(camera["activation"]["max_active"] == 1 for camera in cameras) assert all(camera["delivery"] is None for camera in cameras) assert state["connection_verification"]["network_reachability"] == "unknown" assert state["device_calibration"]["status"] == "unavailable" assert state["device_calibration"]["vehicle_extrinsics"] == ("host-domain-not-owned-by-plugin")