Migrate sync queue persistence to store adapters
This commit is contained in:
@@ -28,6 +28,9 @@ Default local store:
|
|||||||
|
|
||||||
- `Hive` (`USE_HIVE_LOCAL_DB=true` by default)
|
- `Hive` (`USE_HIVE_LOCAL_DB=true` by default)
|
||||||
- first-run migration imports legacy `shared_preferences` payload when present
|
- first-run migration imports legacy `shared_preferences` payload when present
|
||||||
|
- applies to both:
|
||||||
|
- local product state (`LocalRepository`)
|
||||||
|
- sync queue state (`SyncQueueRepository`)
|
||||||
|
|
||||||
Optional migration path:
|
Optional migration path:
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,35 @@
|
|||||||
|
|
||||||
Updated: 2026-02-15
|
Updated: 2026-02-15
|
||||||
|
|
||||||
|
## Latest Milestone (2026-02-15): Sync Queue Storage Adapter + Hive Migration
|
||||||
|
|
||||||
|
Extended the storage abstraction approach to sync queue persistence:
|
||||||
|
|
||||||
|
- Added sync storage abstraction:
|
||||||
|
- `lib/features/sync/storage/sync_state_store.dart`
|
||||||
|
- Added concrete sync stores:
|
||||||
|
- `lib/features/sync/storage/sync_state_store_shared_prefs.dart`
|
||||||
|
- `lib/features/sync/storage/sync_state_store_hive.dart`
|
||||||
|
- `lib/features/sync/storage/sync_state_store_in_memory.dart`
|
||||||
|
- provider wiring: `lib/features/sync/storage/sync_state_store_provider.dart`
|
||||||
|
- Refactored sync queue repository:
|
||||||
|
- `lib/features/sync/sync_queue_repository.dart`
|
||||||
|
- no direct `shared_preferences` dependency
|
||||||
|
- store-agnostic read/write path via `SyncStateStore`
|
||||||
|
- one-time migration path from legacy shared prefs sync payload into Hive
|
||||||
|
- Updated tests to use explicit in-memory store overrides where deterministic:
|
||||||
|
- `test/features/sync/sync_queue_repository_test.dart`
|
||||||
|
- `test/features/sync/sync_coordinator_test.dart`
|
||||||
|
- `test/features/local/local_repository_test.dart`
|
||||||
|
- `test/features/responsive/responsive_views_smoke_test.dart`
|
||||||
|
- Added migration coverage:
|
||||||
|
- `test/features/sync/storage/sync_queue_hive_migration_test.dart`
|
||||||
|
|
||||||
|
Validation for this milestone:
|
||||||
|
|
||||||
|
- `flutter analyze` -> pass
|
||||||
|
- `flutter test` -> pass
|
||||||
|
|
||||||
## Latest Milestone (2026-02-15): Local Store Adapter Layer (DB Migration Scaffold)
|
## Latest Milestone (2026-02-15): Local Store Adapter Layer (DB Migration Scaffold)
|
||||||
|
|
||||||
Implemented a persistence boundary so local app state is no longer tightly coupled to `shared_preferences` internals:
|
Implemented a persistence boundary so local app state is no longer tightly coupled to `shared_preferences` internals:
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
/// Raw persisted payload for sync queue state.
|
||||||
|
class SyncStateRecord {
|
||||||
|
const SyncStateRecord({required this.rawState});
|
||||||
|
|
||||||
|
final String rawState;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Persistence boundary for sync queue metadata and pending changes.
|
||||||
|
abstract interface class SyncStateStore {
|
||||||
|
/// Reads stored sync state payload.
|
||||||
|
Future<SyncStateRecord?> read();
|
||||||
|
|
||||||
|
/// Writes sync state payload.
|
||||||
|
Future<void> write({required String rawState});
|
||||||
|
|
||||||
|
/// Clears stored sync state payload.
|
||||||
|
Future<void> clear();
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import 'package:hive_flutter/hive_flutter.dart';
|
||||||
|
import 'package:relationship_saver/features/sync/storage/sync_state_store.dart';
|
||||||
|
|
||||||
|
/// Hive-backed sync state store.
|
||||||
|
class HiveSyncStateStore implements SyncStateStore {
|
||||||
|
static const String _boxName = 'relationship_saver_sync';
|
||||||
|
static const String _stateKey = 'sync_state_json';
|
||||||
|
|
||||||
|
static bool _initialized = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> clear() async {
|
||||||
|
final Box<dynamic> box = await _openBox();
|
||||||
|
await box.delete(_stateKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<SyncStateRecord?> read() async {
|
||||||
|
final Box<dynamic> box = await _openBox();
|
||||||
|
final String? rawState = box.get(_stateKey) as String?;
|
||||||
|
if (rawState == null || rawState.isEmpty) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return SyncStateRecord(rawState: rawState);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> write({required String rawState}) async {
|
||||||
|
final Box<dynamic> box = await _openBox();
|
||||||
|
await box.put(_stateKey, rawState);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Box<dynamic>> _openBox() async {
|
||||||
|
if (!_initialized) {
|
||||||
|
await Hive.initFlutter();
|
||||||
|
_initialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Hive.isBoxOpen(_boxName)) {
|
||||||
|
return Hive.openBox<dynamic>(_boxName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Hive.box<dynamic>(_boxName);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import 'package:relationship_saver/features/sync/storage/sync_state_store.dart';
|
||||||
|
|
||||||
|
/// In-memory sync state store for tests.
|
||||||
|
class InMemorySyncStateStore implements SyncStateStore {
|
||||||
|
SyncStateRecord? _record;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> clear() async {
|
||||||
|
_record = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<SyncStateRecord?> read() async => _record;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> write({required String rawState}) async {
|
||||||
|
_record = SyncStateRecord(rawState: rawState);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:relationship_saver/core/config/app_config.dart';
|
||||||
|
import 'package:relationship_saver/features/sync/storage/sync_state_store.dart';
|
||||||
|
import 'package:relationship_saver/features/sync/storage/sync_state_store_hive.dart';
|
||||||
|
import 'package:relationship_saver/features/sync/storage/sync_state_store_shared_prefs.dart';
|
||||||
|
|
||||||
|
/// Chooses sync queue persistence backend.
|
||||||
|
final Provider<SyncStateStore> syncStateStoreProvider =
|
||||||
|
Provider<SyncStateStore>((Ref ref) {
|
||||||
|
if (AppConfig.useHiveLocalDb) {
|
||||||
|
return HiveSyncStateStore();
|
||||||
|
}
|
||||||
|
return SharedPrefsSyncStateStore();
|
||||||
|
});
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import 'package:relationship_saver/features/sync/storage/sync_state_store.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
|
/// shared_preferences-backed sync state store.
|
||||||
|
class SharedPrefsSyncStateStore implements SyncStateStore {
|
||||||
|
SharedPrefsSyncStateStore({this.stateKey = 'sync_queue_state_v1'});
|
||||||
|
|
||||||
|
final String stateKey;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> clear() async {
|
||||||
|
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||||
|
await prefs.remove(stateKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<SyncStateRecord?> read() async {
|
||||||
|
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||||
|
final String? rawState = prefs.getString(stateKey);
|
||||||
|
if (rawState == null || rawState.isEmpty) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return SyncStateRecord(rawState: rawState);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> write({required String rawState}) async {
|
||||||
|
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||||
|
await prefs.setString(stateKey, rawState);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,27 +1,43 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:relationship_saver/core/config/app_config.dart';
|
||||||
|
import 'package:relationship_saver/features/sync/storage/sync_state_store.dart';
|
||||||
|
import 'package:relationship_saver/features/sync/storage/sync_state_store_hive.dart';
|
||||||
|
import 'package:relationship_saver/features/sync/storage/sync_state_store_provider.dart';
|
||||||
|
import 'package:relationship_saver/features/sync/storage/sync_state_store_shared_prefs.dart';
|
||||||
import 'package:relationship_saver/features/sync/sync_state.dart';
|
import 'package:relationship_saver/features/sync/sync_state.dart';
|
||||||
import 'package:relationship_saver/integrations/backend/models/backend_models.dart';
|
import 'package:relationship_saver/integrations/backend/models/backend_models.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
|
||||||
|
|
||||||
/// Persists queued local mutations and sync cursor metadata.
|
/// Persists queued local mutations and sync cursor metadata.
|
||||||
class SyncQueueRepository extends AsyncNotifier<SyncState> {
|
class SyncQueueRepository extends AsyncNotifier<SyncState> {
|
||||||
static const String _storageKey = 'sync_queue_state_v1';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<SyncState> build() async {
|
Future<SyncState> build() async {
|
||||||
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
final SyncStateStore store = ref.read(syncStateStoreProvider);
|
||||||
final String? raw = prefs.getString(_storageKey);
|
SyncStateRecord? record = await store.read();
|
||||||
if (raw == null || raw.isEmpty) {
|
|
||||||
|
if ((record == null || record.rawState.isEmpty) &&
|
||||||
|
AppConfig.useHiveLocalDb &&
|
||||||
|
store is HiveSyncStateStore) {
|
||||||
|
final SharedPrefsSyncStateStore legacyStore = SharedPrefsSyncStateStore();
|
||||||
|
final SyncStateRecord? legacy = await legacyStore.read();
|
||||||
|
if (legacy != null && legacy.rawState.isNotEmpty) {
|
||||||
|
await store.write(rawState: legacy.rawState);
|
||||||
|
await legacyStore.clear();
|
||||||
|
record = await store.read();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (record == null || record.rawState.isEmpty) {
|
||||||
return SyncState.empty;
|
return SyncState.empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final Map<String, dynamic> json = jsonDecode(raw) as Map<String, dynamic>;
|
final Map<String, dynamic> json =
|
||||||
|
jsonDecode(record.rawState) as Map<String, dynamic>;
|
||||||
return SyncState.fromJson(json);
|
return SyncState.fromJson(json);
|
||||||
} on FormatException {
|
} on FormatException {
|
||||||
await prefs.remove(_storageKey);
|
await store.clear();
|
||||||
return SyncState.empty;
|
return SyncState.empty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -121,8 +137,9 @@ class SyncQueueRepository extends AsyncNotifier<SyncState> {
|
|||||||
|
|
||||||
Future<void> _setState(SyncState next) async {
|
Future<void> _setState(SyncState next) async {
|
||||||
state = AsyncData<SyncState>(next);
|
state = AsyncData<SyncState>(next);
|
||||||
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
await ref
|
||||||
await prefs.setString(_storageKey, jsonEncode(next.toJson()));
|
.read(syncStateStoreProvider)
|
||||||
|
.write(rawState: jsonEncode(next.toJson()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import 'package:relationship_saver/features/local/local_models.dart';
|
|||||||
import 'package:relationship_saver/features/local/local_repository.dart';
|
import 'package:relationship_saver/features/local/local_repository.dart';
|
||||||
import 'package:relationship_saver/features/local/storage/local_data_store_in_memory.dart';
|
import 'package:relationship_saver/features/local/storage/local_data_store_in_memory.dart';
|
||||||
import 'package:relationship_saver/features/local/storage/local_data_store_provider.dart';
|
import 'package:relationship_saver/features/local/storage/local_data_store_provider.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:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
@@ -179,6 +181,7 @@ ProviderContainer _createContainer() {
|
|||||||
return ProviderContainer(
|
return ProviderContainer(
|
||||||
overrides: [
|
overrides: [
|
||||||
localDataStoreProvider.overrideWithValue(InMemoryLocalDataStore()),
|
localDataStoreProvider.overrideWithValue(InMemoryLocalDataStore()),
|
||||||
|
syncStateStoreProvider.overrideWithValue(InMemorySyncStateStore()),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ import 'package:relationship_saver/features/people/people_view.dart';
|
|||||||
import 'package:relationship_saver/features/reminders/reminders_view.dart';
|
import 'package:relationship_saver/features/reminders/reminders_view.dart';
|
||||||
import 'package:relationship_saver/features/settings/settings_view.dart';
|
import 'package:relationship_saver/features/settings/settings_view.dart';
|
||||||
import 'package:relationship_saver/features/signals/signals_view.dart';
|
import 'package:relationship_saver/features/signals/signals_view.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_view.dart';
|
import 'package:relationship_saver/features/sync/sync_view.dart';
|
||||||
import 'package:relationship_saver/integrations/backend/backend_gateway_fake.dart';
|
import 'package:relationship_saver/integrations/backend/backend_gateway_fake.dart';
|
||||||
import 'package:relationship_saver/integrations/backend/backend_gateway_provider.dart';
|
import 'package:relationship_saver/integrations/backend/backend_gateway_provider.dart';
|
||||||
@@ -102,6 +104,7 @@ Future<void> pumpResponsive(
|
|||||||
tokenStoreProvider.overrideWithValue(InMemoryTokenStore()),
|
tokenStoreProvider.overrideWithValue(InMemoryTokenStore()),
|
||||||
backendGatewayProvider.overrideWithValue(BackendGatewayFake()),
|
backendGatewayProvider.overrideWithValue(BackendGatewayFake()),
|
||||||
localDataStoreProvider.overrideWithValue(InMemoryLocalDataStore()),
|
localDataStoreProvider.overrideWithValue(InMemoryLocalDataStore()),
|
||||||
|
syncStateStoreProvider.overrideWithValue(InMemorySyncStateStore()),
|
||||||
],
|
],
|
||||||
child: MaterialApp(theme: AppTheme.light(), home: home),
|
child: MaterialApp(theme: AppTheme.light(), home: home),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:relationship_saver/features/sync/storage/sync_state_store.dart';
|
||||||
|
import 'package:relationship_saver/features/sync/storage/sync_state_store_hive.dart';
|
||||||
|
import 'package:relationship_saver/features/sync/storage/sync_state_store_provider.dart';
|
||||||
|
import 'package:relationship_saver/features/sync/storage/sync_state_store_shared_prefs.dart';
|
||||||
|
import 'package:relationship_saver/features/sync/sync_queue_repository.dart';
|
||||||
|
import 'package:relationship_saver/integrations/backend/models/backend_models.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
setUp(() {
|
||||||
|
SharedPreferences.setMockInitialValues(<String, Object>{});
|
||||||
|
});
|
||||||
|
|
||||||
|
test(
|
||||||
|
'migrates legacy shared prefs sync queue payload into hive store',
|
||||||
|
() async {
|
||||||
|
final SyncStateRecord legacyRecord = SyncStateRecord(
|
||||||
|
rawState: jsonEncode(<String, dynamic>{
|
||||||
|
'cursor': 'legacy-cursor',
|
||||||
|
'pendingChanges': <Map<String, dynamic>>[
|
||||||
|
ChangeEnvelope(
|
||||||
|
schemaVersion: 1,
|
||||||
|
entityType: 'person',
|
||||||
|
entityId: 'legacy-person',
|
||||||
|
op: ChangeOperation.upsert,
|
||||||
|
modifiedAt: DateTime(2026, 2, 15, 10).toUtc(),
|
||||||
|
clientMutationId: 'legacy-cm-1',
|
||||||
|
payload: const <String, dynamic>{'name': 'Legacy'},
|
||||||
|
).toJson(),
|
||||||
|
],
|
||||||
|
'lastSyncAt': null,
|
||||||
|
'lastAttemptAt': null,
|
||||||
|
'lastError': null,
|
||||||
|
'lastRejected': <Map<String, dynamic>>[],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
final SharedPrefsSyncStateStore legacyStore = SharedPrefsSyncStateStore();
|
||||||
|
await legacyStore.write(rawState: legacyRecord.rawState);
|
||||||
|
|
||||||
|
final _FakeHiveSyncStore hiveStore = _FakeHiveSyncStore();
|
||||||
|
final ProviderContainer container = ProviderContainer(
|
||||||
|
overrides: [syncStateStoreProvider.overrideWithValue(hiveStore)],
|
||||||
|
);
|
||||||
|
addTearDown(container.dispose);
|
||||||
|
|
||||||
|
final state = await container.read(syncQueueRepositoryProvider.future);
|
||||||
|
|
||||||
|
expect(state.cursor, 'legacy-cursor');
|
||||||
|
expect(state.pendingChanges.length, 1);
|
||||||
|
expect(state.pendingChanges.first.clientMutationId, 'legacy-cm-1');
|
||||||
|
|
||||||
|
final SyncStateRecord? migrated = await hiveStore.read();
|
||||||
|
expect(migrated, isNotNull);
|
||||||
|
|
||||||
|
final SyncStateRecord? legacyAfter = await legacyStore.read();
|
||||||
|
expect(legacyAfter, isNull);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FakeHiveSyncStore extends HiveSyncStateStore {
|
||||||
|
SyncStateRecord? _record;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> clear() async {
|
||||||
|
_record = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<SyncStateRecord?> read() async => _record;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> write({required String rawState}) async {
|
||||||
|
_record = SyncStateRecord(rawState: rawState);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,8 @@ import 'package:flutter_test/flutter_test.dart';
|
|||||||
import 'package:relationship_saver/features/local/local_repository.dart';
|
import 'package:relationship_saver/features/local/local_repository.dart';
|
||||||
import 'package:relationship_saver/features/local/storage/local_data_store_in_memory.dart';
|
import 'package:relationship_saver/features/local/storage/local_data_store_in_memory.dart';
|
||||||
import 'package:relationship_saver/features/local/storage/local_data_store_provider.dart';
|
import 'package:relationship_saver/features/local/storage/local_data_store_provider.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_coordinator.dart';
|
import 'package:relationship_saver/features/sync/sync_coordinator.dart';
|
||||||
import 'package:relationship_saver/features/sync/sync_queue_repository.dart';
|
import 'package:relationship_saver/features/sync/sync_queue_repository.dart';
|
||||||
import 'package:relationship_saver/features/sync/sync_state.dart';
|
import 'package:relationship_saver/features/sync/sync_state.dart';
|
||||||
@@ -21,6 +23,7 @@ void main() {
|
|||||||
overrides: [
|
overrides: [
|
||||||
backendGatewayProvider.overrideWithValue(_TestGateway()),
|
backendGatewayProvider.overrideWithValue(_TestGateway()),
|
||||||
localDataStoreProvider.overrideWithValue(InMemoryLocalDataStore()),
|
localDataStoreProvider.overrideWithValue(InMemoryLocalDataStore()),
|
||||||
|
syncStateStoreProvider.overrideWithValue(InMemorySyncStateStore()),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
addTearDown(container.dispose);
|
addTearDown(container.dispose);
|
||||||
@@ -83,6 +86,7 @@ void main() {
|
|||||||
_TestGateway(pullResult: pullResult),
|
_TestGateway(pullResult: pullResult),
|
||||||
),
|
),
|
||||||
localDataStoreProvider.overrideWithValue(InMemoryLocalDataStore()),
|
localDataStoreProvider.overrideWithValue(InMemoryLocalDataStore()),
|
||||||
|
syncStateStoreProvider.overrideWithValue(InMemorySyncStateStore()),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
addTearDown(container.dispose);
|
addTearDown(container.dispose);
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:flutter_test/flutter_test.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/features/sync/sync_queue_repository.dart';
|
||||||
import 'package:relationship_saver/integrations/backend/models/backend_models.dart';
|
import 'package:relationship_saver/integrations/backend/models/backend_models.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
setUp(() {
|
|
||||||
SharedPreferences.setMockInitialValues(<String, Object>{});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('enqueue persists and can be reloaded', () async {
|
test('enqueue persists and can be reloaded', () async {
|
||||||
final ProviderContainer container = ProviderContainer();
|
final InMemorySyncStateStore store = InMemorySyncStateStore();
|
||||||
|
final ProviderContainer container = ProviderContainer(
|
||||||
|
overrides: [syncStateStoreProvider.overrideWithValue(store)],
|
||||||
|
);
|
||||||
|
|
||||||
await container
|
await container
|
||||||
.read(syncQueueRepositoryProvider.notifier)
|
.read(syncQueueRepositoryProvider.notifier)
|
||||||
@@ -20,7 +20,9 @@ void main() {
|
|||||||
|
|
||||||
container.dispose();
|
container.dispose();
|
||||||
|
|
||||||
final ProviderContainer reloaded = ProviderContainer();
|
final ProviderContainer reloaded = ProviderContainer(
|
||||||
|
overrides: [syncStateStoreProvider.overrideWithValue(store)],
|
||||||
|
);
|
||||||
addTearDown(reloaded.dispose);
|
addTearDown(reloaded.dispose);
|
||||||
final SyncStateData reloadedState = await _readState(reloaded);
|
final SyncStateData reloadedState = await _readState(reloaded);
|
||||||
expect(reloadedState.pending.length, 1);
|
expect(reloadedState.pending.length, 1);
|
||||||
@@ -30,7 +32,11 @@ void main() {
|
|||||||
test(
|
test(
|
||||||
'applyPushResult removes completed mutation ids and tracks rejections',
|
'applyPushResult removes completed mutation ids and tracks rejections',
|
||||||
() async {
|
() async {
|
||||||
final ProviderContainer container = ProviderContainer();
|
final ProviderContainer container = ProviderContainer(
|
||||||
|
overrides: [
|
||||||
|
syncStateStoreProvider.overrideWithValue(InMemorySyncStateStore()),
|
||||||
|
],
|
||||||
|
);
|
||||||
addTearDown(container.dispose);
|
addTearDown(container.dispose);
|
||||||
|
|
||||||
final SyncQueueRepository notifier = container.read(
|
final SyncQueueRepository notifier = container.read(
|
||||||
|
|||||||
Reference in New Issue
Block a user