from __future__ import annotations import json import logging import stat from collections.abc import Callable from pathlib import Path from typing import Any import pytest from fastapi import APIRouter from fastapi.routing import APIRoute from pydantic import ValidationError from k1link.web.runtime_diagnostics import ( SCANNER_LOGGER_NAME, configure_scanner_diagnostics, ) from k1link.web.viewer_diagnostics_api import ( LiveViewerDiagnosticEvent, build_viewer_diagnostics_router, ) def _endpoint(router: APIRouter, path: str, method: str) -> Callable[..., Any]: for route in router.routes: if ( isinstance(route, APIRoute) and route.path == path and route.methods is not None and method in route.methods ): return route.endpoint raise AssertionError(f"{method} {path} route is missing") def test_private_scanner_diagnostics_are_durable_structured_and_bounded( tmp_path: Path, ) -> None: target = configure_scanner_diagnostics(tmp_path / "logs") logger = logging.getLogger(f"{SCANNER_LOGGER_NAME}.test") logger.error( "field control failure", extra={ "event_code": "k1_application_control_session_failed", "operation_id": "operation-test-01", "operation_stage": "ble-provisioning-write", "connection_mode": "bridge", "error_category": "device", "error_code": "BleakGATTProtocolError", "reason_code": "mqtt_network_loop_failed", "mqtt_loop_result_code": 7, "mqtt_loop_result_name": "The connection was lost.", "mqtt_loop_phase": "post-publish-drain", "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", "network_change_admissible": True, "network_change_reconciliation": ( "explicit-network-change-only-after-acknowledged-stop" ), "lease_generation": 3, "lease_state": "reachable", "recovery_strategy": "existing-mqtt-endpoint", "endpoint_reachable": True, "address_changed": False, "device_write_performed": False, "preferred_port": 9876, "selected_port": 9877, "unapproved_secret_field": "must-not-be-written", }, ) for handler in logging.getLogger(SCANNER_LOGGER_NAME).handlers: handler.flush() document = json.loads(target.read_text(encoding="utf-8").splitlines()[-1]) assert stat.S_IMODE(target.parent.stat().st_mode) == 0o700 assert stat.S_IMODE(target.stat().st_mode) == 0o600 assert document["event_code"] == "k1_application_control_session_failed" assert document["operation_id"] == "operation-test-01" assert document["operation_stage"] == "ble-provisioning-write" assert document["connection_mode"] == "bridge" assert document["error_category"] == "device" assert document["error_code"] == "BleakGATTProtocolError" assert document["reason_code"] == "mqtt_network_loop_failed" assert document["mqtt_loop_result_code"] == 7 assert document["mqtt_loop_phase"] == "post-publish-drain" 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" assert document["network_change_admissible"] is True assert document["network_change_reconciliation"] == ( "explicit-network-change-only-after-acknowledged-stop" ) assert document["lease_generation"] == 3 assert document["lease_state"] == "reachable" assert document["recovery_strategy"] == "existing-mqtt-endpoint" assert document["endpoint_reachable"] is True assert document["address_changed"] is False assert document["device_write_performed"] is False assert document["preferred_port"] == 9876 assert document["selected_port"] == 9877 assert "unapproved_secret_field" not in document parent = logging.getLogger(SCANNER_LOGGER_NAME) for handler in list(parent.handlers): if getattr(handler, "baseFilename", None) == str(target): parent.removeHandler(handler) handler.close() def test_live_viewer_diagnostic_endpoint_accepts_only_bounded_events( caplog: pytest.LogCaptureFixture, ) -> None: router = build_viewer_diagnostics_router() endpoint = _endpoint(router, "/api/v1/viewer/live-diagnostics", "POST") event = LiveViewerDiagnosticEvent( schema_version="missioncore.live-viewer-diagnostic/v1", event_code="live_receiver_stalled", failure_stage="receiver-stalled", stream_id="acquisition-123", backend_activity_sequence=8_572, viewer_range_max_ns=231_000_000_000, stalled_for_ms=5_500, recovery_attempt=1, ) with caplog.at_level( logging.INFO, logger="k1link.device_plugins.xgrids_k1.viewer_receiver", ): response = endpoint(event) assert response.status_code == 204 assert "event=live_receiver_stalled" in caplog.text assert caplog.records[-1].failure_stage == "receiver-stalled" with pytest.raises(ValidationError): LiveViewerDiagnosticEvent.model_validate( { **event.model_dump(), "source_url": "http://192.168.56.1/private", } ) fallback = LiveViewerDiagnosticEvent( schema_version="missioncore.live-viewer-diagnostic/v1", event_code="live_receiver_active_store_admitted", stream_id="acquisition-123", backend_activity_sequence=8_573, ) assert fallback.failure_stage is None