113 lines
4.3 KiB
Dart
113 lines
4.3 KiB
Dart
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<bool> isRideConfirmedInCloud(String remoteRideId) async => false;
|
|
|
|
@override
|
|
Future<CloudConfirmationResult> verifyCloudConfirmation(String remoteRideId) async =>
|
|
CloudConfirmationResult.missing(remoteRideId: remoteRideId);
|
|
|
|
@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);
|
|
});
|
|
});
|
|
}
|