MotorSignal-App/test/maintenance_v102_test.dart

217 lines
7.8 KiB
Dart

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.png'));
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
final result = await syncService.uploadRide(rideId);
expect(result.success, isTrue);
expect(result.commitSha, equals('c0ffee1234567890'));
expect(syncService.isRideUploaded(rideId), isTrue);
expect(syncService.getRideCommitSha(rideId), 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.uploadRide(rideId);
expect(result.success, isFalse);
expect(syncService.state, equals(GitHubUploadState.failed));
expect(result.message.toLowerCase(), contains('failed'));
});
});
}