import 'package:flutter_test/flutter_test.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:motorcycle_dash/services/sync/sync_models.dart'; import 'package:motorcycle_dash/services/sync/sync_outbox.dart'; import 'package:motorcycle_dash/services/sync/sync_manager.dart'; import 'package:motorcycle_dash/services/sync/sync_provider.dart'; class _ValidationProvider implements SyncProvider { _ValidationProvider({this.available = true, this.authenticated = true}); bool available; bool authenticated; int uploadCount = 0; @override String get providerId => 'firebase'; @override Future isAvailable() async => available; @override Future requiresAuthentication() async => !authenticated; @override Future isRideConfirmedInCloud(String remoteRideId) async => false; @override Future verifyCloudConfirmation(String remoteRideId) async => CloudConfirmationResult.missing(remoteRideId: remoteRideId); @override Future uploadRide(SyncItem item) async { uploadCount++; return SyncResult.success(remoteId: 'cloud-${item.localRideId}'); } } void main() { TestWidgetsFlutterBinding.ensureInitialized(); group('Persistent offline/cloud synchronization validation', () { setUp(() => SharedPreferences.setMockInitialValues({})); test('outbox survives an app restart with retry metadata intact', () async { final prefs = await SharedPreferences.getInstance(); final first = SyncOutbox(prefs); final provider = _ValidationProvider(available: false); final manager = SyncManager(first, initialProviders: [provider], defaultProviderId: 'firebase'); await manager.enqueueRide(42, autoProcess: false); await manager.processQueue(); expect(first.getItem(42, 'firebase')?.status, SyncStatus.waitingForNetwork); final restarted = SyncOutbox(prefs); expect(restarted.getItem(42, 'firebase')?.status, SyncStatus.waitingForNetwork); manager.dispose(); }); test('local offline outbox migrates into authenticated account namespace', () async { final prefs = await SharedPreferences.getInstance(); final local = SyncOutbox(prefs); await local.enqueue(77, 'firebase', autoProcess: false); final account = SyncOutbox(prefs, storageNamespace: 'user_abc'); expect(account.getItem(77, 'firebase')?.status, SyncStatus.queued); expect(prefs.getString('motorsignal_sync_outbox_manifest_v2_local'), isNull); }); test('reconnect retry uploads the preserved ride and marks it synced', () async { final prefs = await SharedPreferences.getInstance(); final outbox = SyncOutbox(prefs); final provider = _ValidationProvider(available: false); final manager = SyncManager(outbox, initialProviders: [provider], defaultProviderId: 'firebase'); await manager.enqueueRide(88, autoProcess: false); await manager.processQueue(); expect(outbox.getItem(88, 'firebase')?.status, SyncStatus.waitingForNetwork); provider.available = true; await manager.processQueue(); expect(outbox.getItem(88, 'firebase')?.status, SyncStatus.synced); expect(provider.uploadCount, 1); manager.dispose(); }); test('authentication block preserves local work until login retry', () async { final prefs = await SharedPreferences.getInstance(); final outbox = SyncOutbox(prefs); final provider = _ValidationProvider(authenticated: false); final manager = SyncManager(outbox, initialProviders: [provider], defaultProviderId: 'firebase'); await manager.enqueueRide(99, autoProcess: false); await manager.processQueue(); expect(outbox.getItem(99, 'firebase')?.status, SyncStatus.authRequired); provider.authenticated = true; await manager.retryAuthRequired(); expect(outbox.getItem(99, 'firebase')?.status, SyncStatus.synced); manager.dispose(); }); test('duplicate enqueue remains idempotent after restart', () async { final prefs = await SharedPreferences.getInstance(); final first = SyncOutbox(prefs); await first.enqueue(123, 'firebase', autoProcess: false); final restarted = SyncOutbox(prefs); await restarted.enqueue(123, 'firebase', autoProcess: false); expect(restarted.count, 1); }); }); }