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), ); }); }); }