Remote sync of webdav
This commit is contained in:
parent
f01186df79
commit
483fbeafb6
44 changed files with 4842 additions and 975 deletions
137
test/vehicle_id_migration_test.dart
Normal file
137
test/vehicle_id_migration_test.dart
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:fuel_tax_tracker/services/database_service.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:path_provider_platform_interface/path_provider_platform_interface.dart';
|
||||
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
|
||||
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
|
||||
|
||||
/// Confirms the version 3 → 4 migration (VIN becomes editable; a hidden
|
||||
/// `id` takes over as vehicles' primary key/merge key) preserves existing
|
||||
/// local data rather than dropping it, unlike the earlier VIN-as-primary-key
|
||||
/// migration which was a from-scratch rebuild. Builds a real on-disk
|
||||
/// version-3 database by hand (the old schema, pre-dating this change), then
|
||||
/// runs [DatabaseService.init] against it and checks what comes out.
|
||||
void main() {
|
||||
late Directory tempDir;
|
||||
|
||||
setUpAll(() {
|
||||
sqfliteFfiInit();
|
||||
databaseFactory = databaseFactoryFfi;
|
||||
});
|
||||
|
||||
setUp(() async {
|
||||
tempDir = await Directory.systemTemp.createTemp('vehicle_id_migration_test_');
|
||||
PathProviderPlatform.instance = _FakePathProviderPlatform(tempDir.path);
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
await tempDir.delete(recursive: true);
|
||||
});
|
||||
|
||||
test('migrates a version-3 database, preserving vehicles and fuel entries', () async {
|
||||
final dbDir = Directory(p.join(tempDir.path, 'FuelTaxTracker'));
|
||||
await dbDir.create(recursive: true);
|
||||
final dbPath = p.join(dbDir.path, dbFileName);
|
||||
|
||||
final oldDb = await databaseFactory.openDatabase(
|
||||
dbPath,
|
||||
options: OpenDatabaseOptions(
|
||||
version: 3,
|
||||
onCreate: (db, version) async {
|
||||
await db.execute('''
|
||||
CREATE TABLE vehicles (
|
||||
vin TEXT PRIMARY KEY,
|
||||
nickname TEXT,
|
||||
updated_at INTEGER NOT NULL,
|
||||
deleted_at INTEGER,
|
||||
dirty INTEGER NOT NULL DEFAULT 1
|
||||
)
|
||||
''');
|
||||
await db.execute('''
|
||||
CREATE TABLE fuel_entries (
|
||||
id TEXT PRIMARY KEY,
|
||||
vehicle_vin TEXT NOT NULL,
|
||||
date INTEGER NOT NULL,
|
||||
gallons REAL NOT NULL,
|
||||
price_per_gallon REAL NOT NULL,
|
||||
total_cost REAL NOT NULL,
|
||||
receipt_image_path TEXT,
|
||||
receipt_drive_file_id TEXT,
|
||||
updated_at INTEGER NOT NULL,
|
||||
deleted_at INTEGER,
|
||||
dirty INTEGER NOT NULL DEFAULT 1
|
||||
)
|
||||
''');
|
||||
},
|
||||
),
|
||||
);
|
||||
await oldDb.insert('vehicles', {
|
||||
'vin': 'VIN1',
|
||||
'nickname': "Mom's Car",
|
||||
'updated_at': 1000,
|
||||
'deleted_at': null,
|
||||
'dirty': 0,
|
||||
});
|
||||
await oldDb.insert('vehicles', {
|
||||
'vin': 'VIN2',
|
||||
'nickname': null,
|
||||
'updated_at': 1500,
|
||||
'deleted_at': 2000, // a soft-deleted vehicle, should still migrate
|
||||
'dirty': 0,
|
||||
});
|
||||
await oldDb.insert('fuel_entries', {
|
||||
'id': 'f1',
|
||||
'vehicle_vin': 'VIN1',
|
||||
'date': 1200,
|
||||
'gallons': 12.5,
|
||||
'price_per_gallon': 3.2,
|
||||
'total_cost': 40.0,
|
||||
'receipt_image_path': '/local/receipt.jpg',
|
||||
'receipt_drive_file_id': null,
|
||||
'updated_at': 1200,
|
||||
'deleted_at': null,
|
||||
'dirty': 0,
|
||||
});
|
||||
await oldDb.close();
|
||||
|
||||
final databaseService = DatabaseService();
|
||||
await databaseService.init();
|
||||
addTearDown(() => databaseService.rawDb.close());
|
||||
|
||||
final vehicles = await databaseService.getVehicles();
|
||||
expect(vehicles, hasLength(1), reason: 'the soft-deleted vehicle should not show as active');
|
||||
expect(vehicles.first.vin, 'VIN1');
|
||||
expect(vehicles.first.nickname, "Mom's Car");
|
||||
expect(vehicles.first.id, isNotEmpty);
|
||||
|
||||
final allVehicleRows = await databaseService.rawDb.query('vehicles');
|
||||
expect(allVehicleRows, hasLength(2), reason: 'the soft-deleted vehicle should still exist');
|
||||
final ids = {for (final row in allVehicleRows) row['vin']: row['id'] as String};
|
||||
expect(ids['VIN1'], isNotEmpty);
|
||||
expect(ids['VIN2'], isNotEmpty);
|
||||
expect(ids['VIN1'], isNot(ids['VIN2']), reason: 'each vehicle gets its own generated id');
|
||||
for (final row in allVehicleRows) {
|
||||
expect(row['dirty'], 1, reason: 'migrated rows must be re-pushed under the new schema');
|
||||
}
|
||||
|
||||
final fuelEntries = await databaseService.getFuelEntries();
|
||||
expect(fuelEntries, hasLength(1));
|
||||
expect(fuelEntries.first.vehicleId, ids['VIN1'],
|
||||
reason: "the fuel entry's foreign key should now point at VIN1's new id");
|
||||
expect(fuelEntries.first.gallons, 12.5);
|
||||
expect(fuelEntries.first.receiptImagePath, '/local/receipt.jpg');
|
||||
});
|
||||
}
|
||||
|
||||
class _FakePathProviderPlatform extends PathProviderPlatform with MockPlatformInterfaceMixin {
|
||||
final String tempPath;
|
||||
_FakePathProviderPlatform(this.tempPath);
|
||||
|
||||
@override
|
||||
Future<String?> getApplicationDocumentsPath() async => tempPath;
|
||||
|
||||
@override
|
||||
Future<String?> getTemporaryPath() async => tempPath;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue