94 lines
3.5 KiB
Dart
94 lines
3.5 KiB
Dart
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);
|
|
});
|
|
});
|
|
}
|