264 lines
11 KiB
Dart
264 lines
11 KiB
Dart
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);
|
|
});
|
|
});
|
|
}
|