MotorSignal-App/test/waze_osint_road_alert_provi...

376 lines
13 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 with injected client returns connected', () async {
final mockClient = MockClient((request) async {
return http.Response(json.encode({'alerts': []}), 200);
});
final provider = WazeOsintRoadAlertProvider(client: mockClient);
final status = await provider.testConnection('');
expect(status, RoadAlertProviderStatus.connected);
});
});
group('WazeOsintRoadAlertProvider Environment / Region Selection', () {
test('resolves North America (na) for US, Canada, Mexico, Puerto Rico, Alaska, Hawaii', () {
// Deals Gap, NC (US-129)
expect(
WazeOsintRoadAlertProvider.determineEnvironment(latitude: 35.3934, longitude: -83.9261),
'na',
);
// Los Angeles, CA
expect(
WazeOsintRoadAlertProvider.determineEnvironment(latitude: 34.0522, longitude: -118.2437),
'na',
);
// Toronto, ON, Canada
expect(
WazeOsintRoadAlertProvider.determineEnvironment(latitude: 43.6532, longitude: -79.3832),
'na',
);
// Mexico City, Mexico
expect(
WazeOsintRoadAlertProvider.determineEnvironment(latitude: 19.4326, longitude: -99.1332),
'na',
);
// Honolulu, HI
expect(
WazeOsintRoadAlertProvider.determineEnvironment(latitude: 21.3069, longitude: -157.8583),
'na',
);
// Anchorage, AK
expect(
WazeOsintRoadAlertProvider.determineEnvironment(latitude: 61.2181, longitude: -149.9003),
'na',
);
// San Juan, Puerto Rico
expect(
WazeOsintRoadAlertProvider.determineEnvironment(latitude: 18.4655, longitude: -66.1057),
'na',
);
});
test('resolves Israel (il) for Israeli coordinates', () {
// Tel Aviv
expect(
WazeOsintRoadAlertProvider.determineEnvironment(latitude: 32.0853, longitude: 34.7818),
'il',
);
// Jerusalem
expect(
WazeOsintRoadAlertProvider.determineEnvironment(latitude: 31.7683, longitude: 35.2137),
'il',
);
// Haifa
expect(
WazeOsintRoadAlertProvider.determineEnvironment(latitude: 32.7940, longitude: 34.9896),
'il',
);
});
test('resolves Rest of World (row) for Europe, Asia, Australia, South America', () {
// London, UK
expect(
WazeOsintRoadAlertProvider.determineEnvironment(latitude: 51.5074, longitude: -0.1278),
'row',
);
// Paris, France
expect(
WazeOsintRoadAlertProvider.determineEnvironment(latitude: 48.8566, longitude: 2.3522),
'row',
);
// Tokyo, Japan
expect(
WazeOsintRoadAlertProvider.determineEnvironment(latitude: 35.6762, longitude: 139.6503),
'row',
);
// Sydney, Australia
expect(
WazeOsintRoadAlertProvider.determineEnvironment(latitude: -33.8688, longitude: 151.2093),
'row',
);
// Sao Paulo, Brazil
expect(
WazeOsintRoadAlertProvider.determineEnvironment(latitude: -23.5505, longitude: -46.6333),
'row',
);
// Cape Town, South Africa
expect(
WazeOsintRoadAlertProvider.determineEnvironment(latitude: -33.9249, longitude: 18.4241),
'row',
);
});
test('handles coordinate edge cases, NaN and antimeridian crossing safely', () {
expect(
WazeOsintRoadAlertProvider.determineEnvironment(latitude: double.nan, longitude: double.nan),
'row',
);
expect(
WazeOsintRoadAlertProvider.determineEnvironment(latitude: 95.0, longitude: -100.0),
'row',
);
// Western Aleutians crossing antimeridian (e.g. Attu Island at 52.9°N, 173.2°E)
expect(
WazeOsintRoadAlertProvider.determineEnvironment(latitude: 52.9, longitude: 173.2),
'na',
);
// Normalized longitude across antimeridian (-186.8° wraps to 173.2°E)
expect(
WazeOsintRoadAlertProvider.determineEnvironment(latitude: 52.9, longitude: -186.8),
'na',
);
});
});
group('WazeOsintRoadAlertProvider Capability Gate & Network Isolation', () {
test('isLiveNetworkAllowed is false by default in standard builds', () {
expect(WazeOsintRoadAlertProvider.isLiveNetworkAllowed, isFalse);
});
test('uninjected provider without capability gate fails closed (notConfigured & 0 requests)', () async {
final defaultProvider = WazeOsintRoadAlertProvider();
// testConnection fails closed
final status = await defaultProvider.testConnection('');
expect(status, RoadAlertProviderStatus.notConfigured);
// getAlerts fails closed and returns empty without throwing
final alerts = await defaultProvider.getAlerts(
latitude: 35.3934,
longitude: -83.9261,
radiusMeters: 5000,
);
expect(alerts, isEmpty);
});
test('injected client allows testing and fixtures without live network', () async {
final mockClient = MockClient((request) async {
return http.Response(json.encode({'alerts': []}), 200);
});
final testProvider = WazeOsintRoadAlertProvider(client: mockClient);
final status = await testProvider.testConnection('');
expect(status, RoadAlertProviderStatus.connected);
});
});
}