import 'dart:convert'; import 'package:flutter_test/flutter_test.dart'; import 'package:http/http.dart' as http; import 'package:http/testing.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:drift/drift.dart' as drift; import 'package:drift/native.dart'; import 'package:motorcycle_dash/core/app_info.dart'; import 'package:motorcycle_dash/core/version/app_identity.dart'; import 'package:motorcycle_dash/database/database.dart'; import 'package:motorcycle_dash/features/rides/ride_state_machine.dart'; import 'package:motorcycle_dash/models/recording_health_status.dart'; import 'package:motorcycle_dash/services/garage/motorcycle_image_service.dart'; import 'package:motorcycle_dash/services/github/github_auth_service.dart'; import 'package:motorcycle_dash/services/github/github_sync_service.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); group('MotorSignal v1.0.2+102 — Maintenance & Feature Verifications', () { late AppDatabase db; late SharedPreferences prefs; setUp(() async { SharedPreferences.setMockInitialValues({}); prefs = await SharedPreferences.getInstance(); db = AppDatabase.forTesting(NativeDatabase.memory()); }); tearDown(() async { await db.close(); }); test('1. Application Versioning & Identity Verification', () { final identity = AppIdentity.current(); expect(identity.semanticVersion, equals('1.0.2')); expect(identity.buildNumber, equals(102)); expect(AppInfo.fallbackVersion, equals('1.0.2+102')); }); test('2. Consecutive Ride Session Performance Reset Verification', () { // Simulate state machine with non-zero values from Ride #10 var session = RideSession( dbId: 10, state: RideState.finished, healthStatus: RecordingHealthStatus.recordingComplete, tripDistanceMiles: 1.59, topSpeedMph: 57.98, maxLeanLeft: 29.57, maxLeanRight: 22.37, maxAccelG: 0.45, maxBrakingG: -0.72, averageSpeedMph: 32.1, ); expect(session.maxLeanLeft, equals(29.57)); expect(session.maxLeanRight, equals(22.37)); // Starting new ride (Ride #11) must reset all metrics to 0 final newSession = session.copyWith( dbId: 11, state: RideState.riding, healthStatus: RecordingHealthStatus.recordingActive, startTime: DateTime.now(), tripDistanceMiles: 0, topSpeedMph: 0, maxLeanLeft: 0, maxLeanRight: 0, maxAccelG: 0, maxBrakingG: 0, averageSpeedMph: 0, ); expect(newSession.dbId, equals(11)); expect(newSession.maxLeanLeft, equals(0.0)); expect(newSession.maxLeanRight, equals(0.0)); expect(newSession.maxAccelG, equals(0.0)); expect(newSession.maxBrakingG, equals(0.0)); expect(newSession.topSpeedMph, equals(0.0)); expect(newSession.tripDistanceMiles, equals(0.0)); }); test('3. Garage Motorcycle Photographic Reference Resolution', () { final imageService = MotorcycleImageService(); // Exact 2001 Yamaha YZF-R6 lookup final r6Ref = imageService.getReferenceImage( make: 'Yamaha', model: 'YZF-R6', year: 2001, ); expect(r6Ref.hasImage, isTrue); expect(r6Ref.isPhoto, isTrue); expect(r6Ref.assetPath, contains('yamaha_yzf_r6_2001.jpg')); expect(r6Ref.caption, contains('2001 Yamaha YZF-R6')); // Fallback for uncataloged motorcycle final fallbackRef = imageService.getReferenceImage( make: 'Custom', model: 'Chopper', year: 1995, ); expect(fallbackRef.isPhoto, isFalse); expect(fallbackRef.hasImage, isFalse); expect(fallbackRef.attribution, contains('Schematic')); }); test('4. GitHub Auth Service Token & Connection Test', () async { final mockClient = MockClient((request) async { if (request.url.path.contains('repos/bponders12-cell/ride_logs')) { if (request.headers['Authorization'] == 'Bearer valid_token') { return http.Response(jsonEncode({ 'full_name': 'bponders12-cell/ride_logs', 'default_branch': 'main', 'permissions': {'push': true}, }), 200); } else { return http.Response(jsonEncode({'message': 'Bad credentials'}), 401); } } return http.Response('Not Found', 404); }); final auth = GitHubAuthService(prefs, mockClient); // Save token await auth.saveToken('valid_token'); expect(auth.getToken(), equals('valid_token')); // Test valid connection final resSuccess = await auth.testConnection(); expect(resSuccess['success'], isTrue); expect(resSuccess['canPush'], isTrue); // Test invalid token await auth.saveToken('invalid_token'); final resFail = await auth.testConnection(); expect(resFail['success'], isFalse); expect(resFail['message'], contains('401')); // Clear token await auth.clearToken(); expect(auth.getToken(), isNull); }); test('5. GitHub Sync Service Offline-First & Upload Flow', () async { // 1. Seed a test ride with telemetry and summary final rideId = await db.into(db.rides).insert(RidesCompanion.insert( startTime: DateTime(2026, 8, 30, 8, 0, 0), endTime: drift.Value(DateTime(2026, 8, 30, 8, 30, 0)), distanceMiles: const drift.Value(15.5), topSpeedMph: const drift.Value(100.8), maxLeanLeft: const drift.Value(22.4), maxLeanRight: const drift.Value(22.6), maxAccelG: const drift.Value(0.65), maxBrakingG: const drift.Value(-1.05), averageSpeed: const drift.Value(45.2), )); await db.into(db.fieldTestSummaries).insert(FieldTestSummariesCompanion.insert( rideId: rideId, startTime: DateTime(2026, 8, 30, 8, 0, 0), endTime: drift.Value(DateTime(2026, 8, 30, 8, 30, 0)), totalSamples: const drift.Value(17237), rideQualityScore: const drift.Value(100), rideQualityRating: const drift.Value('EXCELLENT'), )); final mockClient = MockClient((request) async { if (request.method == 'GET') { // File check before PUT return http.Response(jsonEncode({'message': 'Not Found'}), 404); } else if (request.method == 'PUT') { return http.Response(jsonEncode({ 'commit': {'sha': 'c0ffee1234567890'}, }), 201); } return http.Response('Bad request', 400); }); final auth = GitHubAuthService(prefs, mockClient); await auth.saveToken('test_pat'); final syncService = GitHubSyncService(db, auth, prefs, mockClient); // Perform upload via the explicit GitHub PAT export path final result = await syncService.uploadRideToGitHub(rideId); expect(result.success, isTrue); expect(result.commitSha, equals('c0ffee1234567890')); }); test('6. GitHub Sync Service Graceful Failure & Local Data Preservation', () async { // Seed a test ride final rideId = await db.into(db.rides).insert(RidesCompanion.insert( startTime: DateTime(2026, 8, 30, 8, 0, 0), )); // Simulate network error final mockClient = MockClient((request) async { throw http.ClientException('Connection reset by peer'); }); final auth = GitHubAuthService(prefs, mockClient); await auth.saveToken('test_pat'); final syncService = GitHubSyncService(db, auth, prefs, mockClient); final result = await syncService.uploadRideToGitHub(rideId); expect(result.success, isFalse); expect(syncService.state, equals(GitHubUploadState.failed)); expect(result.message.toLowerCase(), contains('failed')); }); }); }