test: add persistence and offline sync validation matrix
This commit is contained in:
parent
fcf90ec9c0
commit
a67316479d
|
|
@ -0,0 +1,105 @@
|
|||
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<bool> isAvailable() async => available;
|
||||
|
||||
@override
|
||||
Future<bool> requiresAuthentication() async => !authenticated;
|
||||
|
||||
@override
|
||||
Future<SyncResult> 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);
|
||||
});
|
||||
});
|
||||
}
|
||||
Loading…
Reference in New Issue