fix(k1): restore canonical local connection lifecycle

This commit is contained in:
DCCONSTRUCTIONS
2026-08-06 13:52:46 +03:00
parent 52da9b75b7
commit aff331082f
19 changed files with 2169 additions and 274 deletions
+150
View File
@@ -1,16 +1,33 @@
import asyncio
from collections.abc import Iterator
import pytest
from bleak.backends.device import BLEDevice
from bleak.backends.scanner import AdvertisementData
from pytest import MonkeyPatch
import k1link.device_plugins.xgrids_k1.ble.scanner as scanner_module
from k1link.device_plugins.xgrids_k1.ble.scanner import (
advertisement_record,
discovered_device,
discovered_device_selection,
scan,
)
@pytest.fixture(autouse=True)
def reset_runtime_handle_lease() -> Iterator[None]:
with scanner_module._runtime_handle_lock: # noqa: SLF001
scanner_module._runtime_handles.clear() # noqa: SLF001
scanner_module._runtime_handle_observed_at_monotonic = None # noqa: SLF001
scanner_module._runtime_handle_generation = 0 # noqa: SLF001
yield
with scanner_module._runtime_handle_lock: # noqa: SLF001
scanner_module._runtime_handles.clear() # noqa: SLF001
scanner_module._runtime_handle_observed_at_monotonic = None # noqa: SLF001
scanner_module._runtime_handle_generation = 0 # noqa: SLF001
def test_advertisement_record_marks_k1_candidate() -> None:
device = BLEDevice("TEST-UUID", "Unknown", details=None)
advertisement = AdvertisementData(
@@ -72,3 +89,136 @@ def test_scan_retains_the_live_corebluetooth_handle(
assert result["devices"][0]["macos_uuid"] == "LIVE-UUID"
assert discovered_device("LIVE-UUID") is device
assert discovered_device_selection("LIVE-UUID").from_fresh_scan is True
def test_scan_start_invalidates_previous_lease_and_failure_leaves_it_empty(
monkeypatch: MonkeyPatch,
) -> None:
old_device = BLEDevice("OLD-UUID", "XGR-OLD", details=object())
old_advertisement = AdvertisementData(
local_name="XGR-OLD",
manufacturer_data={},
service_data={},
service_uuids=[],
tx_power=0,
rssi=-41,
platform_data=(),
)
async def scenario() -> None:
async def initial_discover(
**_kwargs: object,
) -> dict[str, tuple[BLEDevice, AdvertisementData]]:
return {old_device.address: (old_device, old_advertisement)}
monkeypatch.setattr(scanner_module.BleakScanner, "discover", initial_discover)
await scan(1.0)
assert discovered_device(old_device.address) is old_device
failing_scan_started = asyncio.Event()
release_failing_scan = asyncio.Event()
async def failing_discover(**_kwargs: object) -> object:
failing_scan_started.set()
await release_failing_scan.wait()
raise RuntimeError("synthetic BLE scan failure")
monkeypatch.setattr(scanner_module.BleakScanner, "discover", failing_discover)
scan_task = asyncio.create_task(scan(1.0))
await failing_scan_started.wait()
# Starting a new explicit scan revokes the prior generation before I/O.
assert discovered_device(old_device.address) is None
assert discovered_device_selection(old_device.address).from_fresh_scan is False
release_failing_scan.set()
with pytest.raises(RuntimeError, match="synthetic BLE scan failure"):
await scan_task
assert discovered_device(old_device.address) is None
assert discovered_device_selection(old_device.address).from_fresh_scan is False
asyncio.run(scenario())
@pytest.mark.parametrize("older_scan_fails", [False, True])
def test_late_scan_generation_cannot_replace_or_clear_newer_lease(
monkeypatch: MonkeyPatch,
older_scan_fails: bool,
) -> None:
older_device = BLEDevice("OLDER-UUID", "XGR-OLDER", details=object())
newer_device = BLEDevice("NEWER-UUID", "XGR-NEWER", details=object())
older_advertisement = AdvertisementData(
local_name="XGR-OLDER",
manufacturer_data={},
service_data={},
service_uuids=[],
tx_power=0,
rssi=-51,
platform_data=(),
)
newer_advertisement = AdvertisementData(
local_name="XGR-NEWER",
manufacturer_data={},
service_data={},
service_uuids=[],
tx_power=0,
rssi=-31,
platform_data=(),
)
async def scenario() -> None:
call_count = 0
older_scan_started = asyncio.Event()
release_older_scan = asyncio.Event()
async def overlapping_discover(
**_kwargs: object,
) -> dict[str, tuple[BLEDevice, AdvertisementData]]:
nonlocal call_count
call_count += 1
if call_count == 1:
older_scan_started.set()
await release_older_scan.wait()
if older_scan_fails:
raise RuntimeError("late older scan failed")
return {older_device.address: (older_device, older_advertisement)}
return {newer_device.address: (newer_device, newer_advertisement)}
monkeypatch.setattr(scanner_module.BleakScanner, "discover", overlapping_discover)
older_task = asyncio.create_task(scan(1.0))
await older_scan_started.wait()
await scan(1.0)
assert discovered_device(newer_device.address) is newer_device
assert discovered_device(older_device.address) is None
release_older_scan.set()
if older_scan_fails:
with pytest.raises(RuntimeError, match="late older scan failed"):
await older_task
else:
await older_task
# Neither a late success nor a late failure owns the current lease.
assert discovered_device(newer_device.address) is newer_device
assert discovered_device(older_device.address) is None
asyncio.run(scenario())
def test_runtime_handle_outlives_operator_candidate_boundary(
monkeypatch: MonkeyPatch,
) -> None:
clock = [100.0]
handle = BLEDevice("LIVE-UUID", "XGR-K1", details=object())
monkeypatch.setattr(scanner_module, "monotonic", lambda: clock[0])
scanner_module._runtime_handles[handle.address] = handle # noqa: SLF001
scanner_module._runtime_handle_observed_at_monotonic = clock[0] # noqa: SLF001
clock[0] += scanner_module.BLE_DISCOVERY_CANDIDATE_LEASE_TTL_SECONDS + 0.001
assert discovered_device(handle.address) is handle
clock[0] = 100.0 + scanner_module.BLE_RUNTIME_HANDLE_LEASE_TTL_SECONDS + 0.001
assert discovered_device(handle.address) is None
+12
View File
@@ -55,6 +55,12 @@ def test_private_scanner_diagnostics_are_durable_structured_and_bounded(
"automatic_retry": False,
"side_effect_status": "unknown",
"network_change_attempted": True,
"device_write_attempted": True,
"device_write_confirmed": False,
"ble_att_error_code": 4,
"ble_att_error_name": "INVALID_PDU",
"helper_stage": "compile",
"helper_elapsed_ms": 34720,
"camera_source_id": "sensor.camera.right",
"evidence_session_id": "20260728T163450Z_viewer_live",
"activation_trigger": "application-control-scanning",
@@ -91,6 +97,12 @@ def test_private_scanner_diagnostics_are_durable_structured_and_bounded(
assert document["automatic_retry"] is False
assert document["side_effect_status"] == "unknown"
assert document["network_change_attempted"] is True
assert document["device_write_attempted"] is True
assert document["device_write_confirmed"] is False
assert document["ble_att_error_code"] == 4
assert document["ble_att_error_name"] == "INVALID_PDU"
assert document["helper_stage"] == "compile"
assert document["helper_elapsed_ms"] == 34720
assert document["camera_source_id"] == "sensor.camera.right"
assert document["evidence_session_id"] == "20260728T163450Z_viewer_live"
assert document["activation_trigger"] == "application-control-scanning"
+284 -22
View File
@@ -1,10 +1,12 @@
import asyncio
from collections.abc import Iterator
from types import SimpleNamespace
from typing import Any
import pytest
from bleak.exc import BleakDeviceNotFoundError
from bleak.exc import BleakDeviceNotFoundError, BleakGATTProtocolError
import k1link.device_plugins.xgrids_k1.ble.scanner as scanner_module
import k1link.device_plugins.xgrids_k1.ble.wifi_provisioning as wifi_module
from k1link.device_plugins.xgrids_k1.ble.wifi_provisioning import (
FRAME_LENGTH,
@@ -15,6 +17,27 @@ from k1link.device_plugins.xgrids_k1.ble.wifi_provisioning import (
)
@pytest.fixture(autouse=True)
def reset_runtime_handle_lease() -> Iterator[None]:
with scanner_module._runtime_handle_lock: # noqa: SLF001
scanner_module._runtime_handles.clear() # noqa: SLF001
scanner_module._runtime_handle_observed_at_monotonic = None # noqa: SLF001
scanner_module._runtime_handle_generation = 0 # noqa: SLF001
yield
with scanner_module._runtime_handle_lock: # noqa: SLF001
scanner_module._runtime_handles.clear() # noqa: SLF001
scanner_module._runtime_handle_observed_at_monotonic = None # noqa: SLF001
scanner_module._runtime_handle_generation = 0 # noqa: SLF001
def _seed_scan_lease(handles: dict[str, object], *, observed_at: float) -> None:
with scanner_module._runtime_handle_lock: # noqa: SLF001
scanner_module._runtime_handles.clear() # noqa: SLF001
scanner_module._runtime_handles.update(handles) # type: ignore[arg-type] # noqa: SLF001
scanner_module._runtime_handle_observed_at_monotonic = observed_at # noqa: SLF001
scanner_module._runtime_handle_generation += 1 # noqa: SLF001
def test_build_wifi_provisioning_frame_layout() -> None:
credential = "x" * 13
frame = build_wifi_provisioning_frame("LabNet", credential)
@@ -122,7 +145,15 @@ def test_read_wifi_status_once_reads_only_and_returns_current_dhcp_address(
self.write_calls += 1
raise AssertionError("status refresh must not write a BLE characteristic")
monkeypatch.setattr(wifi_module, "discovered_device", lambda _uuid: object())
retained_handle = object()
monkeypatch.setattr(
wifi_module,
"discovered_device_selection",
lambda _uuid: SimpleNamespace(
device=retained_handle,
from_fresh_scan=True,
),
)
monkeypatch.setattr(wifi_module, "BleakClient", FakeClient)
result = asyncio.run(read_wifi_status_once("synthetic-corebluetooth-uuid"))
@@ -132,7 +163,7 @@ def test_read_wifi_status_once_reads_only_and_returns_current_dhcp_address(
assert result["status"]["ipv4"] == "10.255.254.77"
def test_read_wifi_status_recovery_rediscover_ignores_retained_handle(
def test_read_wifi_status_recovery_keeps_fresh_retained_handle(
monkeypatch: pytest.MonkeyPatch,
) -> None:
value = bytearray(54)
@@ -141,8 +172,7 @@ def test_read_wifi_status_recovery_rediscover_ignores_retained_handle(
value[33] = 4
value[34:38] = bytes((10, 255, 254, 77))
value[50] = 1
stale_handle = object()
recovered_handle = object()
retained_handle = object()
characteristic = SimpleNamespace(
uuid=wifi_module.STATUS_CHARACTERISTIC_UUID,
service_uuid=wifi_module.SERVICE_UUID,
@@ -159,7 +189,7 @@ def test_read_wifi_status_recovery_rediscover_ignores_retained_handle(
class FakeClient:
def __init__(self, device: object, **_kwargs: object) -> None:
assert device is recovered_handle
assert device is retained_handle
self.services = FakeServices()
self.name = "XGR-K1"
@@ -173,9 +203,16 @@ def test_read_wifi_status_recovery_rediscover_ignores_retained_handle(
return bytes(value)
async def rediscover(*_args: object, **_kwargs: object) -> object:
return recovered_handle
raise AssertionError("a fresh explicit scan handle must not be discarded")
monkeypatch.setattr(wifi_module, "discovered_device", lambda _uuid: stale_handle)
monkeypatch.setattr(
wifi_module,
"discovered_device_selection",
lambda _uuid: SimpleNamespace(
device=retained_handle,
from_fresh_scan=True,
),
)
monkeypatch.setattr(wifi_module.BleakScanner, "find_device_by_address", rediscover)
monkeypatch.setattr(wifi_module, "BleakClient", FakeClient)
@@ -189,39 +226,264 @@ def test_read_wifi_status_recovery_rediscover_ignores_retained_handle(
assert result["status"]["ipv4"] == "10.255.254.77"
def test_provisioning_write_requires_fresh_rediscovery_before_connecting(
def test_provisioning_write_uses_retained_handle_without_rediscovery(
monkeypatch: pytest.MonkeyPatch,
) -> None:
stale_handle = object()
device_id = "synthetic-corebluetooth-uuid"
retained_handle = object()
rediscovery_calls: list[tuple[str, float]] = []
client_calls: list[object] = []
async def missing_device(address: str, *, timeout: float) -> None:
rediscovery_calls.append((address, timeout))
return None
class SelectedHandleObserved(RuntimeError):
pass
class ForbiddenClient:
async def forbidden_rediscovery(address: str, *, timeout: float) -> None:
rediscovery_calls.append((address, timeout))
raise AssertionError("a fresh explicit scan handle must be used directly")
class CapturingClient:
def __init__(self, device: object, **_kwargs: object) -> None:
client_calls.append(device)
raise AssertionError("a failed fresh discovery must stop before the BLE write session")
raise SelectedHandleObserved
monkeypatch.setattr(wifi_module, "discovered_device", lambda _uuid: stale_handle)
monkeypatch.setattr(scanner_module, "monotonic", lambda: 100.0)
_seed_scan_lease({device_id: retained_handle}, observed_at=100.0)
monkeypatch.setattr(
wifi_module.BleakScanner,
"find_device_by_address",
missing_device,
forbidden_rediscovery,
)
monkeypatch.setattr(wifi_module, "BleakClient", ForbiddenClient)
monkeypatch.setattr(wifi_module, "BleakClient", CapturingClient)
with pytest.raises(BleakDeviceNotFoundError):
with pytest.raises(SelectedHandleObserved) as caught:
asyncio.run(
provision_wifi_once(
"synthetic-corebluetooth-uuid",
device_id,
"LabNet",
"synthetic-password",
timeout_seconds=1.0,
)
)
assert rediscovery_calls == [("synthetic-corebluetooth-uuid", 1.0)]
assert client_calls == []
assert rediscovery_calls == []
assert client_calls == [retained_handle]
assert caught.value.operation_stage == "connect" # type: ignore[attr-defined]
assert caught.value.device_write_attempted is False # type: ignore[attr-defined]
assert caught.value.device_write_confirmed is False # type: ignore[attr-defined]
def test_provisioning_write_does_not_fallback_when_fresh_scan_omits_device(
monkeypatch: pytest.MonkeyPatch,
) -> None:
rediscovery_calls: list[tuple[str, float]] = []
async def forbidden_rediscovery(address: str, *, timeout: float) -> None:
rediscovery_calls.append((address, timeout))
return None
monkeypatch.setattr(scanner_module, "monotonic", lambda: 100.0)
_seed_scan_lease({}, observed_at=100.0)
monkeypatch.setattr(
wifi_module.BleakScanner,
"find_device_by_address",
forbidden_rediscovery,
)
with pytest.raises(BleakDeviceNotFoundError) as caught:
asyncio.run(
provision_wifi_once(
"not-in-fresh-scan",
"LabNet",
"synthetic-password",
timeout_seconds=1.0,
)
)
assert rediscovery_calls == []
assert caught.value.operation_stage == "resolution" # type: ignore[attr-defined]
assert caught.value.device_write_attempted is False # type: ignore[attr-defined]
assert caught.value.device_write_confirmed is False # type: ignore[attr-defined]
def test_provisioning_write_rediscovery_fallback_after_scan_lease_expires(
monkeypatch: pytest.MonkeyPatch,
) -> None:
device_id = "synthetic-corebluetooth-uuid"
expired_handle = object()
rediscovered_handle = object()
rediscovery_calls: list[tuple[str, float]] = []
client_calls: list[object] = []
clock = [100.0]
class RediscoveredHandleObserved(RuntimeError):
pass
async def rediscover(address: str, *, timeout: float) -> object:
rediscovery_calls.append((address, timeout))
return rediscovered_handle
class CapturingClient:
def __init__(self, device: object, **_kwargs: object) -> None:
client_calls.append(device)
raise RediscoveredHandleObserved
monkeypatch.setattr(scanner_module, "monotonic", lambda: clock[0])
_seed_scan_lease({device_id: expired_handle}, observed_at=clock[0])
clock[0] += scanner_module.BLE_RUNTIME_HANDLE_LEASE_TTL_SECONDS + 0.001
monkeypatch.setattr(wifi_module.BleakScanner, "find_device_by_address", rediscover)
monkeypatch.setattr(wifi_module, "BleakClient", CapturingClient)
with pytest.raises(RediscoveredHandleObserved):
asyncio.run(
provision_wifi_once(
device_id,
"LabNet",
"synthetic-password",
timeout_seconds=1.0,
)
)
assert rediscovery_calls == [(device_id, 1.0)]
assert client_calls == [rediscovered_handle]
assert scanner_module.discovered_device(device_id) is None
def test_provisioning_baseline_error_keeps_type_and_adds_safe_gatt_facts(
monkeypatch: pytest.MonkeyPatch,
) -> None:
device_id = "synthetic-corebluetooth-uuid"
retained_handle = object()
service = SimpleNamespace(uuid=wifi_module.SERVICE_UUID)
write_characteristic = SimpleNamespace(
uuid=wifi_module.WRITE_CHARACTERISTIC_UUID,
service_uuid=wifi_module.SERVICE_UUID,
properties=["write"],
max_write_without_response_size=512,
)
status_characteristic = SimpleNamespace(
uuid=wifi_module.STATUS_CHARACTERISTIC_UUID,
service_uuid=wifi_module.SERVICE_UUID,
properties=["read"],
)
class FakeServices:
def get_service(self, uuid: str) -> object | None:
return service if uuid == wifi_module.SERVICE_UUID else None
def get_characteristic(self, uuid: str) -> object | None:
if uuid == wifi_module.WRITE_CHARACTERISTIC_UUID:
return write_characteristic
if uuid == wifi_module.STATUS_CHARACTERISTIC_UUID:
return status_characteristic
return None
class FailingBaselineClient:
def __init__(self, device: object, **_kwargs: object) -> None:
assert device is retained_handle
self.services = FakeServices()
self.name = "XGR-K1"
async def __aenter__(self) -> Any:
return self
async def __aexit__(self, *_args: object) -> None:
return None
async def read_gatt_char(self, _characteristic: object) -> bytes:
raise BleakGATTProtocolError(0x0E)
monkeypatch.setattr(scanner_module, "monotonic", lambda: 100.0)
_seed_scan_lease({device_id: retained_handle}, observed_at=100.0)
monkeypatch.setattr(wifi_module, "BleakClient", FailingBaselineClient)
with pytest.raises(BleakGATTProtocolError) as caught:
asyncio.run(
provision_wifi_once(
device_id,
"LabNet",
"synthetic-password",
timeout_seconds=1.0,
)
)
error = caught.value
assert error.operation_stage == "baseline-read" # type: ignore[attr-defined]
assert error.device_write_attempted is False # type: ignore[attr-defined]
assert error.device_write_confirmed is False # type: ignore[attr-defined]
assert error.att_error_code == 0x0E # type: ignore[attr-defined]
assert error.att_error_name == "UNLIKELY_ERROR" # type: ignore[attr-defined]
def test_provisioning_status_poll_error_reports_confirmed_write(
monkeypatch: pytest.MonkeyPatch,
) -> None:
device_id = "synthetic-corebluetooth-uuid"
retained_handle = object()
service = SimpleNamespace(uuid=wifi_module.SERVICE_UUID)
write_characteristic = SimpleNamespace(
uuid=wifi_module.WRITE_CHARACTERISTIC_UUID,
service_uuid=wifi_module.SERVICE_UUID,
properties=["write"],
max_write_without_response_size=512,
)
status_characteristic = SimpleNamespace(
uuid=wifi_module.STATUS_CHARACTERISTIC_UUID,
service_uuid=wifi_module.SERVICE_UUID,
properties=["read"],
)
baseline = bytearray(52)
class FakeServices:
def get_service(self, uuid: str) -> object | None:
return service if uuid == wifi_module.SERVICE_UUID else None
def get_characteristic(self, uuid: str) -> object | None:
if uuid == wifi_module.WRITE_CHARACTERISTIC_UUID:
return write_characteristic
if uuid == wifi_module.STATUS_CHARACTERISTIC_UUID:
return status_characteristic
return None
class FailingPollClient:
def __init__(self, device: object, **_kwargs: object) -> None:
assert device is retained_handle
self.services = FakeServices()
self.name = "XGR-K1"
self.is_connected = True
self.read_count = 0
async def __aenter__(self) -> Any:
return self
async def __aexit__(self, *_args: object) -> None:
return None
async def read_gatt_char(self, _characteristic: object) -> bytes:
self.read_count += 1
if self.read_count == 1:
return bytes(baseline)
raise BleakGATTProtocolError(0x12)
async def write_gatt_char(self, *_args: object, **_kwargs: object) -> None:
return None
monkeypatch.setattr(scanner_module, "monotonic", lambda: 100.0)
_seed_scan_lease({device_id: retained_handle}, observed_at=100.0)
monkeypatch.setattr(wifi_module, "BleakClient", FailingPollClient)
with pytest.raises(BleakGATTProtocolError) as caught:
asyncio.run(
provision_wifi_once(
device_id,
"LabNet",
"synthetic-password",
timeout_seconds=1.0,
)
)
error = caught.value
assert error.operation_stage == "status-poll" # type: ignore[attr-defined]
assert error.device_write_attempted is True # type: ignore[attr-defined]
assert error.device_write_confirmed is True # type: ignore[attr-defined]
assert error.att_error_code == 0x12 # type: ignore[attr-defined]
assert error.att_error_name == "DATABASE_OUT_OF_SYNC" # type: ignore[attr-defined]
+284 -4
View File
@@ -200,14 +200,18 @@ def service_with_fake_runtime(
return service, runtime
def _wifi_status_read(ipv4: str | None) -> dict[str, Any]:
def _wifi_status_read(
ipv4: str | None,
*,
device_id: str = "test-ble-transport",
) -> 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_macos_uuid": device_id,
"device_name": "XGR-K1",
"service_uuid": "00007f00-0000-1000-8000-00805f9b34fb",
"status_characteristic_uuid": "00007f02-0000-1000-8000-00805f9b34fb",
@@ -2867,6 +2871,7 @@ def test_quick_connect_activates_the_device_ap_then_associates_the_host(
"ready_observed": True,
"write_performed": True,
"write_mode": "with_response",
"observations": [{"status": {"mode": "WIFI_AP"}}],
}
finally:
ble_session_open = False
@@ -3090,6 +3095,7 @@ def test_quick_connect_does_not_start_host_wifi_without_ap_ready_flag(
"ready_observed": False,
"write_performed": True,
"write_mode": "with_response",
"observations": [],
}
def forbidden_association(*_: object, **__: object) -> dict[str, Any]:
@@ -3121,6 +3127,9 @@ def test_quick_connect_does_not_start_host_wifi_without_ap_ready_flag(
assert len(quick_sessions) == 1
assert (quick_sessions[0] / "ap-activation.redacted.json").exists()
assert not (quick_sessions[0] / "manifest.redacted.json").exists()
reconciliation = service.state()["network_write_reconciliation"]
assert reconciliation["transport_ref"] == "k1-a"
assert reconciliation["status"] == "device-state-unknown-after-write"
def test_failed_connection_change_revokes_the_previous_route(
@@ -3154,6 +3163,7 @@ def test_failed_connection_change_revokes_the_previous_route(
"ready_observed": True,
"write_performed": True,
"write_mode": "with_response",
"observations": [{"status": {"mode": "WIFI_AP"}}],
}
def failed_association(*_: object, **__: object) -> dict[str, Any]:
@@ -3195,6 +3205,75 @@ def test_failed_connection_change_revokes_the_previous_route(
assert failure_evidence["scan_elapsed_ms"] == 15014
def test_quick_connect_helper_build_failure_after_ap_write_preserves_side_effect_facts(
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 ready_activation(*_: object, **__: object) -> AsyncIterator[dict[str, Any]]:
yield {
"profile_id": "xgrids-k1-fw3-quick-connect-ap-v1",
"started_at_utc": "2026-08-06T10:00:00Z",
"completed_at_utc": "2026-08-06T10:00:01Z",
"outcome": "ap_ready_observed",
"ready_observed": True,
"write_performed": True,
"write_mode": "with_response",
"observations": [{"status": {"mode": "WIFI_AP"}}],
}
def failed_post_write_build(*_: object, **__: object) -> dict[str, Any]:
raise facade_module.HostWifiProfileError(
"host-wifi-helper-build-failed",
helper_stage="compile",
helper_elapsed_ms=21,
)
monkeypatch.setattr(facade_module, "device_ap_activation_session", ready_activation)
monkeypatch.setattr(
facade_module,
"associate_with_wifi_profile_once",
failed_post_write_build,
)
with pytest.raises(
facade_module.HostWifiProfileError,
match="host-wifi-helper-build-failed",
):
asyncio.run(
service.connect(
ConnectRequest(
device_id="k1-a",
connection_mode="quick-connect",
compatibility_attestation=QUICK_CONNECT_ATTESTATION,
)
)
)
state = service.state()
operation = next(item for item in state["operations"] if item["action"] == "network.provision")
assert operation["stage_code"] == "host-wifi-association-failed"
assert operation["error"]["code"] == "host-wifi-helper-build-failed"
assert operation["error"]["side_effect_status"] == "confirmed"
assert operation["error"]["safe_to_retry"] is False
assert operation["error"]["helper_stage"] == "compile"
assert state["network_write_reconciliation"] is None
def test_network_provisioning_rejects_an_ipv4_owned_by_the_local_host(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
@@ -3236,7 +3315,8 @@ def test_network_provisioning_rejects_an_ipv4_owned_by_the_local_host(
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"
assert operation["error"]["side_effect_status"] == "confirmed"
assert state["network_write_reconciliation"] is None
failure_log = next(
record
for record in caplog.records
@@ -3246,12 +3326,212 @@ def test_network_provisioning_rejects_an_ipv4_owned_by_the_local_host(
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.side_effect_status == "confirmed"
assert failure_log.network_change_attempted is True
assert failure_log.device_write_attempted is True
assert failure_log.device_write_confirmed is True
assert PRIMARY_TEST_CREDENTIAL not in caplog.text
assert "lab-network" not in caplog.text
def test_ambiguous_ble_write_blocks_new_network_mutation_until_reconciled(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
) -> None:
service, _ = service_with_fake_runtime(tmp_path)
_set_scanned_devices(service, [{"device_id": "k1-a"}])
calls = 0
async def ambiguous_write(*_: object, **__: object) -> dict[str, Any]:
nonlocal calls
calls += 1
exc = RuntimeError("synthetic transport failure")
exc.operation_stage = "gatt-write" # type: ignore[attr-defined]
exc.device_write_attempted = True # type: ignore[attr-defined]
exc.device_write_confirmed = False # type: ignore[attr-defined]
exc.att_error_code = 4 # type: ignore[attr-defined]
exc.att_error_name = "INVALID_PDU" # type: ignore[attr-defined]
raise exc
monkeypatch.setattr(facade_module, "provision_wifi_once", ambiguous_write)
with pytest.raises(RuntimeError, match="synthetic transport failure"):
asyncio.run(
service.connect(
ConnectRequest(
device_id="k1-a",
ssid="lab-network",
password=SecretStr(PRIMARY_TEST_CREDENTIAL),
compatibility_attestation=ATTESTATION,
idempotency_key="ambiguous-write-1",
)
)
)
state = service.state()
operation = next(item for item in state["operations"] if item["action"] == "network.provision")
assert operation["error"] == {
"category": "device",
"code": "RuntimeError",
"retryable": False,
"safe_to_retry": False,
"side_effect_status": "unknown",
"operation_stage": "gatt-write",
"device_write_attempted": True,
"device_write_confirmed": False,
"ble_att_error_code": 4,
"ble_att_error_name": "INVALID_PDU",
}
assert state["network_write_reconciliation"] == {
"status": "device-state-unknown-after-write",
"operation_id": operation["operation_id"],
"transport_ref": "k1-a",
"connection_mode": "bridge",
"operation_stage": "gatt-write",
"reason_code": "RuntimeError",
"device_write_confirmed": False,
"required_action": "explicit-read-only-ble-status-observation",
"scope": "process-runtime",
"observed_at": state["network_write_reconciliation"]["observed_at"],
}
with pytest.raises(
facade_module.NetworkWriteReconciliationRequired,
match="новая запись заблокирована",
):
asyncio.run(
service.connect(
ConnectRequest(
device_id="k1-a",
ssid="another-network",
password=SecretStr(SECONDARY_TEST_CREDENTIAL),
compatibility_attestation=ATTESTATION,
idempotency_key="ambiguous-write-2",
)
)
)
assert calls == 1
def test_unchanged_status_does_not_confirm_without_response_write(
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"}])
unchanged_status = {"ipv4": None, "mode": "WIFI_CLIENT"}
async def unchanged_write(*_: object, **__: object) -> dict[str, Any]:
return {
"started_at_utc": "2026-08-06T10:00:00Z",
"completed_at_utc": "2026-08-06T10:00:01Z",
"profile_id": "xgrids-k1-fw3-wifi-v1",
"outcome": "no_status_change_before_timeout",
"write_mode": "without_response",
"baseline_status": unchanged_status,
"observations": [{"status": unchanged_status}],
}
monkeypatch.setattr(facade_module, "provision_wifi_once", unchanged_write)
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,
)
)
)
record = next(
item
for item in caplog.records
if getattr(item, "event_code", None) == "k1_network_provision_failed"
)
assert record.device_write_attempted is True
assert record.device_write_confirmed is False
assert record.side_effect_status == "unknown"
assert service.state()["network_write_reconciliation"] is None
def test_read_only_ble_status_clears_network_write_reconciliation_fence(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
) -> None:
service, _ = service_with_fake_runtime(tmp_path)
_set_scanned_k1(service)
service._network_write_reconciliation = { # noqa: SLF001
"status": "device-state-unknown-after-write",
"operation_id": "ambiguous-operation",
"transport_ref": "test-ble-transport",
"connection_mode": "bridge",
"operation_stage": "gatt-write",
"reason_code": "BleakGATTProtocolError",
"device_write_confirmed": False,
"required_action": "explicit-read-only-ble-status-observation",
"scope": "process-runtime",
"observed_at": "2026-08-06T10:00:00Z",
}
async def read_current_status(*_: object, **__: object) -> dict[str, Any]:
return _wifi_status_read(None)
monkeypatch.setattr(facade_module, "read_wifi_status_once", read_current_status)
with pytest.raises(RuntimeError, match="не сообщил актуальный DHCP-адрес"):
service.verify_connection(
ConnectionVerifyRequest(
device_id="test-ble-transport",
compatibility_attestation=ATTESTATION,
)
)
assert service.state()["network_write_reconciliation"] is None
def test_read_only_ble_status_for_another_transport_keeps_reconciliation_fence(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
) -> None:
service, _ = service_with_fake_runtime(tmp_path)
_set_scanned_k1(service, device_id="k1-b")
fence = {
"status": "device-state-unknown-after-write",
"operation_id": "ambiguous-operation",
"transport_ref": "k1-a",
"connection_mode": "bridge",
"operation_stage": "gatt-write",
"reason_code": "BleakGATTProtocolError",
"device_write_confirmed": False,
"required_action": "explicit-read-only-ble-status-observation",
"scope": "process-runtime",
"observed_at": "2026-08-06T10:00:00Z",
}
service._network_write_reconciliation = dict(fence) # noqa: SLF001
async def read_other_status(*_: object, **__: object) -> dict[str, Any]:
return _wifi_status_read(None, device_id="k1-b")
monkeypatch.setattr(facade_module, "read_wifi_status_once", read_other_status)
with pytest.raises(RuntimeError, match="не сообщил актуальный DHCP-адрес"):
service.verify_connection(
ConnectionVerifyRequest(
device_id="k1-b",
compatibility_attestation=ATTESTATION,
)
)
assert service.state()["network_write_reconciliation"] == fence
def test_provisioning_cannot_switch_device_during_active_acquisition(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
+145 -16
View File
@@ -1,7 +1,10 @@
import asyncio
from collections.abc import Iterator
from types import SimpleNamespace
from typing import Any
import pytest
from bleak.exc import BleakDeviceNotFoundError
from bleak.exc import BleakDeviceNotFoundError, BleakGATTProtocolError
import k1link.device_plugins.xgrids_k1.ble.ap_activation as ap_module
import k1link.device_plugins.xgrids_k1.ble.scanner as scanner_module
@@ -15,6 +18,27 @@ from k1link.device_plugins.xgrids_k1.ble.ap_activation import (
from k1link.device_plugins.xgrids_k1.ble.wifi_provisioning import WifiStatus
@pytest.fixture(autouse=True)
def reset_runtime_handle_lease() -> Iterator[None]:
with scanner_module._runtime_handle_lock: # noqa: SLF001
scanner_module._runtime_handles.clear() # noqa: SLF001
scanner_module._runtime_handle_observed_at_monotonic = None # noqa: SLF001
scanner_module._runtime_handle_generation = 0 # noqa: SLF001
yield
with scanner_module._runtime_handle_lock: # noqa: SLF001
scanner_module._runtime_handles.clear() # noqa: SLF001
scanner_module._runtime_handle_observed_at_monotonic = None # noqa: SLF001
scanner_module._runtime_handle_generation = 0 # noqa: SLF001
def _seed_scan_lease(handles: dict[str, object], *, observed_at: float) -> None:
with scanner_module._runtime_handle_lock: # noqa: SLF001
scanner_module._runtime_handles.clear() # noqa: SLF001
scanner_module._runtime_handles.update(handles) # type: ignore[arg-type] # noqa: SLF001
scanner_module._runtime_handle_observed_at_monotonic = observed_at # noqa: SLF001
scanner_module._runtime_handle_generation += 1 # noqa: SLF001
def test_build_ap_activation_frame_matches_reviewed_lixelgo_layout() -> None:
frame = build_ap_activation_frame()
@@ -42,34 +66,36 @@ def test_ap_ready_requires_the_reviewed_byte_51_flag() -> None:
assert is_ap_ready_status(_status(reserved=1))
def test_ap_activation_requires_fresh_rediscovery_before_connecting(
def test_ap_activation_uses_retained_handle_without_rediscovery(
monkeypatch: pytest.MonkeyPatch,
) -> None:
device_id = "synthetic-corebluetooth-uuid"
stale_handle = object()
retained_handle = object()
rediscovery_calls: list[tuple[str, float]] = []
client_calls: list[object] = []
async def missing_device(address: str, *, timeout: float) -> None:
rediscovery_calls.append((address, timeout))
return None
class SelectedHandleObserved(RuntimeError):
pass
class ForbiddenClient:
async def forbidden_rediscovery(address: str, *, timeout: float) -> None:
rediscovery_calls.append((address, timeout))
raise AssertionError("a fresh explicit scan handle must be used directly")
class CapturingClient:
def __init__(self, device: object, **_kwargs: object) -> None:
client_calls.append(device)
raise AssertionError("a failed fresh discovery must stop before the BLE write session")
raise SelectedHandleObserved
# Seed the real retained-handle cache so the test fails if the mutating
# path ever regresses to discovered_device(...)-first behavior.
monkeypatch.setitem(scanner_module._runtime_handles, device_id, stale_handle) # noqa: SLF001
monkeypatch.setattr(scanner_module, "monotonic", lambda: 100.0)
_seed_scan_lease({device_id: retained_handle}, observed_at=100.0)
monkeypatch.setattr(
ap_module.BleakScanner,
"find_device_by_address",
missing_device,
forbidden_rediscovery,
)
monkeypatch.setattr(ap_module, "BleakClient", ForbiddenClient)
monkeypatch.setattr(ap_module, "BleakClient", CapturingClient)
with pytest.raises(BleakDeviceNotFoundError):
with pytest.raises(SelectedHandleObserved) as caught:
asyncio.run(
ap_module.activate_device_ap_once(
device_id,
@@ -77,5 +103,108 @@ def test_ap_activation_requires_fresh_rediscovery_before_connecting(
)
)
assert rediscovery_calls == [(device_id, 1.0)]
assert client_calls == []
assert rediscovery_calls == []
assert client_calls == [retained_handle]
assert caught.value.operation_stage == "connect" # type: ignore[attr-defined]
assert caught.value.device_write_attempted is False # type: ignore[attr-defined]
assert caught.value.device_write_confirmed is False # type: ignore[attr-defined]
def test_ap_activation_does_not_fallback_when_fresh_scan_omits_device(
monkeypatch: pytest.MonkeyPatch,
) -> None:
rediscovery_calls: list[tuple[str, float]] = []
async def forbidden_rediscovery(address: str, *, timeout: float) -> None:
rediscovery_calls.append((address, timeout))
return None
monkeypatch.setattr(scanner_module, "monotonic", lambda: 100.0)
_seed_scan_lease({}, observed_at=100.0)
monkeypatch.setattr(
ap_module.BleakScanner,
"find_device_by_address",
forbidden_rediscovery,
)
with pytest.raises(BleakDeviceNotFoundError) as caught:
asyncio.run(
ap_module.activate_device_ap_once(
"not-in-fresh-scan",
timeout_seconds=1.0,
)
)
assert rediscovery_calls == []
assert caught.value.operation_stage == "resolution" # type: ignore[attr-defined]
assert caught.value.device_write_attempted is False # type: ignore[attr-defined]
assert caught.value.device_write_confirmed is False # type: ignore[attr-defined]
def test_ap_activation_write_error_keeps_type_and_adds_safe_gatt_facts(
monkeypatch: pytest.MonkeyPatch,
) -> None:
device_id = "synthetic-corebluetooth-uuid"
retained_handle = object()
service = SimpleNamespace(uuid=ap_module.SERVICE_UUID)
write_characteristic = SimpleNamespace(
uuid=ap_module.WRITE_CHARACTERISTIC_UUID,
service_uuid=ap_module.SERVICE_UUID,
properties=["write"],
max_write_without_response_size=512,
)
status_characteristic = SimpleNamespace(
uuid=ap_module.STATUS_CHARACTERISTIC_UUID,
service_uuid=ap_module.SERVICE_UUID,
properties=["read"],
)
baseline = bytearray(52)
class FakeServices:
def get_service(self, uuid: str) -> object | None:
return service if uuid == ap_module.SERVICE_UUID else None
def get_characteristic(self, uuid: str) -> object | None:
if uuid == ap_module.WRITE_CHARACTERISTIC_UUID:
return write_characteristic
if uuid == ap_module.STATUS_CHARACTERISTIC_UUID:
return status_characteristic
return None
class FailingWriteClient:
def __init__(self, device: object, **_kwargs: object) -> None:
assert device is retained_handle
self.services = FakeServices()
self.name = "XGR-K1"
self.is_connected = True
async def __aenter__(self) -> Any:
return self
async def __aexit__(self, *_args: object) -> None:
return None
async def read_gatt_char(self, _characteristic: object) -> bytes:
return bytes(baseline)
async def write_gatt_char(self, *_args: object, **_kwargs: object) -> None:
raise BleakGATTProtocolError(0x03)
monkeypatch.setattr(scanner_module, "monotonic", lambda: 100.0)
_seed_scan_lease({device_id: retained_handle}, observed_at=100.0)
monkeypatch.setattr(ap_module, "BleakClient", FailingWriteClient)
with pytest.raises(BleakGATTProtocolError) as caught:
asyncio.run(
ap_module.activate_device_ap_once(
device_id,
timeout_seconds=1.0,
)
)
error = caught.value
assert error.operation_stage == "gatt-write" # type: ignore[attr-defined]
assert error.device_write_attempted is True # type: ignore[attr-defined]
assert error.device_write_confirmed is False # type: ignore[attr-defined]
assert error.att_error_code == 0x03 # type: ignore[attr-defined]
assert error.att_error_name == "WRITE_NOT_PERMITTED" # type: ignore[attr-defined]
+278 -3
View File
@@ -1,6 +1,8 @@
from __future__ import annotations
import hashlib
import json
import os
import subprocess
from pathlib import Path
@@ -13,9 +15,14 @@ TEST_PROFILE_ID = "fixture.quick-connect.v1"
TEST_CREDENTIAL_SOURCE_ID = "fixture.firmware-provider.v1"
def _helper(tmp_path: Path) -> Path:
def _helper(tmp_path: Path, *, seed_compiled_cache: bool = True) -> Path:
helper = tmp_path / "associate_wifi.swift"
helper.write_text("// offline fixture\n", encoding="utf-8")
if seed_compiled_cache:
executable = wifi._compiled_macos_helper_path(helper)
executable.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
executable.write_bytes(b"offline compiled fixture\n")
executable.chmod(0o700)
return helper
@@ -42,8 +49,9 @@ def test_association_exposes_only_profile_id_and_expected_ssid_to_platform_helpe
stderr=b"",
)
helper = _helper(tmp_path)
result = wifi.associate_with_wifi_profile_once(
_helper(tmp_path),
helper,
TEST_PROFILE_ID,
"XGR-OFFLINE",
runner=fake_runner,
@@ -61,7 +69,7 @@ def test_association_exposes_only_profile_id_and_expected_ssid_to_platform_helpe
}
assert len(calls) == 1
call = calls[0]
assert call["argv"][:2] == ["/usr/bin/xcrun", "swift"]
assert call["argv"] == [str(wifi._compiled_macos_helper_path(helper))]
request = json.loads(bytes(call["input"]).decode("utf-8"))
assert request == {
"action": "associate",
@@ -290,6 +298,8 @@ def test_association_reports_operator_timeout_separately_from_missing_helper(
)
assert raised.value.reason_code == "host-wifi-operation-timeout"
assert raised.value.helper_stage is None
assert raised.value.helper_elapsed_ms is None
def test_association_reports_an_unavailable_helper_separately_from_timeout(
@@ -317,6 +327,271 @@ def test_association_reports_an_unavailable_helper_separately_from_timeout(
assert raised.value.reason_code == "host-wifi-helper-unavailable"
def test_cold_helper_build_uses_source_hash_and_separate_timeout(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
) -> None:
monkeypatch.setattr(wifi.sys, "platform", "darwin")
helper = _helper(tmp_path, seed_compiled_cache=False)
executable = wifi._compiled_macos_helper_path(helper)
calls: list[dict[str, object]] = []
def fake_runner(argv: list[str], **kwargs: object) -> subprocess.CompletedProcess[bytes]:
calls.append({"argv": argv, **kwargs})
if argv[:2] == ["/usr/bin/xcrun", "swiftc"]:
assert argv[2] == str(helper.resolve())
assert argv[3] == "-o"
assert kwargs["stdin"] == subprocess.DEVNULL
assert "input" not in kwargs
assert 0 < float(kwargs["timeout"]) <= wifi.DEFAULT_HELPER_BUILD_TIMEOUT_SECONDS
staging = Path(argv[4])
staging.write_bytes(b"compiled fixture\n")
staging.chmod(0o700)
return subprocess.CompletedProcess(argv, 0, stdout=b"", stderr=b"")
assert argv == [str(executable)]
assert kwargs["timeout"] == 30.0
return subprocess.CompletedProcess(
argv,
0,
stdout=(
b'{"ok":true,"adapter":"macOS Keychain",'
b'"profile_available":true}'
),
stderr=b"",
)
result = wifi.check_wifi_profile(
helper,
TEST_PROFILE_ID,
"XGR-OFFLINE",
runner=fake_runner,
)
assert result["available"] is True
assert len(calls) == 2
assert calls[0]["argv"][:2] == ["/usr/bin/xcrun", "swiftc"]
assert calls[1]["argv"] == [str(executable)]
assert executable.is_file()
assert executable.stat().st_mode & 0o111
assert executable.name.endswith(hashlib.sha256(helper.read_bytes()).hexdigest())
assert not list(executable.parent.glob(f".{executable.name}.*.tmp"))
def test_plugin_helper_cache_is_stable_under_repository_runtime(tmp_path: Path) -> None:
helper = tmp_path / "repo" / "plugins" / "xgrids-k1" / "macos" / "associate_wifi.swift"
helper.parent.mkdir(parents=True)
helper.write_text("// offline fixture\n", encoding="utf-8")
assert wifi._helper_cache_directory(helper) == (
tmp_path / "repo" / ".runtime" / "mission-core" / "helpers"
)
def test_helper_lock_and_compile_share_one_build_deadline(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
) -> None:
monkeypatch.setattr(wifi.sys, "platform", "darwin")
helper = _helper(tmp_path, seed_compiled_cache=False)
monotonic_values = iter((100.0, 100.0, 104.0))
monkeypatch.setattr(wifi.time, "monotonic", lambda: next(monotonic_values))
monkeypatch.setattr(wifi.time, "monotonic_ns", lambda: 0)
lock_timeouts: list[float] = []
compile_timeouts: list[float] = []
class FakeLock:
def __enter__(self) -> None:
return None
def __exit__(self, *_args: object) -> None:
return None
def fake_lock(_path: Path, *, timeout_seconds: float) -> FakeLock:
lock_timeouts.append(timeout_seconds)
return FakeLock()
def fake_runner(argv: list[str], **kwargs: object) -> subprocess.CompletedProcess[bytes]:
if argv[:2] == ["/usr/bin/xcrun", "swiftc"]:
compile_timeouts.append(float(kwargs["timeout"]))
staging = Path(argv[4])
staging.write_bytes(b"compiled fixture\n")
staging.chmod(0o700)
return subprocess.CompletedProcess(argv, 0, stdout=b"", stderr=b"")
return subprocess.CompletedProcess(
argv,
0,
stdout=b'{"ok":true,"adapter":"macOS Keychain","profile_available":true}',
stderr=b"",
)
monkeypatch.setattr(wifi, "_exclusive_helper_build_lock", fake_lock)
result = wifi._run_macos_helper(
helper,
{"action": "check-profile"},
timeout_seconds=3.0,
build_timeout_seconds=10.0,
runner=fake_runner,
)
assert result["ok"] is True
assert lock_timeouts == [10.0]
assert compile_timeouts == [6.0]
def test_compiled_helper_is_reused_without_recompiling(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
) -> None:
monkeypatch.setattr(wifi.sys, "platform", "darwin")
helper = _helper(tmp_path, seed_compiled_cache=False)
executable = wifi._compiled_macos_helper_path(helper)
compile_count = 0
runtime_count = 0
def fake_runner(argv: list[str], **_: object) -> subprocess.CompletedProcess[bytes]:
nonlocal compile_count, runtime_count
if argv[:2] == ["/usr/bin/xcrun", "swiftc"]:
compile_count += 1
staging = Path(argv[4])
staging.write_bytes(b"compiled fixture\n")
staging.chmod(0o700)
return subprocess.CompletedProcess(argv, 0, stdout=b"", stderr=b"")
runtime_count += 1
assert argv == [str(executable)]
return subprocess.CompletedProcess(
argv,
0,
stdout=(
b'{"ok":true,"adapter":"macOS Keychain",'
b'"profile_available":true}'
),
stderr=b"",
)
for _ in range(2):
wifi.check_wifi_profile(
helper,
TEST_PROFILE_ID,
"XGR-OFFLINE",
runner=fake_runner,
)
assert compile_count == 1
assert runtime_count == 2
def test_helper_build_timeout_is_separate_from_operation_timeout(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
) -> None:
monkeypatch.setattr(wifi.sys, "platform", "darwin")
helper = _helper(tmp_path, seed_compiled_cache=False)
seen_timeout: float | None = None
monotonic_values = iter((1_000_000_000, 1_123_000_000))
monkeypatch.setattr(wifi.time, "monotonic_ns", lambda: next(monotonic_values))
def timed_out_compiler(
argv: list[str], **kwargs: object
) -> subprocess.CompletedProcess[bytes]:
nonlocal seen_timeout
assert argv[:2] == ["/usr/bin/xcrun", "swiftc"]
seen_timeout = float(kwargs["timeout"])
raise subprocess.TimeoutExpired(argv, timeout=seen_timeout)
with pytest.raises(
wifi.HostWifiProfileError,
match="host-wifi-helper-build-timeout",
) as raised:
wifi._run_macos_helper(
helper,
{"action": "check-profile"},
timeout_seconds=3.0,
build_timeout_seconds=7.5,
runner=timed_out_compiler,
)
assert raised.value.reason_code == "host-wifi-helper-build-timeout"
assert raised.value.helper_stage == "compile"
assert raised.value.helper_elapsed_ms == 123
assert seen_timeout is not None
assert 0 < seen_timeout <= 7.5
assert not wifi._compiled_macos_helper_path(helper).exists()
def test_helper_build_lock_contention_is_bounded(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
) -> None:
fcntl = pytest.importorskip("fcntl")
lock_path = tmp_path / "helper.lock"
descriptor = os.open(lock_path, os.O_RDWR | os.O_CREAT, 0o600)
fcntl.flock(descriptor, fcntl.LOCK_EX)
monotonic_values = iter((100.0, 100.02))
monkeypatch.setattr(wifi.time, "monotonic", lambda: next(monotonic_values))
try:
with (
pytest.raises(wifi.HostWifiProfileError) as raised,
wifi._exclusive_helper_build_lock(
lock_path,
timeout_seconds=0.01,
),
):
raise AssertionError("contended lock must not be acquired")
finally:
fcntl.flock(descriptor, fcntl.LOCK_UN)
os.close(descriptor)
assert raised.value.reason_code == "host-wifi-helper-build-timeout"
assert raised.value.helper_stage == "compile-lock"
assert raised.value.helper_elapsed_ms == 19
@pytest.mark.parametrize(
("compiler_failure", "expected_reason_code"),
[
("exit", "host-wifi-helper-build-failed"),
("unavailable", "host-wifi-helper-compiler-unavailable"),
],
)
def test_helper_build_errors_have_sanitized_build_taxonomy(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
compiler_failure: str,
expected_reason_code: str,
) -> None:
monkeypatch.setattr(wifi.sys, "platform", "darwin")
helper = _helper(tmp_path, seed_compiled_cache=False)
def failing_compiler(
argv: list[str], **_: object
) -> subprocess.CompletedProcess[bytes]:
assert argv[:2] == ["/usr/bin/xcrun", "swiftc"]
if compiler_failure == "unavailable":
raise OSError(f"private diagnostic {TEST_PASSWORD}")
return subprocess.CompletedProcess(
argv,
1,
stdout=b"",
stderr=f"private diagnostic {TEST_PASSWORD}".encode(),
)
with pytest.raises(wifi.HostWifiProfileError) as raised:
wifi.check_wifi_profile(
helper,
TEST_PROFILE_ID,
"XGR-OFFLINE",
runner=failing_compiler,
)
assert raised.value.reason_code == expected_reason_code
assert raised.value.helper_stage == "compile"
assert isinstance(raised.value.helper_elapsed_ms, int)
assert raised.value.helper_elapsed_ms >= 0
assert TEST_PASSWORD not in str(raised.value)
def test_association_rejects_an_uninstalled_platform(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,