import 'dart:convert'; import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:http/http.dart' as http; import 'package:http/testing.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:motorcycle_dash/services/alerts/road_alert_models.dart'; import 'package:motorcycle_dash/services/alerts/here_road_alert_provider.dart'; import 'package:motorcycle_dash/services/alerts/road_alert_providers.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); setUp(() async { SharedPreferences.setMockInitialValues({}); }); group('HereRoadAlertProvider Metadata Tests', () { test('provider has correct id, name, and supported categories', () { final provider = HereRoadAlertProvider(); expect(provider.id, equals('here_safety_cameras')); expect(provider.name, equals('HERE Safety Cameras')); expect( provider.supportedCategories, containsAll([ RoadAlertCategory.police, RoadAlertCategory.speedCamera, RoadAlertCategory.hazard, ]), ); }); }); group('HereRoadAlertProvider Connection Tests', () { test('testConnection returns notConfigured for empty or whitespace key', () async { final provider = HereRoadAlertProvider(); expect(await provider.testConnection(''), equals(RoadAlertProviderStatus.notConfigured)); expect(await provider.testConnection(' '), equals(RoadAlertProviderStatus.notConfigured)); }); test('testConnection returns connected on HTTP 200', () async { final mockClient = MockClient((request) async { expect(request.url.host, equals('safetycameras.hereapi.com')); expect(request.url.queryParameters['apiKey'], equals('valid_test_key')); return http.Response(json.encode({'items': []}), 200); }); final provider = HereRoadAlertProvider(client: mockClient); final status = await provider.testConnection('valid_test_key'); expect(status, equals(RoadAlertProviderStatus.connected)); }); test('testConnection returns authError on HTTP 401 or 403', () async { final mockClient401 = MockClient((request) async { return http.Response('{"error": "Unauthorized"}', 401); }); final provider401 = HereRoadAlertProvider(client: mockClient401); expect(await provider401.testConnection('bad_key'), equals(RoadAlertProviderStatus.authError)); final mockClient403 = MockClient((request) async { return http.Response('{"error": "Forbidden"}', 403); }); final provider403 = HereRoadAlertProvider(client: mockClient403); expect(await provider403.testConnection('forbidden_key'), equals(RoadAlertProviderStatus.authError)); }); test('testConnection returns rateLimited on HTTP 429', () async { final mockClient = MockClient((request) async { return http.Response('{"error": "Rate limit exceeded"}', 429); }); final provider = HereRoadAlertProvider(client: mockClient); expect(await provider.testConnection('busy_key'), equals(RoadAlertProviderStatus.rateLimited)); }); test('testConnection returns networkError on 500 or network exception', () async { final mockClient500 = MockClient((request) async { return http.Response('{"error": "Internal Error"}', 500); }); final provider500 = HereRoadAlertProvider(client: mockClient500); expect(await provider500.testConnection('key'), equals(RoadAlertProviderStatus.networkError)); final mockClientException = MockClient((request) async { throw http.ClientException('Connection refused'); }); final providerEx = HereRoadAlertProvider(client: mockClientException); expect(await providerEx.testConnection('key'), equals(RoadAlertProviderStatus.networkError)); }); }); group('HereRoadAlertProvider Parsing & Query Tests', () { test('getAlerts returns empty list if apiKey is null or empty', () async { final provider = HereRoadAlertProvider(); final r1 = await provider.getAlerts( latitude: 34.05, longitude: -118.25, radiusMeters: 1000, apiKey: null, ); expect(r1, isEmpty); final r2 = await provider.getAlerts( latitude: 34.05, longitude: -118.25, radiusMeters: 1000, apiKey: ' ', ); expect(r2, isEmpty); }); test('getAlerts correctly formats request and parses fixed & mobile camera items', () async { final sampleResponse = { 'items': [ { 'id': 'here_cam_001', 'type': 'FIXED_SPEED_CAMERA', 'position': {'lat': 34.0522, 'lng': -118.2437}, 'road': 'US-101 Northbound', 'speedLimit': 65, 'speedLimitUnit': 'MPH', 'heading': 350.0, 'description': 'Speed enforcement camera', }, { 'id': 'here_cam_002', 'type': 'MOBILE_SPEED_CAMERA', 'coordinate': {'latitude': 34.0600, 'longitude': -118.2500}, 'road': 'I-10 Westbound', 'speedLimit': 55, 'bearing': 270.0, }, { 'id': 'here_cam_003', 'type': 'RED_LIGHT_CAMERA', 'position': {'lat': 34.0450, 'lng': -118.2400}, 'road': 'Grand Ave at 7th St', }, { 'id': 'corrupt_item', 'type': 'FIXED_SPEED_CAMERA', 'position': {}, // Missing lat/lng }, ] }; final mockClient = MockClient((request) async { expect(request.url.queryParameters['apiKey'], equals('test_key_123')); expect(request.url.queryParameters['in'], contains('circle:34.0522,-118.2437;r=1500')); return http.Response(json.encode(sampleResponse), 200); }); final provider = HereRoadAlertProvider(client: mockClient); final alerts = await provider.getAlerts( latitude: 34.0522, longitude: -118.2437, radiusMeters: 1500, apiKey: 'test_key_123', ); expect(alerts.length, equals(3)); // Corrupt item skipped // Alert 1: Fixed speed camera final alert1 = alerts[0]; expect(alert1.id, equals('here_cam_001')); expect(alert1.providerId, equals('here_safety_cameras')); expect(alert1.category, equals(RoadAlertCategory.speedCamera)); expect(alert1.severity, equals(RoadAlertSeverity.medium)); expect(alert1.latitude, equals(34.0522)); expect(alert1.longitude, equals(-118.2437)); expect(alert1.roadName, equals('US-101 Northbound')); expect(alert1.heading, equals(350.0)); expect(alert1.description, equals('Speed enforcement camera')); expect(alert1.source, equals('HERE Technologies')); expect(alert1.sourceType, equals(RoadAlertSourceType.providerReported)); // Alert 2: Mobile speed camera (mapped to Police) final alert2 = alerts[1]; expect(alert2.id, equals('here_cam_002')); expect(alert2.category, equals(RoadAlertCategory.police)); expect(alert2.severity, equals(RoadAlertSeverity.high)); expect(alert2.latitude, equals(34.0600)); expect(alert2.longitude, equals(-118.2500)); expect(alert2.roadName, equals('I-10 Westbound')); expect(alert2.heading, equals(270.0)); expect(alert2.description, contains('Mobile Speed Trap (55 MPH zone)')); // Alert 3: Red light camera final alert3 = alerts[2]; expect(alert3.id, equals('here_cam_003')); expect(alert3.category, equals(RoadAlertCategory.speedCamera)); expect(alert3.severity, equals(RoadAlertSeverity.medium)); expect(alert3.latitude, equals(34.0450)); expect(alert3.longitude, equals(-118.2400)); expect(alert3.description, contains('Enforcement Camera')); }); test('getAlerts handles server failure response gracefully', () async { final mockClient = MockClient((request) async { return http.Response('{"error": "Internal Server Error"}', 500); }); final provider = HereRoadAlertProvider(client: mockClient); final alerts = await provider.getAlerts( latitude: 34.0522, longitude: -118.2437, radiusMeters: 1000, apiKey: 'test_key', ); expect(alerts, isEmpty); }); }); group('HERE Road Alerts Riverpod Integration Tests', () { test('hereApiKeyProvider and hereRoadAlertsEnabledProvider update properly', () async { final container = ProviderContainer(); addTearDown(container.dispose); expect(container.read(hereApiKeyProvider), isEmpty); expect(container.read(hereRoadAlertsEnabledProvider), isTrue); await container.read(hereApiKeyProvider.notifier).setApiKey('here_new_token'); expect(container.read(hereApiKeyProvider), equals('here_new_token')); await container.read(hereRoadAlertsEnabledProvider.notifier).setEnabled(false); expect(container.read(hereRoadAlertsEnabledProvider), isFalse); }); test('hereStatusProvider reports configured only when key present and enabled', () async { final container = ProviderContainer(); addTearDown(container.dispose); // Initially no key -> notConfigured expect(container.read(hereStatusProvider), equals(RoadAlertProviderStatus.notConfigured)); // Set key -> configured await container.read(hereApiKeyProvider.notifier).setApiKey('test_key'); expect(container.read(hereStatusProvider), equals(RoadAlertProviderStatus.configured)); // Disable provider -> notConfigured await container.read(hereRoadAlertsEnabledProvider.notifier).setEnabled(false); expect(container.read(hereStatusProvider), equals(RoadAlertProviderStatus.notConfigured)); }); test('activeRoadAlertProviders dynamically includes HERE provider when enabled with key', () async { final container = ProviderContainer(); addTearDown(container.dispose); // Without key, here_safety_cameras should not be in activeRoadAlertProviders var active = container.read(activeRoadAlertProviders); expect(active.any((p) => p.id == 'here_safety_cameras'), isFalse); // Add key -> should be included await container.read(hereApiKeyProvider.notifier).setApiKey('my_here_key'); active = container.read(activeRoadAlertProviders); expect(active.any((p) => p.id == 'here_safety_cameras'), isTrue); // Disable HERE -> should not be included await container.read(hereRoadAlertsEnabledProvider.notifier).setEnabled(false); active = container.read(activeRoadAlertProviders); expect(active.any((p) => p.id == 'here_safety_cameras'), isFalse); }); }); }