import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:relationship_saver/features/sync/storage/sync_state_store_in_memory.dart'; import 'package:relationship_saver/features/sync/storage/sync_state_store_provider.dart'; import 'package:relationship_saver/features/sync/sync_queue_repository.dart'; import 'package:relationship_saver/integrations/backend/models/backend_models.dart'; void main() { test('enqueue persists and can be reloaded', () async { final InMemorySyncStateStore store = InMemorySyncStateStore(); final ProviderContainer container = ProviderContainer( overrides: [syncStateStoreProvider.overrideWithValue(store)], ); await container .read(syncQueueRepositoryProvider.notifier) .enqueue(_change(id: 'p-1', mutationId: 'cm-1')); final SyncStateData firstState = await _readState(container); expect(firstState.pending.length, 1); container.dispose(); final ProviderContainer reloaded = ProviderContainer( overrides: [syncStateStoreProvider.overrideWithValue(store)], ); addTearDown(reloaded.dispose); final SyncStateData reloadedState = await _readState(reloaded); expect(reloadedState.pending.length, 1); expect(reloadedState.pending.first.clientMutationId, 'cm-1'); }); test( 'applyPushResult removes completed mutation ids and tracks rejections', () async { final ProviderContainer container = ProviderContainer( overrides: [ syncStateStoreProvider.overrideWithValue(InMemorySyncStateStore()), ], ); addTearDown(container.dispose); final SyncQueueRepository notifier = container.read( syncQueueRepositoryProvider.notifier, ); await notifier.enqueueAll([ _change(id: 'p-1', mutationId: 'cm-a'), _change(id: 'p-2', mutationId: 'cm-b'), ]); await notifier.applyPushResult( SyncPushResult( cursor: 'cursor-2', accepted: const [MutationAck(clientMutationId: 'cm-a')], rejected: const [ MutationRejection( clientMutationId: 'cm-b', code: 'VALIDATION_FAILED', message: 'invalid payload', ), ], ), at: DateTime(2026, 2, 15, 12), ); final SyncStateData state = await _readState(container); expect(state.pending, isEmpty); expect(state.cursor, 'cursor-2'); expect(state.rejected.length, 1); expect(state.lastError, contains('Push rejected')); }, ); test( 'clearRejections removes last rejected payload and rejection error', () async { final ProviderContainer container = ProviderContainer( overrides: [ syncStateStoreProvider.overrideWithValue(InMemorySyncStateStore()), ], ); addTearDown(container.dispose); final SyncQueueRepository notifier = container.read( syncQueueRepositoryProvider.notifier, ); await notifier.enqueue(_change(id: 'p-1', mutationId: 'cm-r')); await notifier.applyPushResult( SyncPushResult( cursor: 'cursor-3', accepted: const [], rejected: const [ MutationRejection( clientMutationId: 'cm-r', code: 'VALIDATION_FAILED', message: 'invalid payload', ), ], ), at: DateTime(2026, 2, 15, 13), ); await notifier.clearRejections(); final SyncStateData state = await _readState(container); expect(state.rejected, isEmpty); expect(state.lastError, isNull); }, ); } class SyncStateData { const SyncStateData({ required this.cursor, required this.pending, required this.rejected, required this.lastError, }); final String? cursor; final List pending; final List rejected; final String? lastError; } Future _readState(ProviderContainer container) async { final state = await container.read(syncQueueRepositoryProvider.future); return SyncStateData( cursor: state.cursor, pending: state.pendingChanges, rejected: state.lastRejected, lastError: state.lastError, ); } ChangeEnvelope _change({required String id, required String mutationId}) { return ChangeEnvelope( schemaVersion: 1, entityType: 'person', entityId: id, op: ChangeOperation.upsert, modifiedAt: DateTime(2026, 2, 15, 12).toUtc(), clientMutationId: mutationId, payload: const {'name': 'Alex', 'relationship': 'Friend'}, ); }