MotorSignal-App/test/ride_timestamp_migration_te...

113 lines
5.3 KiB
Dart

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<String, Object?>.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<String, Object?>.from(r)).toList();
final children = raw.select('SELECT * FROM telemetry_samples').map((r) => Map<String, Object?>.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<String, Object?>.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<String, Object?>.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<StateError>()));
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();
});
}