MotorSignal-App/test/sync_persistence_validation...

106 lines
4.0 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<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);
});
});
}