import 'package:flutter_test/flutter_test.dart'; import 'package:drift/native.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:motorcycle_dash/database/database.dart' hide TelemetrySample; import 'package:motorcycle_dash/models/field_test_event.dart'; import 'package:motorcycle_dash/services/field_test/field_test_logger.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); late AppDatabase db; late FieldTestLogger logger; setUp(() async { SharedPreferences.setMockInitialValues({}); db = AppDatabase.forTesting(NativeDatabase.memory()); logger = FieldTestLogger(db); }); tearDown(() async { logger.dispose(); await db.close(); }); group('Field Test Logger API Tests', () { test('Session lifecycle starts and stops cleanly', () async { logger.startSession(10); expect(logger.isSessionActive, isTrue); expect(logger.activeRideId, 10); expect( logger.timelineEvents.any( (e) => e.eventType == FieldTestEventType.rideStarted, ), isTrue, ); final summary = await logger.stopSession(); expect(logger.isSessionActive, isFalse); expect(summary.rideId, 10); }); test( 'Queue drops lowest-priority events when buffer capacity is reached', () { logger.startSession(11); // Fill queue with 1,000 info events for (int i = 0; i < 1005; i++) { logger.log( eventType: FieldTestEventType.databaseFlush, severity: EventSeverity.info, message: 'Flush event $i', rideId: 11, ); } // Add a critical event logger.log( eventType: FieldTestEventType.databaseRecovery, severity: EventSeverity.critical, message: 'Critical DB Recovery', rideId: 11, ); expect(logger.droppedEventsCount, greaterThanOrEqualTo(5)); expect( logger.timelineEvents.any( (e) => e.severity == EventSeverity.critical, ), isTrue, ); }, ); test( 'Dropped diagnostic events alone do not inflate databaseSamplesLost', () async { logger.startSession(12); // Saturate diagnostic event queue so diagnostic events are dropped for (int i = 0; i < 1010; i++) { logger.log( eventType: FieldTestEventType.databaseFlush, severity: EventSeverity.info, message: 'Flush info event $i', rideId: 12, ); } expect(logger.droppedEventsCount, greaterThanOrEqualTo(10)); // Zero telemetry lost recorded expect(logger.telemetrySamplesLost, 0); final summary = await logger.stopSession(); // databaseSamplesLost MUST be 0 despite droppedEventsCount > 0 expect(summary.databaseSamplesLost, 0); }, ); test( 'Recorded telemetry loss is preserved across diagnostic event drops and reflected in summary', () async { logger.startSession(13); // Record 101 telemetry drops logger.recordTelemetryLoss(101); expect(logger.telemetrySamplesLost, 101); // Saturate diagnostic event queue with warning burst for (int i = 0; i < 1020; i++) { logger.log( eventType: FieldTestEventType.databaseQueueOverflow, severity: EventSeverity.warning, message: 'Overflow warning $i', rideId: 13, ); } expect(logger.droppedEventsCount, greaterThanOrEqualTo(20)); // Telemetry loss tally must NOT be corrupted by diagnostic event drops expect(logger.telemetrySamplesLost, 101); final summary = await logger.stopSession(); expect(summary.databaseSamplesLost, 101); }, ); test('Starting a new session resets telemetry loss tally', () async { logger.startSession(14); logger.recordTelemetryLoss(55); expect(logger.telemetrySamplesLost, 55); final summary1 = await logger.stopSession(); expect(summary1.databaseSamplesLost, 55); // Start new session #15 logger.startSession(15); expect(logger.telemetrySamplesLost, 0); final summary2 = await logger.stopSession(); expect(summary2.databaseSamplesLost, 0); }); }); }