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 isAvailable(); /// Whether this provider requires an active user authentication credential Future requiresAuthentication(); /// Performs the upload of the local ride referenced by [item]. /// Must return a deterministic [SyncResult]. Future uploadRide(SyncItem item); /// Checks if the ride with [remoteRideId] is already confirmed uploaded in the cloud. /// Default implementation returns false. Future isRideConfirmedInCloud(String remoteRideId) async => false; } /// 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 uploadedItems = []; final Map uploadAttemptsPerRide = {}; final Set confirmedRemoteRideIds = {}; FakeSyncProvider({ this.providerId = 'fake', this.behavior = FakeSyncBehavior.success, this.delayMs = 0, }); @override Future isAvailable() async { if (behavior == FakeSyncBehavior.offline) return false; return true; } @override Future requiresAuthentication() async { return behavior == FakeSyncBehavior.authRequired; } @override Future isRideConfirmedInCloud(String remoteRideId) async { return confirmedRemoteRideIds.contains(remoteRideId); } @override Future uploadRide(SyncItem item) async { if (delayMs > 0) { await Future.delayed(Duration(milliseconds: delayMs)); } uploadAttemptsPerRide[item.localRideId] = (uploadAttemptsPerRide[item.localRideId] ?? 0) + 1; switch (behavior) { case FakeSyncBehavior.success: uploadedItems.add(item); final remoteId = 'remote_${item.localRideId}_${DateTime.now().millisecondsSinceEpoch}'; confirmedRemoteRideIds.add(remoteId); return SyncResult.success( remoteId: remoteId, ); 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(); confirmedRemoteRideIds.clear(); } }