fix(k1): harden live handoff and camera recovery
This commit is contained in:
@@ -11,6 +11,7 @@ import pytest
|
||||
from fastapi import APIRouter
|
||||
from fastapi.routing import APIRoute
|
||||
from pydantic import ValidationError
|
||||
from starlette.requests import Request
|
||||
|
||||
from k1link.web.runtime_diagnostics import (
|
||||
SCANNER_LOGGER_NAME,
|
||||
@@ -34,6 +35,13 @@ def _endpoint(router: APIRouter, path: str, method: str) -> Callable[..., Any]:
|
||||
raise AssertionError(f"{method} {path} route is missing")
|
||||
|
||||
|
||||
def _request(*, ui_build_id: str | None = None) -> Request:
|
||||
headers = []
|
||||
if ui_build_id is not None:
|
||||
headers.append((b"x-missioncore-ui-build", ui_build_id.encode("ascii")))
|
||||
return Request({"type": "http", "method": "GET", "path": "/", "headers": headers})
|
||||
|
||||
|
||||
def test_private_scanner_diagnostics_are_durable_structured_and_bounded(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
@@ -76,6 +84,11 @@ def test_private_scanner_diagnostics_are_durable_structured_and_bounded(
|
||||
"device_write_performed": False,
|
||||
"preferred_port": 9876,
|
||||
"selected_port": 9877,
|
||||
"camera_queue_bytes": 12_000_000,
|
||||
"camera_queue_segments": 96,
|
||||
"camera_retry_count": 3,
|
||||
"websocket_close_code": 4_008,
|
||||
"transport_epoch": 7,
|
||||
"unapproved_secret_field": "must-not-be-written",
|
||||
},
|
||||
)
|
||||
@@ -118,6 +131,11 @@ def test_private_scanner_diagnostics_are_durable_structured_and_bounded(
|
||||
assert document["device_write_performed"] is False
|
||||
assert document["preferred_port"] == 9876
|
||||
assert document["selected_port"] == 9877
|
||||
assert document["camera_queue_bytes"] == 12_000_000
|
||||
assert document["camera_queue_segments"] == 96
|
||||
assert document["camera_retry_count"] == 3
|
||||
assert document["websocket_close_code"] == 4_008
|
||||
assert document["transport_epoch"] == 7
|
||||
assert "unapproved_secret_field" not in document
|
||||
|
||||
parent = logging.getLogger(SCANNER_LOGGER_NAME)
|
||||
@@ -180,6 +198,40 @@ def test_live_viewer_diagnostic_endpoint_accepts_only_bounded_events(
|
||||
)
|
||||
assert fallback.failure_stage is None
|
||||
|
||||
camera_restart = LiveViewerDiagnosticEvent(
|
||||
schema_version="missioncore.live-viewer-diagnostic/v2",
|
||||
event_code="live_camera_transport_restart_requested",
|
||||
ui_build_id=expected_build,
|
||||
document_instance_id="00000000-0000-4000-8000-000000000001",
|
||||
viewer_instance_id="00000000-0000-4000-8000-000000000003",
|
||||
lifecycle_generation=2,
|
||||
failure_stage="camera-queue-capacity",
|
||||
stream_id="camera-preview-2",
|
||||
camera_queue_bytes=12_000_000,
|
||||
camera_queue_segments=96,
|
||||
camera_retry_count=3,
|
||||
websocket_close_code=4_008,
|
||||
transport_epoch=7,
|
||||
camera_append_error_name="InvalidStateError",
|
||||
camera_media_source_state="open",
|
||||
camera_video_error_code=3,
|
||||
)
|
||||
with caplog.at_level(
|
||||
logging.INFO,
|
||||
logger="k1link.device_plugins.xgrids_k1.viewer_receiver",
|
||||
):
|
||||
camera_response = endpoint(camera_restart)
|
||||
assert camera_response.status_code == 204
|
||||
assert caplog.records[-1].failure_stage == "camera-queue-capacity"
|
||||
assert caplog.records[-1].camera_queue_bytes == 12_000_000
|
||||
assert caplog.records[-1].camera_queue_segments == 96
|
||||
assert caplog.records[-1].camera_retry_count == 3
|
||||
assert caplog.records[-1].websocket_close_code == 4_008
|
||||
assert caplog.records[-1].transport_epoch == 7
|
||||
assert caplog.records[-1].camera_append_error_name == "InvalidStateError"
|
||||
assert caplog.records[-1].camera_media_source_state == "open"
|
||||
assert caplog.records[-1].camera_video_error_code == 3
|
||||
|
||||
|
||||
def test_live_viewer_diagnostic_rejects_stale_build_before_logging(
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
@@ -212,7 +264,7 @@ def test_live_viewer_client_contract_is_no_store_and_exact_build() -> None:
|
||||
router = build_viewer_diagnostics_router(expected_ui_build_id=lambda: expected_build)
|
||||
endpoint = _endpoint(router, "/api/v1/viewer/client-contract", "GET")
|
||||
|
||||
response = endpoint()
|
||||
response = endpoint(_request(ui_build_id=expected_build))
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.headers["cache-control"] == "no-store"
|
||||
@@ -225,11 +277,33 @@ def test_live_viewer_client_contract_is_no_store_and_exact_build() -> None:
|
||||
}
|
||||
|
||||
|
||||
def test_live_viewer_client_contract_logs_suppressed_stale_build_reload(
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
loaded_build = "/assets/index-abcdefgh.js"
|
||||
expected_build = "/assets/index-ijklmnop.js"
|
||||
router = build_viewer_diagnostics_router(expected_ui_build_id=lambda: expected_build)
|
||||
endpoint = _endpoint(router, "/api/v1/viewer/client-contract", "GET")
|
||||
|
||||
with caplog.at_level(
|
||||
logging.INFO,
|
||||
logger="k1link.device_plugins.xgrids_k1.viewer_receiver",
|
||||
):
|
||||
response = endpoint(_request(ui_build_id=loaded_build))
|
||||
|
||||
assert response.status_code == 200
|
||||
assert caplog.records[-1].event_code == "ui_build_drift_reload_suppressed"
|
||||
assert caplog.records[-1].ui_build_id == loaded_build
|
||||
assert caplog.records[-1].expected_ui_build_id == expected_build
|
||||
assert caplog.records[-1].device_write_performed is False
|
||||
assert caplog.records[-1].automatic_retry is False
|
||||
|
||||
|
||||
def test_live_viewer_client_contract_no_dist_is_retryable_without_reload_header() -> None:
|
||||
router = build_viewer_diagnostics_router(expected_ui_build_id=lambda: None)
|
||||
endpoint = _endpoint(router, "/api/v1/viewer/client-contract", "GET")
|
||||
|
||||
response = endpoint()
|
||||
response = endpoint(_request())
|
||||
|
||||
assert response.status_code == 503
|
||||
assert response.headers["cache-control"] == "no-store"
|
||||
|
||||
Reference in New Issue
Block a user