MotorSignal-App/test/us_speed_camera_test.dart

289 lines
8.8 KiB
Dart

import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:motorcycle_dash/services/alerts/road_alert_models.dart';
import 'package:motorcycle_dash/services/alerts/road_alert_providers.dart';
import 'package:motorcycle_dash/services/alerts/us_speed_camera_database.dart';
import 'package:motorcycle_dash/services/alerts/osm_road_alert_provider.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
setUp(() async {
SharedPreferences.setMockInitialValues({});
});
group('UsSpeedCameraDatabase Tests', () {
final sampleData = [
{
'id': 'test_ny_001',
'lat': 40.7258,
'lng': -73.8617,
'road': 'Queens Blvd at 65th Rd',
'limit': 25,
'type': 'speed_camera',
'state': 'NY',
'city': 'Queens, NYC',
'direction': 120.0,
},
{
'id': 'test_il_001',
'lat': 41.8092,
'lng': -87.5889,
'road': 'DuSable Lake Shore Dr at 47th St',
'limit': 40,
'type': 'speed_camera',
'state': 'IL',
'city': 'Chicago',
'direction': 180.0,
},
{
'id': 'test_tn_001',
'lat': 35.4712,
'lng': -83.9234,
'road': 'US-129 Tail of the Dragon at Deals Gap',
'limit': 30,
'type': 'speed_trap',
'state': 'NC',
'city': 'Deals Gap',
'direction': 320.0,
},
{
'id': 'test_pa_001',
'lat': 40.0987,
'lng': -75.0234,
'road': 'Roosevelt Blvd (US-1) at Red Lion Rd',
'limit': 45,
'type': 'speed_camera',
'state': 'PA',
'city': 'Philadelphia',
'direction': 50.0,
},
];
tearDown(() {
UsSpeedCameraDatabase.instance.reset();
});
test('Initializes correctly and populates spatial grid', () {
final db = UsSpeedCameraDatabase.instance;
expect(db.isInitialized, isFalse);
db.loadFromJsonList(sampleData);
expect(db.isInitialized, isTrue);
expect(db.totalCount, equals(4));
expect(db.supportedStates, containsAll(['NY', 'IL', 'NC', 'PA']));
});
test('Finds camera within 500 meters of exact coordinate', () {
final db = UsSpeedCameraDatabase.instance;
db.loadFromJsonList(sampleData);
// Search 100 meters away from Queens Blvd camera
final results = db.findCamerasNear(
latitude: 40.7260,
longitude: -73.8615,
radiusMeters: 500.0,
);
expect(results.length, equals(1));
final cam = results.first;
expect(cam.id, equals('test_ny_001'));
expect(cam.roadName, contains('Queens Blvd'));
expect(cam.speedLimitMph, equals(25));
expect(cam.distanceMeters, isNotNull);
expect(cam.distanceMeters!, lessThan(100.0));
});
test('Finds Deals Gap speed enforcement zone on US-129', () {
final db = UsSpeedCameraDatabase.instance;
db.loadFromJsonList(sampleData);
// Search near Deals Gap Motorcycle Resort
final results = db.findCamerasNear(
latitude: 35.4715,
longitude: -83.9230,
radiusMeters: 600.0,
);
expect(results.length, equals(1));
final cam = results.first;
expect(cam.id, equals('test_tn_001'));
expect(cam.type, equals('speed_trap'));
expect(cam.roadName, contains('Tail of the Dragon'));
});
test('Returns empty list when no cameras are in radius', () {
final db = UsSpeedCameraDatabase.instance;
db.loadFromJsonList(sampleData);
// Query middle of ocean / remote desert
final results = db.findCamerasNear(
latitude: 30.0000,
longitude: -60.0000,
radiusMeters: 1000.0,
);
expect(results, isEmpty);
});
test('Results are sorted by distance ascending', () {
final multiNearData = [
{
'id': 'near_1',
'lat': 40.7258,
'lng': -73.8617,
'road': 'Camera 1',
'state': 'NY',
},
{
'id': 'near_2',
'lat': 40.7280,
'lng': -73.8617,
'road': 'Camera 2',
'state': 'NY',
},
];
final db = UsSpeedCameraDatabase.instance;
db.loadFromJsonList(multiNearData);
final results = db.findCamerasNear(
latitude: 40.7257,
longitude: -73.8617,
radiusMeters: 1000.0,
);
expect(results.length, equals(2));
expect(results[0].distanceMeters!, lessThan(results[1].distanceMeters!));
});
});
group('OsmRoadAlertProvider Tests', () {
tearDown(() {
UsSpeedCameraDatabase.instance.reset();
});
test('Advertises supported categories and id', () {
final provider = OsmRoadAlertProvider();
expect(provider.id, equals('osm_us_cameras'));
expect(provider.name, equals('US Speed Cameras (Offline OSM)'));
expect(provider.supportedCategories, contains(RoadAlertCategory.speedCamera));
expect(provider.supportedCategories, contains(RoadAlertCategory.police));
});
test('TestConnection always returns connected (offline)', () async {
final provider = OsmRoadAlertProvider();
final status = await provider.testConnection('');
expect(status, equals(RoadAlertProviderStatus.connected));
});
test('Maps fixed speed camera correctly to RoadAlert', () async {
final db = UsSpeedCameraDatabase.instance;
db.loadFromJsonList([
{
'id': 'test_fixed_1',
'lat': 40.0987,
'lng': -75.0234,
'road': 'Roosevelt Blvd',
'limit': 45,
'type': 'speed_camera',
'state': 'PA',
}
]);
final provider = OsmRoadAlertProvider(database: db);
final alerts = await provider.getAlerts(
latitude: 40.0987,
longitude: -75.0234,
radiusMeters: 500.0,
);
expect(alerts.length, equals(1));
final alert = alerts.first;
expect(alert.id, equals('test_fixed_1'));
expect(alert.providerId, equals('osm_us_cameras'));
expect(alert.category, equals(RoadAlertCategory.speedCamera));
expect(alert.roadName, equals('Roosevelt Blvd'));
expect(alert.description, contains('45 MPH'));
expect(alert.expiresAt, isNull); // Static infrastructure does not expire
expect(alert.confidence, equals(0.98));
expect(alert.source, equals('OpenStreetMap US'));
expect(alert.sourceType, equals(RoadAlertSourceType.providerReported));
});
test('Maps speed trap to police RoadAlertCategory', () async {
final db = UsSpeedCameraDatabase.instance;
db.loadFromJsonList([
{
'id': 'test_trap_1',
'lat': 34.0989,
'lng': -118.7892,
'road': 'Mulholland Hwy',
'limit': 35,
'type': 'speed_trap',
'state': 'CA',
}
]);
final provider = OsmRoadAlertProvider(database: db);
final alerts = await provider.getAlerts(
latitude: 34.0989,
longitude: -118.7892,
radiusMeters: 500.0,
);
expect(alerts.length, equals(1));
final alert = alerts.first;
expect(alert.id, equals('test_trap_1'));
expect(alert.category, equals(RoadAlertCategory.police));
expect(alert.description, contains('Speed enforcement checkpoint'));
});
});
group('Riverpod Provider Wiring Tests', () {
test('usSpeedCamerasEnabledProvider defaults to true', () {
final container = ProviderContainer();
addTearDown(container.dispose);
final isEnabled = container.read(usSpeedCamerasEnabledProvider);
expect(isEnabled, isTrue);
});
test('activeRoadAlertProviders includes OsmRoadAlertProvider when enabled', () {
final container = ProviderContainer();
addTearDown(container.dispose);
final providers = container.read(activeRoadAlertProviders);
expect(providers.any((p) => p.id == 'osm_us_cameras'), isTrue);
});
test('activeRoadAlertProviders excludes OsmRoadAlertProvider when disabled', () async {
final container = ProviderContainer();
addTearDown(container.dispose);
await container.read(usSpeedCamerasEnabledProvider.notifier).setEnabled(false);
final providers = container.read(activeRoadAlertProviders);
expect(providers.any((p) => p.id == 'osm_us_cameras'), isFalse);
});
test('osmSpeedCameraStatusProvider reflects toggle', () async {
final container = ProviderContainer();
addTearDown(container.dispose);
expect(
container.read(osmSpeedCameraStatusProvider),
equals(RoadAlertProviderStatus.connected),
);
await container.read(usSpeedCamerasEnabledProvider.notifier).setEnabled(false);
expect(
container.read(osmSpeedCameraStatusProvider),
equals(RoadAlertProviderStatus.notConfigured),
);
});
});
}