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() : super(httpClient: http.Client()); @override Future convertUsdToSek({ required DateTime estDate, required double amount, }) async { return ConversionResult(quote: 10, result: amount * 10); } } class _RecordedMonth { _RecordedMonth(this.year, this.month); final int year; final int month; } class _RecordingNotificationService extends NotificationService { _RecordingNotificationService() : super((_) async {}); final List<_RecordedMonth> scheduled = <_RecordedMonth>[]; final List<_RecordedMonth> cancelled = <_RecordedMonth>[]; var cancelAllCount = 0; @override Future initialize() async {} @override Future processPendingActions() async {} @override Future scheduleForMonth({ required int year, required int month, required int quietHourStart, required int fallbackHour, }) async { scheduled.add(_RecordedMonth(year, month)); } @override Future cancelForMonth(int year, int month) async { cancelled.add(_RecordedMonth(year, month)); } @override Future cancelAll() async { cancelAllCount++; } } void main() { group('notification scheduling', () { test('setOccupied(true) schedules multiple future months', () async { final notifications = _RecordingNotificationService(); final controller = RentController( storageService: _FakeStorageService(), exchangeService: _FakeExchangeService(), notificationService: notifications, ); await controller.setOccupied(true); expect(notifications.cancelAllCount, 1); expect(notifications.scheduled.length, greaterThanOrEqualTo(12)); final now = DateTime.now(); expect( notifications.scheduled.any( (entry) => entry.year == now.year && entry.month == now.month, ), isTrue, ); final nextMonth = DateTime(now.year, now.month + 1, 1); expect( notifications.scheduled.any( (entry) => entry.year == nextMonth.year && entry.month == nextMonth.month, ), isTrue, ); }); test( 'setOccupied(false) cancels notifications and does not reschedule', () async { final notifications = _RecordingNotificationService(); final controller = RentController( storageService: _FakeStorageService(), exchangeService: _FakeExchangeService(), notificationService: notifications, ); await controller.setOccupied(false); expect(notifications.cancelAllCount, 1); expect(notifications.scheduled, isEmpty); }, ); }); }