fix: assign stable cloud ride identity at creation
This commit is contained in:
parent
32531e477b
commit
68c0bda344
|
|
@ -142,10 +142,23 @@ class AppDatabase extends _$AppDatabase {
|
|||
return created;
|
||||
}
|
||||
|
||||
Future<int> startRide() async {
|
||||
final id = await into(rides).insert(RidesCompanion.insert(startTime: DateTime.now()));
|
||||
await ensureCloudRideId(id);
|
||||
return id;
|
||||
Future<int> startRide({
|
||||
DateTime? startTime,
|
||||
int? motorcycleId,
|
||||
int? motorcycleYear,
|
||||
String? motorcycleMake,
|
||||
String? motorcycleModel,
|
||||
String? motorcycleVariant,
|
||||
}) async {
|
||||
return into(rides).insert(RidesCompanion.insert(
|
||||
cloudRideId: Value(_newCloudRideId()),
|
||||
startTime: startTime ?? DateTime.now(),
|
||||
motorcycleId: Value(motorcycleId),
|
||||
motorcycleYear: Value(motorcycleYear),
|
||||
motorcycleMake: Value(motorcycleMake),
|
||||
motorcycleModel: Value(motorcycleModel),
|
||||
motorcycleVariant: Value(motorcycleVariant),
|
||||
));
|
||||
}
|
||||
|
||||
Future<void> endRide(int rideId, RidesCompanion data) async {
|
||||
|
|
|
|||
|
|
@ -211,20 +211,21 @@ class RideStateMachine extends StateNotifier<RideSession> {
|
|||
} catch (_) {}
|
||||
|
||||
final defaultBike = ref.read(defaultMotorcycleProvider);
|
||||
final id = await db.into(db.rides).insert(RidesCompanion.insert(
|
||||
startTime: DateTime.now(),
|
||||
motorcycleId: drift.Value(defaultBike?.id),
|
||||
motorcycleYear: drift.Value(defaultBike?.year),
|
||||
motorcycleMake: drift.Value(defaultBike?.makeName),
|
||||
motorcycleModel: drift.Value(defaultBike?.modelName),
|
||||
motorcycleVariant: drift.Value(defaultBike?.variantName),
|
||||
));
|
||||
final startTime = DateTime.now();
|
||||
final id = await db.startRide(
|
||||
startTime: startTime,
|
||||
motorcycleId: defaultBike?.id,
|
||||
motorcycleYear: defaultBike?.year,
|
||||
motorcycleMake: defaultBike?.makeName,
|
||||
motorcycleModel: defaultBike?.modelName,
|
||||
motorcycleVariant: defaultBike?.variantName,
|
||||
);
|
||||
|
||||
state = state.copyWith(
|
||||
dbId: id,
|
||||
state: RideState.riding,
|
||||
healthStatus: RecordingHealthStatus.recordingActive,
|
||||
startTime: DateTime.now(),
|
||||
startTime: startTime,
|
||||
tripDistanceMiles: 0,
|
||||
topSpeedMph: 0,
|
||||
maxLeanLeft: 0,
|
||||
|
|
|
|||
|
|
@ -35,6 +35,22 @@ void main() {
|
|||
expect(second, first);
|
||||
});
|
||||
|
||||
test('ensureCloudRideId repairs legacy null ride identity', () async {
|
||||
final db = AppDatabase.forTesting(NativeDatabase.memory());
|
||||
addTearDown(db.close);
|
||||
|
||||
final legacyRideId = await db.into(db.rides).insert(
|
||||
RidesCompanion.insert(startTime: DateTime.utc(2026, 9, 1, 10)),
|
||||
);
|
||||
expect(await db.getCloudRideId(legacyRideId), isNull);
|
||||
|
||||
final repaired = await db.ensureCloudRideId(legacyRideId);
|
||||
|
||||
expect(repaired, startsWith('ride_'));
|
||||
expect(await db.getCloudRideId(legacyRideId), repaired);
|
||||
expect(await db.ensureCloudRideId(legacyRideId), repaired);
|
||||
});
|
||||
|
||||
test('real v5 rides migrate to their legacy MotorCloud identity', () async {
|
||||
final connection = NativeDatabase.memory(setup: (rawDb) {
|
||||
rawDb.execute('''
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import 'package:motorcycle_dash/services/field_test/field_test_logger.dart';
|
|||
import 'package:motorcycle_dash/services/validation/telemetry_validator.dart';
|
||||
import 'package:motorcycle_dash/features/rides/ride_state_machine.dart';
|
||||
import 'package:motorcycle_dash/features/records/records_service.dart';
|
||||
import 'package:motorcycle_dash/features/garage/garage_providers.dart';
|
||||
|
||||
class MockAudioAlertService extends Fake implements AudioAlertService {
|
||||
final List<String> spokenMessages = [];
|
||||
|
|
@ -177,6 +178,86 @@ void main() {
|
|||
expect(events.any((e) => e.eventType.contains('START') || e.eventType.contains('RIDE')), isTrue);
|
||||
});
|
||||
|
||||
test('RideStateMachine assigns stable cloud identity at ride creation', () async {
|
||||
final bike = GarageMotorcycle(
|
||||
id: 7,
|
||||
year: 2024,
|
||||
makeId: 'triumph',
|
||||
makeName: 'Triumph',
|
||||
modelId: 'street-triple',
|
||||
modelName: 'Street Triple',
|
||||
variantName: 'RS',
|
||||
isDefault: true,
|
||||
dateAdded: DateTime.utc(2026, 9, 1),
|
||||
lastUpdated: DateTime.utc(2026, 9, 1),
|
||||
);
|
||||
final container = ProviderContainer(
|
||||
overrides: [
|
||||
sharedPreferencesProvider.overrideWithValue(prefs),
|
||||
databaseProvider.overrideWithValue(db),
|
||||
telemetryServiceProvider.overrideWithValue(fakeTelemetry),
|
||||
audioAlertServiceProvider.overrideWithValue(mockAudio),
|
||||
fieldTestLoggerProvider.overrideWithValue(logger),
|
||||
defaultMotorcycleProvider.overrideWithValue(bike),
|
||||
],
|
||||
);
|
||||
addTearDown(container.dispose);
|
||||
|
||||
final notifier = container.read(rideStateMachineProvider.notifier);
|
||||
await notifier.startRide();
|
||||
|
||||
final session = container.read(rideStateMachineProvider);
|
||||
expect(session.dbId, isNotNull);
|
||||
|
||||
final ride = await (db.select(db.rides)..where((r) => r.id.equals(session.dbId!))).getSingle();
|
||||
expect(ride.cloudRideId, isNotNull);
|
||||
expect(ride.cloudRideId, startsWith('ride_'));
|
||||
expect(ride.motorcycleId, bike.id);
|
||||
expect(ride.motorcycleYear, bike.year);
|
||||
expect(ride.motorcycleMake, bike.makeName);
|
||||
expect(ride.motorcycleModel, bike.modelName);
|
||||
expect(ride.motorcycleVariant, bike.variantName);
|
||||
|
||||
final ensured = await db.ensureCloudRideId(ride.id);
|
||||
expect(ensured, ride.cloudRideId);
|
||||
expect(await db.getCloudRideId(ride.id), ride.cloudRideId);
|
||||
});
|
||||
|
||||
test('Interrupted ride recovery preserves existing cloud identity', () async {
|
||||
final rideId = await db.startRide(startTime: DateTime.utc(2026, 9, 2, 12));
|
||||
final cloudRideId = await db.getCloudRideId(rideId);
|
||||
expect(cloudRideId, isNotNull);
|
||||
|
||||
await db.into(db.telemetrySamples).insert(
|
||||
TelemetrySamplesCompanion.insert(
|
||||
rideId: rideId,
|
||||
timestamp: DateTime.utc(2026, 9, 2, 12, 5),
|
||||
),
|
||||
);
|
||||
|
||||
final container = ProviderContainer(
|
||||
overrides: [
|
||||
sharedPreferencesProvider.overrideWithValue(prefs),
|
||||
databaseProvider.overrideWithValue(db),
|
||||
telemetryServiceProvider.overrideWithValue(fakeTelemetry),
|
||||
audioAlertServiceProvider.overrideWithValue(mockAudio),
|
||||
fieldTestLoggerProvider.overrideWithValue(logger),
|
||||
],
|
||||
);
|
||||
addTearDown(container.dispose);
|
||||
|
||||
container.read(rideStateMachineProvider.notifier);
|
||||
await Future<void>.delayed(const Duration(milliseconds: 50));
|
||||
|
||||
final recovered = await (db.select(db.rides)..where((r) => r.id.equals(rideId))).getSingle();
|
||||
expect(
|
||||
recovered.endTime!.isAtSameMomentAs(DateTime.utc(2026, 9, 2, 12, 5)),
|
||||
isTrue,
|
||||
);
|
||||
expect(recovered.cloudRideId, cloudRideId);
|
||||
expect(await db.ensureCloudRideId(rideId), cloudRideId);
|
||||
});
|
||||
|
||||
test('Motion Safeguard: Moving vehicle auto-promotes READY state to active recording', () async {
|
||||
final container = ProviderContainer(
|
||||
overrides: [
|
||||
|
|
|
|||
Loading…
Reference in New Issue