import 'dart:io'; import 'package:flutter_test/flutter_test.dart'; import 'package:http/http.dart' as http; import 'package:rental_income_tracker/models/app_settings.dart'; import 'package:rental_income_tracker/models/monthly_rent_record.dart'; import 'package:rental_income_tracker/services/notification_service.dart'; import 'package:rental_income_tracker/services/open_exchange_service.dart'; import 'package:rental_income_tracker/services/storage_service.dart'; import 'package:rental_income_tracker/state/rent_controller.dart'; class _FakeStorageService extends StorageService { AppSettings _settings = AppSettings.defaults(); Map _records = {}; @override Future loadSettings() async => _settings; @override Future saveSettings(AppSettings settings) async { _settings = settings; } @override Future> loadRecords() async => Map.from(_records); @override Future saveRecords(Map records) async { _records = Map.from(records); } } class _FakeExchangeService extends FrankfurterApiService { _FakeExchangeService({required this.quote}) : super(httpClient: http.Client(), apiKey: 'test-key'); final double quote; @override Future convertUsdToSek({ required DateTime estDate, required double amount, }) async { return ConversionResult(quote: quote, result: quote * amount); } } class _FakeNotificationService extends NotificationService { _FakeNotificationService() : super((_) async {}); @override Future initialize() async {} @override Future processPendingActions() async {} @override Future scheduleForMonth({ required int year, required int month, required int quietHourStart, required int fallbackHour, }) async {} @override Future cancelForMonth(int year, int month) async {} @override Future cancelAll() async {} } void main() { group('backup/import compatibility', () { test( 'round-trips current backup format through export and import', () async { final sourceStorage = _FakeStorageService(); final sourceController = RentController( storageService: sourceStorage, exchangeService: _FakeExchangeService(quote: 10.5), notificationService: _FakeNotificationService(), ); await sourceController.updateRentUsd(2450); await sourceController.setMonthStatusManually( year: 2025, month: 1, status: PaymentStatus.onTime, paidUsd: 2000, ); await sourceController.setMonthStatusManually( year: 2025, month: 2, status: PaymentStatus.late, paidUsd: 1800, ); final tempDir = await Directory.systemTemp.createTemp('backup_test_'); addTearDown(() async { if (await tempDir.exists()) { await tempDir.delete(recursive: true); } }); final backupPath = await sourceController.exportBackupToDirectory( tempDir.path, ); final targetStorage = _FakeStorageService(); final targetController = RentController( storageService: targetStorage, exchangeService: _FakeExchangeService(quote: 99), notificationService: _FakeNotificationService(), ); await targetController.importBackupFromFile(backupPath); expect(targetController.settings.rentUsd, 2450); targetController.selectYear(2025); final rows = targetController.buildRowsForSelectedYear(); final january = rows.firstWhere((row) => row.month == 1); final february = rows.firstWhere((row) => row.month == 2); expect(january.status, PaymentStatus.onTime); expect(january.usdAmount, 2000); expect(january.sekAmount, 21000); expect(january.effectiveUsdToSekRate, 10.5); expect(february.status, PaymentStatus.late); expect(february.usdAmount, 1800); expect(february.sekAmount, 18900); expect(february.effectiveUsdToSekRate, 10.5); final persistedSettings = await targetStorage.loadSettings(); final persistedRecords = await targetStorage.loadRecords(); expect(persistedSettings.rentUsd, 2450); expect( persistedRecords.keys, containsAll(['2025-01', '2025-02']), ); }, ); test( 'imports legacy export payloads with missing optional fields', () async { final tempDir = await Directory.systemTemp.createTemp('backup_legacy_'); addTearDown(() async { if (await tempDir.exists()) { await tempDir.delete(recursive: true); } }); final legacyFile = File('${tempDir.path}/legacy_export.json'); await legacyFile.writeAsString(''' { "exportedAtUtc": "2026-03-20T10:00:00.000Z", "settings": { "rentUsd": 1995.0, "occupied": true }, "records": [ { "year": 2024, "month": 12, "usdAmount": 1995.0, "sekAmount": 21446.25, "usdToSekRate": 10.75, "paidAtUtc": "2024-12-02T12:00:00.000Z", "onTime": false } ] } '''); final controller = RentController( storageService: _FakeStorageService(), exchangeService: _FakeExchangeService(quote: 1), notificationService: _FakeNotificationService(), ); await controller.importBackupFromFile(legacyFile.path); expect(controller.settings.rentUsd, 1995.0); expect(controller.settings.localFallbackHour, 9); expect(controller.settings.localQuietHourStart, 23); controller.selectYear(2024); final december = controller.buildRowsForSelectedYear().firstWhere( (row) => row.month == 12, ); expect(december.status, PaymentStatus.late); expect(december.usdAmount, 1995.0); expect(december.sekAmount, 21446.25); expect(december.effectiveUsdToSekRate, 10.75); }, ); }); }