fix: route completed rides through durable sync outbox
This commit is contained in:
parent
57e5508878
commit
32531e477b
|
|
@ -12,7 +12,6 @@ import '../../services/audio_alert_service.dart';
|
|||
import '../records/records_service.dart';
|
||||
import '../garage/garage_providers.dart';
|
||||
import '../../services/field_test/field_test_logger.dart';
|
||||
import '../../services/github/github_sync_service.dart';
|
||||
|
||||
enum RideState { idle, ready, riding, paused, finished }
|
||||
|
||||
|
|
@ -377,14 +376,6 @@ class RideStateMachine extends StateNotifier<RideSession> {
|
|||
}
|
||||
} catch (_) {}
|
||||
|
||||
// Offline-first background synchronization to GitHub (if enabled)
|
||||
final completedRideId = state.dbId;
|
||||
if (completedRideId != null) {
|
||||
try {
|
||||
ref.read(githubSyncServiceProvider).queueRideForUpload(completedRideId);
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
state = state.copyWith(
|
||||
state: RideState.finished,
|
||||
healthStatus: RecordingHealthStatus.recordingComplete,
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import 'firebase_options.dart';
|
|||
import 'core/theme/theme.dart';
|
||||
import 'core/router.dart';
|
||||
import 'features/records/records_service.dart';
|
||||
import 'services/sync/sync_providers.dart';
|
||||
import 'services/sync/sync_observer.dart';
|
||||
|
||||
Future<void> main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
|
@ -47,9 +47,9 @@ class _MotorcycleDashAppState extends ConsumerState<MotorcycleDashApp> {
|
|||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
// Eagerly construct the sync manager so persisted outbox work is
|
||||
// reconciled immediately at app startup, not only after visiting a sync UI.
|
||||
ref.read(syncManagerProvider);
|
||||
// Eagerly construct the sync observer so completed rides enter the durable
|
||||
// outbox at startup scope, not only after visiting a sync UI.
|
||||
ref.read(syncObserverProvider);
|
||||
_initDeepLinks();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:drift/native.dart';
|
||||
|
|
@ -428,5 +430,102 @@ void main() {
|
|||
observer.onRideSessionChanged(prevSession, nextSession);
|
||||
expect(outbox.count, equals(1));
|
||||
});
|
||||
|
||||
test('Stage 2B: production startup instantiates the ride sync observer', () {
|
||||
final mainSource = File('lib/main.dart').readAsStringSync();
|
||||
|
||||
expect(mainSource, contains("import 'services/sync/sync_observer.dart';"));
|
||||
expect(mainSource, contains('ref.read(syncObserverProvider);'));
|
||||
});
|
||||
|
||||
test('Stage 2B: completed ride transition enters durable firebase outbox', () async {
|
||||
final outbox = SyncOutbox(prefs);
|
||||
final provider = FakeSyncProvider(
|
||||
providerId: 'firebase',
|
||||
behavior: FakeSyncBehavior.authRequired,
|
||||
);
|
||||
final manager = SyncManager(
|
||||
outbox,
|
||||
initialProviders: [provider],
|
||||
defaultProviderId: 'firebase',
|
||||
);
|
||||
final observer = SyncObserver(manager);
|
||||
addTearDown(manager.dispose);
|
||||
|
||||
final previous = RideSession(
|
||||
dbId: 901,
|
||||
state: RideState.riding,
|
||||
healthStatus: RecordingHealthStatus.recordingActive,
|
||||
);
|
||||
final completed = previous.copyWith(
|
||||
state: RideState.finished,
|
||||
healthStatus: RecordingHealthStatus.recordingComplete,
|
||||
);
|
||||
|
||||
observer.onRideSessionChanged(previous, completed);
|
||||
await Future<void>.delayed(Duration.zero);
|
||||
|
||||
final item = outbox.getItem(901, 'firebase');
|
||||
expect(item, isNotNull);
|
||||
expect(item?.localRideId, 901);
|
||||
expect(item?.providerId, 'firebase');
|
||||
expect(
|
||||
prefs.getString(outbox.storageKey),
|
||||
contains('"localRideId":901'),
|
||||
);
|
||||
});
|
||||
|
||||
test('Stage 2B: automatic completion does not invoke legacy queue path', () {
|
||||
final rideStateMachineSource =
|
||||
File('lib/features/rides/ride_state_machine.dart').readAsStringSync();
|
||||
final legacyServiceSource =
|
||||
File('lib/services/github/github_sync_service.dart').readAsStringSync();
|
||||
|
||||
expect(rideStateMachineSource, isNot(contains('queueRideForUpload')));
|
||||
expect(
|
||||
rideStateMachineSource,
|
||||
isNot(contains('services/github/github_sync_service.dart')),
|
||||
);
|
||||
expect(legacyServiceSource, contains('queueRideForUpload'));
|
||||
expect(legacyServiceSource, contains('uploadRideToGitHub'));
|
||||
});
|
||||
|
||||
test('Stage 2B: duplicate completed emissions keep one active outbox item', () async {
|
||||
final outbox = SyncOutbox(prefs);
|
||||
final manager = SyncManager(
|
||||
outbox,
|
||||
initialProviders: [
|
||||
FakeSyncProvider(
|
||||
providerId: 'firebase',
|
||||
behavior: FakeSyncBehavior.authRequired,
|
||||
),
|
||||
],
|
||||
defaultProviderId: 'firebase',
|
||||
);
|
||||
final observer = SyncObserver(manager);
|
||||
addTearDown(manager.dispose);
|
||||
|
||||
final riding = RideSession(
|
||||
dbId: 902,
|
||||
state: RideState.riding,
|
||||
healthStatus: RecordingHealthStatus.recordingActive,
|
||||
);
|
||||
final finished = riding.copyWith(
|
||||
state: RideState.finished,
|
||||
healthStatus: RecordingHealthStatus.recordingComplete,
|
||||
);
|
||||
|
||||
observer.onRideSessionChanged(riding, finished);
|
||||
observer.onRideSessionChanged(riding, finished);
|
||||
observer.onRideSessionChanged(finished, finished);
|
||||
await Future<void>.delayed(Duration.zero);
|
||||
|
||||
expect(outbox.count, 1);
|
||||
expect(
|
||||
outbox.getAllItems().where((item) => item.localRideId == 902),
|
||||
hasLength(1),
|
||||
);
|
||||
expect(outbox.getItem(902, 'firebase'), isNotNull);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue