import 'dart:io'; import 'package:drift/native.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:motorcycle_dash/database/database.dart'; // ignore: depend_on_referenced_packages import 'package:sqlite3/sqlite3.dart' as sqlite; void main() { TestWidgetsFlutterBinding.ensureInitialized(); late Directory directory; late File file; setUp(() async { directory = await Directory.systemTemp.createTemp('ride-timestamp-test-'); file = File('${directory.path}/copy.sqlite'); final db = AppDatabase.forTesting(NativeDatabase(file)); await db.customSelect('SELECT 1').get(); await db.close(); }); tearDown(() => directory.delete(recursive: true)); final backupPath = Platform.environment['MOTORSIGNAL_TEST_BACKUP']; if (backupPath != null) { test('verified device backup copy preserves every original table row', () async { await File(backupPath).copy(file.path); final raw = sqlite.sqlite3.open(file.path); final names = raw.select("SELECT name FROM sqlite_master WHERE type = 'table'") .map((r) => r['name'] as String).toList(); final before = { for (final name in names) name: raw.select('SELECT * FROM "$name" ORDER BY rowid') .map((r) => Map.from(r)).toList(), }; raw.close(); final db = AppDatabase.forTesting(NativeDatabase(file)); addTearDown(db.close); expect(await db.select(db.rides).get(), hasLength(22)); expect(await db.select(db.telemetrySamples).get(), hasLength(67798)); for (final name in names) { final rows = await db.customSelect('SELECT * FROM "$name" ORDER BY rowid').get(); expect(rows.map((r) => r.data).toList(), before[name], reason: name); } expect(await db.customSelect('SELECT * FROM ride_timestamp_archive').get(), isEmpty); expect((await db.customSelect('PRAGMA integrity_check').get()).single.data.values.single, 'ok'); await db.close(); }); } test('v7 mixed dates migrate once with exact originals and children preserved', () async { final raw = sqlite.sqlite3.open(file.path); for (final row in [ [1, 1791761491, null], [2, 1791761491123, 1791761501456], [3, 1791761491123456, 1791761501654321], [4, 1791761491, 1791761501000], ]) { raw.execute('INSERT INTO rides (id, start_time, end_time, cloud_ride_id) VALUES (?, ?, ?, ?)', [...row, 'ride_preserved_${row.first}']); } raw.execute('INSERT INTO telemetry_samples (ride_id, timestamp, speed) VALUES (2, 1791761491, 17.5)'); raw.execute('PRAGMA user_version = 7'); final before = raw.select('SELECT * FROM rides ORDER BY id').map((r) => Map.from(r)).toList(); final children = raw.select('SELECT * FROM telemetry_samples').map((r) => Map.from(r)).toList(); raw.close(); for (var open = 0; open < 2; open++) { final db = AppDatabase.forTesting(NativeDatabase(file)); addTearDown(db.close); final rides = await db.select(db.rides).get(); expect(rides, hasLength(4)); expect(rides.map((r) => r.startTime.toUtc().millisecondsSinceEpoch), everyElement(1791761491000)); expect(rides.first.endTime, isNull); expect(rides.skip(1).map((r) => r.endTime!.toUtc().millisecondsSinceEpoch), everyElement(1791761501000)); final archive = await db.customSelect('SELECT * FROM ride_timestamp_archive ORDER BY ride_id').get(); expect(archive, hasLength(3)); expect(archive.first.data['original_start_time'], 1791761491123); expect(archive[1].data['original_start_time'], 1791761491123456); expect(archive[1].data['original_end_time'], 1791761501654321); expect((await db.customSelect('SELECT * FROM telemetry_samples').get()).map((r) => r.data).toList(), children); final after = (await db.customSelect('SELECT * FROM rides ORDER BY id').get()).map((r) => Map.from(r.data)).toList(); for (var i = 0; i < before.length; i++) { after[i]['start_time'] = before[i]['start_time']; after[i]['end_time'] = before[i]['end_time']; } expect(after, before); await db.close(); } final restored = sqlite.sqlite3.open(file.path); restored.execute(File('tools/rollback_ride_timestamps_v8.sql').readAsStringSync()); expect(restored.select('SELECT * FROM rides ORDER BY id').map((r) => Map.from(r)).toList(), before); expect(restored.select('PRAGMA user_version').single['user_version'], 7); expect(restored.select('SELECT COUNT(*) AS n FROM ride_timestamp_archive').single['n'], 3); restored.close(); }); test('unrecognized large timestamp aborts without rewriting records', () async { final raw = sqlite.sqlite3.open(file.path); raw.execute('INSERT INTO rides (start_time) VALUES (1791761491123), (9223372036854775807)'); raw.execute('PRAGMA user_version = 7'); raw.close(); final db = AppDatabase.forTesting(NativeDatabase(file)); addTearDown(db.close); await expectLater(db.select(db.rides).get(), throwsA(isA())); await db.close(); final check = sqlite.sqlite3.open(file.path); expect(check.select('SELECT start_time FROM rides ORDER BY id').map((r) => r['start_time']).toList(), [1791761491123, 9223372036854775807]); expect(check.select('PRAGMA user_version').single['user_version'], 7); check.close(); }); }