import 'package:flutter_test/flutter_test.dart'; import 'package:drift/native.dart'; import 'package:drift/drift.dart' as drift; import 'package:motorcycle_dash/database/database.dart'; void main() { group('Phase 9 Forensic Validation: Persistence, Database & Export Integrity', () { late AppDatabase db; setUp(() { db = AppDatabase.forTesting(NativeDatabase.memory()); }); tearDown(() async { await db.close(); }); test('Telemetry samples batch insert and query accurately in chronological order', () async { final now = DateTime(2026, 8, 28, 12, 0, 0); final rideId = await db.into(db.rides).insert(RidesCompanion.insert( startTime: now, )); final batchSamples = List.generate(50, (i) { return TelemetrySamplesCompanion( rideId: drift.Value(rideId), timestamp: drift.Value(now.add(Duration(milliseconds: i * 50))), latitude: drift.Value(37.7749 + (i * 0.0001)), longitude: drift.Value(-122.4194), speed: drift.Value(45.0 + (i * 0.2)), heading: drift.Value(90.0), gpsAccuracy: drift.Value(3.2), leanAngle: drift.Value(12.5), longitudinalG: drift.Value(0.1), lateralG: drift.Value(0.2), ); }); await db.batch((batch) { batch.insertAll(db.telemetrySamples, batchSamples); }); final retrieved = await (db.select(db.telemetrySamples) ..where((t) => t.rideId.equals(rideId)) ..orderBy([(t) => drift.OrderingTerm(expression: t.timestamp)]) ).get(); expect(retrieved.length, 50); expect(retrieved.first.speed, 45.0); expect(retrieved.last.speed, closeTo(54.8, 0.01)); expect(retrieved.first.gpsAccuracy, 3.2); }); test('Crash Recovery: Incomplete ride with null endTime is recovered with correct timestamp', () async { final startTime = DateTime(2026, 8, 28, 14, 0, 0); final rideId = await db.into(db.rides).insert(RidesCompanion.insert( startTime: startTime, movingTime: const drift.Value(120), stoppedTime: const drift.Value(30), )); // Simulate crash by adding breadcrumbs then leaving endTime null final lastBreadcrumbTime = startTime.add(const Duration(seconds: 150)); await db.into(db.telemetrySamples).insert(TelemetrySamplesCompanion.insert( rideId: rideId, timestamp: lastBreadcrumbTime, latitude: const drift.Value(37.7749), longitude: const drift.Value(-122.4194), )); // Execute crash recovery logic final unclosedRides = await (db.select(db.rides)..where((r) => r.endTime.isNull())).get(); expect(unclosedRides.length, 1); for (final r in unclosedRides) { final lastSample = await (db.select(db.telemetrySamples) ..where((t) => t.rideId.equals(r.id)) ..orderBy([(t) => drift.OrderingTerm(expression: t.timestamp, mode: drift.OrderingMode.desc)]) ..limit(1) ).getSingleOrNull(); final calculatedEndTime = lastSample?.timestamp ?? r.startTime.add(Duration(seconds: r.movingTime + r.stoppedTime)); await (db.update(db.rides)..where((t) => t.id.equals(r.id))).write( RidesCompanion(endTime: drift.Value(calculatedEndTime)), ); } final updatedRide = await (db.select(db.rides)..where((r) => r.id.equals(rideId))).getSingle(); expect(updatedRide.endTime, lastBreadcrumbTime); expect(updatedRide.endTime!.difference(updatedRide.startTime).inSeconds, 150); }); }); }