import 'dart:async'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:ground_suite/core/observations/observed_event.dart'; import 'package:ground_suite/core/observations/vehicle_data_port.dart'; import 'package:ground_suite/core/observations/vehicle_data_update.dart'; import 'package:ground_suite/features/observability/presentation/observability_controller.dart'; void main() { test('coalesces rapid critical accessibility announcements', () async { final port = _ControlledDataPort(); final container = ProviderContainer( overrides: [vehicleDataPortProvider.overrideWithValue(port)], ); addTearDown(container.dispose); container.read(observabilityControllerProvider); await Future.delayed(Duration.zero); port.events.add( ObservationReceived( ErrorEvent( receivedAt: DateTime.utc(2026, 8, 10), path: 'kaptan_drivers::lidar_lite_v3::error::Error::TransferFault', ), ), ); await Future.delayed(Duration.zero); expect( container.read(observabilityControllerProvider).accessibilityAnnouncement, 'Error received: kaptan_drivers::lidar_lite_v3::error::Error::TransferFault', ); port.events.add( ObservationReceived( ShutdownEvent( receivedAt: DateTime.utc(2026, 8, 10, 0, 0, 1), reason: KaptanShutdownReason.safety, ), ), ); await Future.delayed(Duration.zero); final state = container.read(observabilityControllerProvider); expect(state.accessibilityAnnouncement, isNull); expect(state.coalescedAlertCount, 1); }); test( 'retains expected parser and semantic drops without ending observation', () async { final port = _ControlledDataPort(); final container = ProviderContainer( overrides: [vehicleDataPortProvider.overrideWithValue(port)], ); addTearDown(container.dispose); container.read(observabilityControllerProvider); await Future.delayed(Duration.zero); port.events.add( const ObservationDropped(kind: ObservationDropKind.parser, count: 2), ); port.events.add( const ObservationDropped(kind: ObservationDropKind.semantic), ); await Future.delayed(Duration.zero); final state = container.read(observabilityControllerProvider); expect(state.status, ObservationStatus.receiving); expect(state.parserDropCount, 2); expect(state.semanticDropCount, 1); }, ); test('treats a transport stream failure as a disconnected link', () async { final port = _ControlledDataPort(); final container = ProviderContainer( overrides: [vehicleDataPortProvider.overrideWithValue(port)], ); addTearDown(container.dispose); container.read(observabilityControllerProvider); await Future.delayed(Duration.zero); port.events.addError(StateError('serial endpoint closed')); await Future.delayed(Duration.zero); final state = container.read(observabilityControllerProvider); expect(state.status, ObservationStatus.disconnected); expect(state.parserDropCount, 0); expect(state.semanticDropCount, 0); }); test( 'bounds diagnostics while telemetry stays outside the timeline', () async { final port = _ControlledDataPort(); final container = ProviderContainer( overrides: [vehicleDataPortProvider.overrideWithValue(port)], ); addTearDown(container.dispose); container.read(observabilityControllerProvider); await Future.delayed(Duration.zero); port.events.add( ObservationReceived( ErrorEvent( receivedAt: DateTime.utc(2026, 8, 10), path: 'kaptan_drivers::lidar_lite_v3::error::Error::TransferFault', ), ), ); port.events.add( ObservationReceived( HeartbeatEvent( receivedAt: DateTime.utc(2026, 8, 10, 0, 0, 1), systemId: 7, componentId: 42, armed: false, vehicleState: 'Standby', ), ), ); for (var index = 0; index < 501; index++) { port.events.add( ObservationReceived( TelemetryEvent( receivedAt: DateTime.utc(2026, 8, 10, 0, 0, index % 60), kind: TelemetryKind.rawImu, ), ), ); } for (var index = 0; index < 500; index++) { port.events.add( ObservationReceived( LogEvent( receivedAt: DateTime.utc(2026, 8, 10, 0, 1, index % 60), level: MavSeverity.info, message: 'Diagnostic $index', ), ), ); } final state = container.read(observabilityControllerProvider); expect(state.events, hasLength(500)); expect(state.localTimelineEvictionCount, 1); expect(state.events.whereType(), hasLength(1)); expect(state.lastHeartbeat?.systemId, 7); expect(state.observedTelemetryKinds, contains(TelemetryKind.rawImu)); }, ); } class _ControlledDataPort implements VehicleDataPort { final events = StreamController(sync: true); @override bool get isReplay => false; @override String get label => 'Test transport'; @override Future close() => events.close(); @override Stream observe() => events.stream; }