MotorSignal-App/test/waze_osint_road_alert_provi...

229 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: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);
});
});
}