175 lines
5.4 KiB
Dart
175 lines
5.4 KiB
Dart
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<void>.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<void>.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<void>.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<void>.delayed(Duration.zero);
|
|
|
|
port.events.add(
|
|
const ObservationDropped(kind: ObservationDropKind.parser, count: 2),
|
|
);
|
|
port.events.add(
|
|
const ObservationDropped(kind: ObservationDropKind.semantic),
|
|
);
|
|
await Future<void>.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<void>.delayed(Duration.zero);
|
|
|
|
port.events.addError(StateError('serial endpoint closed'));
|
|
await Future<void>.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<void>.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<ErrorEvent>(), hasLength(1));
|
|
expect(state.lastHeartbeat?.systemId, 7);
|
|
expect(state.observedTelemetryKinds, contains(TelemetryKind.rawImu));
|
|
},
|
|
);
|
|
}
|
|
|
|
class _ControlledDataPort implements VehicleDataPort {
|
|
final events = StreamController<VehicleDataUpdate>(sync: true);
|
|
|
|
@override
|
|
bool get isReplay => false;
|
|
|
|
@override
|
|
String get label => 'Test transport';
|
|
|
|
@override
|
|
Future<void> close() => events.close();
|
|
|
|
@override
|
|
Stream<VehicleDataUpdate> observe() => events.stream;
|
|
}
|