import 'dart:convert'; import 'package:flutter_test/flutter_test.dart'; import 'package:http/http.dart' as http; import 'package:http/testing.dart'; import 'package:motorcycle_dash/services/alerts/road_alert_models.dart'; import 'package:motorcycle_dash/services/alerts/waze_osint_road_alert_provider.dart'; void main() { group('WazeOsintRoadAlertProvider Category Mapping', () { test('maps all Waze types and subtypes to 10 native MotorSignal categories', () { expect( WazeOsintRoadAlertProvider.mapTypeToCategory('POLICE', null), RoadAlertCategory.police, ); expect( WazeOsintRoadAlertProvider.mapTypeToCategory('POLICE', 'POLICE_HIDDEN'), RoadAlertCategory.police, ); expect( WazeOsintRoadAlertProvider.mapTypeToCategory('ACCIDENT', 'ACCIDENT_MAJOR'), RoadAlertCategory.accident, ); expect( WazeOsintRoadAlertProvider.mapTypeToCategory('JAM', 'JAM_HEAVY_TRAFFIC'), RoadAlertCategory.traffic, ); expect( WazeOsintRoadAlertProvider.mapTypeToCategory('ROAD_CLOSED', 'ROAD_CLOSED_HAZARD'), RoadAlertCategory.roadClosure, ); expect( WazeOsintRoadAlertProvider.mapTypeToCategory('WEATHERHAZARD', 'HAZARD_WEATHER_ICE'), RoadAlertCategory.weatherHazard, ); expect( WazeOsintRoadAlertProvider.mapTypeToCategory('HAZARD', 'HAZARD_ON_ROAD_POT_HOLE'), RoadAlertCategory.pothole, ); expect( WazeOsintRoadAlertProvider.mapTypeToCategory('HAZARD', 'HAZARD_ON_ROAD_OBJECT'), RoadAlertCategory.debris, ); expect( WazeOsintRoadAlertProvider.mapTypeToCategory('HAZARD', 'HAZARD_ON_SHOULDER_CAR_STOPPED'), RoadAlertCategory.disabledVehicle, ); expect( WazeOsintRoadAlertProvider.mapTypeToCategory('HAZARD', 'HAZARD_ON_ROAD_CONSTRUCTION'), RoadAlertCategory.construction, ); expect( WazeOsintRoadAlertProvider.mapTypeToCategory('HAZARD', 'HAZARD_GENERIC'), RoadAlertCategory.hazard, ); expect( WazeOsintRoadAlertProvider.mapTypeToCategory('OTHER', null), RoadAlertCategory.other, ); }); }); group('WazeOsintRoadAlertProvider JSON Fixture Parsing', () { late WazeOsintRoadAlertProvider provider; setUp(() { provider = WazeOsintRoadAlertProvider(); }); test('parses realistic LiveMap GeoRSS JSON fixture with multiple alerts', () { final fixture = { 'alerts': [ { 'uuid': 'alert_police_001', 'type': 'POLICE', 'subtype': 'POLICE_HIDDEN', 'location': {'x': -83.9261, 'y': 35.3934}, 'street': 'US-129 Deals Gap', 'city': 'Robbinsville', 'magvar': 180, 'reportDescription': 'Trooper on bike hiding behind curve', 'pubMillis': DateTime.now().millisecondsSinceEpoch, 'confidence': 5, 'reliability': 8, 'nThumbsUp': 4, }, { 'uuid': 'alert_pothole_002', 'type': 'HAZARD', 'subtype': 'HAZARD_ON_ROAD_POT_HOLE', 'latitude': 35.4000, 'longitude': -83.9300, 'street': 'Cherohala Skyway', 'reportDescription': 'Deep rim-bending hole mid-corner', 'pubMillis': DateTime.now().millisecondsSinceEpoch, 'nThumbsUp': 2, }, { 'uuid': 'alert_debris_003', 'type': 'HAZARD', 'subtype': 'HAZARD_ON_ROAD_OBJECT', 'location': {'x': -83.9200, 'y': 35.3900}, 'street': 'NC-28 Moonshiner', 'nThumbsUp': 0, }, { 'uuid': 'alert_invalid_coords', 'type': 'POLICE', 'latitude': 999.0, // invalid latitude 'longitude': -83.90, }, { 'uuid': 'alert_missing_type', 'location': {'x': -83.90, 'y': 35.0}, } ] }; final parsed = provider.parseWazeResponse(fixture); expect(parsed.length, 3); final police = parsed.firstWhere((a) => a.id == 'waze_osint_alert_police_001'); expect(police.category, RoadAlertCategory.police); expect(police.latitude, 35.3934); expect(police.longitude, -83.9261); expect(police.roadName, 'US-129 Deals Gap, Robbinsville'); expect(police.description, 'Trooper on bike hiding behind curve'); expect(police.severity, RoadAlertSeverity.high); expect(police.confirmations, 5); // 1 base + 4 thumbsUp expect(police.isCommunityConfirmed, isTrue); final pothole = parsed.firstWhere((a) => a.id == 'waze_osint_alert_pothole_002'); expect(pothole.category, RoadAlertCategory.pothole); expect(pothole.latitude, 35.4000); expect(pothole.longitude, -83.9300); expect(pothole.roadName, 'Cherohala Skyway'); expect(pothole.confirmations, 3); // 1 base + 2 thumbsUp expect(pothole.isCommunityConfirmed, isTrue); final debris = parsed.firstWhere((a) => a.id == 'waze_osint_alert_debris_003'); expect(debris.category, RoadAlertCategory.debris); expect(debris.latitude, 35.3900); expect(debris.longitude, -83.9200); expect(debris.confirmations, 1); expect(debris.isCommunityConfirmed, isFalse); }); test('deduplicates alerts sharing the same uuid', () { final duplicateFixture = { 'alerts': [ { 'uuid': 'dup_1', 'type': 'ACCIDENT', 'location': {'x': -84.0, 'y': 35.0}, }, { 'uuid': 'dup_1', 'type': 'ACCIDENT', 'location': {'x': -84.0, 'y': 35.0}, }, ] }; final parsed = provider.parseWazeResponse(duplicateFixture); expect(parsed.length, 1); }); }); group('WazeOsintRoadAlertProvider HTTP Client & Rate Limiting', () { test('getAlerts queries bounding box and caches results', () async { int requestCount = 0; final mockClient = MockClient((request) async { requestCount++; expect(request.url.host, 'www.waze.com'); expect(request.url.path, '/live-map/api/georss'); expect(request.url.queryParameters['types'], 'alerts'); expect(request.headers['User-Agent'], 'MotorSignal-Community-Alerts/1.0'); final payload = { 'alerts': [ { 'uuid': 'test_waze_live_1', 'type': 'POLICE', 'location': {'x': -83.9261, 'y': 35.3934}, 'street': 'US-129 Deals Gap', 'pubMillis': DateTime.now().millisecondsSinceEpoch, 'nThumbsUp': 3, } ] }; return http.Response(json.encode(payload), 200, headers: { 'content-type': 'application/json', }); }); final provider = WazeOsintRoadAlertProvider( client: mockClient, minQueryInterval: const Duration(seconds: 30), ); // 1. Initial query executes HTTP request final alerts1 = await provider.getAlerts( latitude: 35.3934, longitude: -83.9261, radiusMeters: 5000, ); expect(requestCount, 1); expect(alerts1.length, 1); expect(alerts1.first.category, RoadAlertCategory.police); // 2. Immediate second query within 30s interval uses cache without extra HTTP call final alerts2 = await provider.getAlerts( latitude: 35.3934, longitude: -83.9261, radiusMeters: 5000, ); expect(requestCount, 1); // No new network call expect(alerts2.length, 1); }); test('testConnection returns connected without requiring paid API key', () async { final provider = WazeOsintRoadAlertProvider(); final status = await provider.testConnection(''); expect(status, RoadAlertProviderStatus.connected); }); }); }