100 lines
2.8 KiB
Dart
100 lines
2.8 KiB
Dart
import 'dart:async';
|
|
import 'sync_models.dart';
|
|
|
|
/// Abstract contract for synchronization providers (e.g. Firebase, GitHub, Supabase).
|
|
/// Implementations must not leak provider-specific SDK types into the core sync engine.
|
|
abstract class SyncProvider {
|
|
/// Unique identifier of the provider (e.g. 'firebase', 'github', 'fake')
|
|
String get providerId;
|
|
|
|
/// Whether the provider backend and network are currently available
|
|
Future<bool> isAvailable();
|
|
|
|
/// Whether this provider requires an active user authentication credential
|
|
Future<bool> requiresAuthentication();
|
|
|
|
/// Performs the upload of the local ride referenced by [item].
|
|
/// Must return a deterministic [SyncResult].
|
|
Future<SyncResult> uploadRide(SyncItem item);
|
|
}
|
|
|
|
/// Simulated behavior modes for testing the synchronization engine deterministically
|
|
enum FakeSyncBehavior {
|
|
success,
|
|
temporaryFailure,
|
|
permanentFailure,
|
|
authRequired,
|
|
offline,
|
|
}
|
|
|
|
/// Deterministic, in-memory fake synchronization provider for testing
|
|
class FakeSyncProvider implements SyncProvider {
|
|
@override
|
|
final String providerId;
|
|
|
|
FakeSyncBehavior behavior;
|
|
int delayMs;
|
|
final List<SyncItem> uploadedItems = [];
|
|
final Map<int, int> uploadAttemptsPerRide = {};
|
|
|
|
FakeSyncProvider({
|
|
this.providerId = 'fake',
|
|
this.behavior = FakeSyncBehavior.success,
|
|
this.delayMs = 0,
|
|
});
|
|
|
|
@override
|
|
Future<bool> isAvailable() async {
|
|
if (behavior == FakeSyncBehavior.offline) return false;
|
|
return true;
|
|
}
|
|
|
|
@override
|
|
Future<bool> requiresAuthentication() async {
|
|
return behavior == FakeSyncBehavior.authRequired;
|
|
}
|
|
|
|
@override
|
|
Future<SyncResult> uploadRide(SyncItem item) async {
|
|
if (delayMs > 0) {
|
|
await Future<void>.delayed(Duration(milliseconds: delayMs));
|
|
}
|
|
|
|
uploadAttemptsPerRide[item.localRideId] =
|
|
(uploadAttemptsPerRide[item.localRideId] ?? 0) + 1;
|
|
|
|
switch (behavior) {
|
|
case FakeSyncBehavior.success:
|
|
uploadedItems.add(item);
|
|
return SyncResult.success(
|
|
remoteId: 'remote_${item.localRideId}_${DateTime.now().millisecondsSinceEpoch}',
|
|
);
|
|
|
|
case FakeSyncBehavior.temporaryFailure:
|
|
return SyncResult.temporaryFailure(
|
|
'Simulated temporary network/server error (HTTP 503)',
|
|
);
|
|
|
|
case FakeSyncBehavior.permanentFailure:
|
|
return SyncResult.permanentFailure(
|
|
'Simulated unrecoverable payload or validation error (HTTP 422)',
|
|
);
|
|
|
|
case FakeSyncBehavior.authRequired:
|
|
return SyncResult.authRequired(
|
|
message: 'Simulated authentication expired or required (HTTP 401)',
|
|
);
|
|
|
|
case FakeSyncBehavior.offline:
|
|
return SyncResult.temporaryFailure(
|
|
'Simulated offline network unreachable',
|
|
);
|
|
}
|
|
}
|
|
|
|
void reset() {
|
|
uploadedItems.clear();
|
|
uploadAttemptsPerRide.clear();
|
|
}
|
|
}
|