import 'package:flutter_test/flutter_test.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:motorcycle_dash/models/telemetry_sample.dart'; import 'package:motorcycle_dash/services/validation/telemetry_validator.dart'; import 'package:motorcycle_dash/services/calibration_service.dart'; import 'package:motorcycle_dash/features/records/records_service.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); group('Phase 8: Telemetry Validator Tests', () { late TelemetryValidator validator; setUp(() { validator = TelemetryValidator(); }); test('Valid cruising sample passes with high confidence', () { final now = DateTime.now(); final sample = TelemetrySample( timestamp: now, latitude: 37.7749, longitude: -122.4194, speed: 65.0, gpsAccuracy: 3.5, leanAngle: 12.0, pitchAngle: 0.5, accelX: 0.2, accelY: 0.1, accelZ: 9.8, longitudinalG: 0.02, lateralG: 0.21, ); final quality = validator.validate(sample); expect(quality.isValid, isTrue); expect(quality.status, equals(ValidationStatus.valid)); expect(quality.reasons, isEmpty); expect(quality.gpsConfidence, equals(1.0)); expect(quality.overallConfidence, greaterThanOrEqualTo(0.90)); expect(quality.isStationary, isFalse); }); test('Stationary vehicle correctly marked with stationary flag', () { final sample = TelemetrySample( timestamp: DateTime.now(), speed: 0.0, gpsAccuracy: 4.0, leanAngle: 0.0, longitudinalG: 0.0, lateralG: 0.0, ); final quality = validator.validate(sample); expect(quality.isValid, isTrue); expect(quality.isStationary, isTrue); expect(quality.isAccelerating, isFalse); expect(quality.isBraking, isFalse); }); test('Impossible speed jump > 220 MPH is rejected with reason', () { final sample = TelemetrySample( timestamp: DateTime.now(), speed: 345.0, gpsAccuracy: 10.0, ); final quality = validator.validate(sample); expect(quality.isRejected, isTrue); expect(quality.status, equals(ValidationStatus.rejected)); expect(quality.reasons.any((r) => r.contains('Impossible vehicle speed')), isTrue); expect(quality.speedConfidence, equals(0.0)); expect(validator.rejectedCount, equals(1)); }); test('NaN numeric values are rejected immediately', () { final sample = TelemetrySample( timestamp: DateTime.now(), speed: double.nan, latitude: 37.77, ); final quality = validator.validate(sample); expect(quality.isRejected, isTrue); expect(quality.overallConfidence, equals(0.0)); expect(quality.reasons.any((r) => r.contains('NaN or Infinity')), isTrue); }); test('Degraded GPS accuracy reduces confidence and marks sample suspect', () { final sample = TelemetrySample( timestamp: DateTime.now(), speed: 45.0, gpsAccuracy: 48.0, // poor GPS lock ); final quality = validator.validate(sample); expect(quality.isSuspect, isTrue); expect(quality.gpsConfidence, lessThan(0.50)); expect(quality.reasons.any((r) => r.contains('Poor GPS accuracy')), isTrue); expect(validator.suspectCount, equals(1)); }); test('Extreme roll rate (>180 deg/sec) flags suspect lean', () { final t0 = DateTime.now(); validator.validate(TelemetrySample( timestamp: t0, speed: 50.0, leanAngle: 0.0, )); // 50ms later, jump from 0 deg to 30 deg lean (600 deg/sec impossible flick) final quality = validator.validate(TelemetrySample( timestamp: t0.add(const Duration(milliseconds: 50)), speed: 50.0, leanAngle: 30.0, )); expect(quality.isSuspect, isTrue); expect(quality.reasons.any((r) => r.contains('Excessive roll rate')), isTrue); }); }); group('Phase 8: Motorcycle-Specific Calibration Tests', () { test('Motorcycle A and Motorcycle B have isolated calibration offsets', () async { SharedPreferences.setMockInitialValues({}); final calibrationService = CalibrationService(); // Calibrate Bike 1 (e.g. Yamaha R6 in portrait mount) await calibrationService.saveCalibrationOffsets(2.5, -1.2, motorcycleId: 1); // Calibrate Bike 2 (e.g. BMW GS in landscape mount) await calibrationService.saveCalibrationOffsets(-4.0, 3.8, motorcycleId: 2); // Load Bike 1 final bike1Offsets = await calibrationService.loadCalibrationOffsets(motorcycleId: 1); expect(bike1Offsets['pitch'], equals(2.5)); expect(bike1Offsets['roll'], equals(-1.2)); // Load Bike 2 final bike2Offsets = await calibrationService.loadCalibrationOffsets(motorcycleId: 2); expect(bike2Offsets['pitch'], equals(-4.0)); expect(bike2Offsets['roll'], equals(3.8)); }); }); group('Phase 8: Personal Record Integrity Tests', () { test('GPS multipath glitch is rejected by confidence and sanity limits', () async { SharedPreferences.setMockInitialValues({}); final prefs = await SharedPreferences.getInstance(); final recordsService = RecordsService(prefs); // Legitimate record await recordsService.checkAndSaveRecords( const PersonalRecords(topSpeedMph: 112.0), null, confidence: 0.95, ); expect(recordsService.state.topSpeedMph, equals(112.0)); // Glitched GPS spike with low confidence is discarded await recordsService.checkAndSaveRecords( const PersonalRecords(topSpeedMph: 185.0), null, confidence: 0.40, // low confidence ); expect(recordsService.state.topSpeedMph, equals(112.0)); // Impossible speed > 220 is discarded even if confidence claimed high await recordsService.checkAndSaveRecords( const PersonalRecords(topSpeedMph: 350.0), null, confidence: 1.0, ); expect(recordsService.state.topSpeedMph, equals(112.0)); }); }); }