289 lines
8.8 KiB
Dart
289 lines
8.8 KiB
Dart
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
import 'package:motorcycle_dash/services/alerts/road_alert_models.dart';
|
|
import 'package:motorcycle_dash/services/alerts/road_alert_providers.dart';
|
|
import 'package:motorcycle_dash/services/alerts/us_speed_camera_database.dart';
|
|
import 'package:motorcycle_dash/services/alerts/osm_road_alert_provider.dart';
|
|
|
|
void main() {
|
|
TestWidgetsFlutterBinding.ensureInitialized();
|
|
|
|
setUp(() async {
|
|
SharedPreferences.setMockInitialValues({});
|
|
});
|
|
|
|
group('UsSpeedCameraDatabase Tests', () {
|
|
final sampleData = [
|
|
{
|
|
'id': 'test_ny_001',
|
|
'lat': 40.7258,
|
|
'lng': -73.8617,
|
|
'road': 'Queens Blvd at 65th Rd',
|
|
'limit': 25,
|
|
'type': 'speed_camera',
|
|
'state': 'NY',
|
|
'city': 'Queens, NYC',
|
|
'direction': 120.0,
|
|
},
|
|
{
|
|
'id': 'test_il_001',
|
|
'lat': 41.8092,
|
|
'lng': -87.5889,
|
|
'road': 'DuSable Lake Shore Dr at 47th St',
|
|
'limit': 40,
|
|
'type': 'speed_camera',
|
|
'state': 'IL',
|
|
'city': 'Chicago',
|
|
'direction': 180.0,
|
|
},
|
|
{
|
|
'id': 'test_tn_001',
|
|
'lat': 35.4712,
|
|
'lng': -83.9234,
|
|
'road': 'US-129 Tail of the Dragon at Deals Gap',
|
|
'limit': 30,
|
|
'type': 'speed_trap',
|
|
'state': 'NC',
|
|
'city': 'Deals Gap',
|
|
'direction': 320.0,
|
|
},
|
|
{
|
|
'id': 'test_pa_001',
|
|
'lat': 40.0987,
|
|
'lng': -75.0234,
|
|
'road': 'Roosevelt Blvd (US-1) at Red Lion Rd',
|
|
'limit': 45,
|
|
'type': 'speed_camera',
|
|
'state': 'PA',
|
|
'city': 'Philadelphia',
|
|
'direction': 50.0,
|
|
},
|
|
];
|
|
|
|
tearDown(() {
|
|
UsSpeedCameraDatabase.instance.reset();
|
|
});
|
|
|
|
test('Initializes correctly and populates spatial grid', () {
|
|
final db = UsSpeedCameraDatabase.instance;
|
|
expect(db.isInitialized, isFalse);
|
|
|
|
db.loadFromJsonList(sampleData);
|
|
expect(db.isInitialized, isTrue);
|
|
expect(db.totalCount, equals(4));
|
|
expect(db.supportedStates, containsAll(['NY', 'IL', 'NC', 'PA']));
|
|
});
|
|
|
|
test('Finds camera within 500 meters of exact coordinate', () {
|
|
final db = UsSpeedCameraDatabase.instance;
|
|
db.loadFromJsonList(sampleData);
|
|
|
|
// Search 100 meters away from Queens Blvd camera
|
|
final results = db.findCamerasNear(
|
|
latitude: 40.7260,
|
|
longitude: -73.8615,
|
|
radiusMeters: 500.0,
|
|
);
|
|
|
|
expect(results.length, equals(1));
|
|
final cam = results.first;
|
|
expect(cam.id, equals('test_ny_001'));
|
|
expect(cam.roadName, contains('Queens Blvd'));
|
|
expect(cam.speedLimitMph, equals(25));
|
|
expect(cam.distanceMeters, isNotNull);
|
|
expect(cam.distanceMeters!, lessThan(100.0));
|
|
});
|
|
|
|
test('Finds Deals Gap speed enforcement zone on US-129', () {
|
|
final db = UsSpeedCameraDatabase.instance;
|
|
db.loadFromJsonList(sampleData);
|
|
|
|
// Search near Deals Gap Motorcycle Resort
|
|
final results = db.findCamerasNear(
|
|
latitude: 35.4715,
|
|
longitude: -83.9230,
|
|
radiusMeters: 600.0,
|
|
);
|
|
|
|
expect(results.length, equals(1));
|
|
final cam = results.first;
|
|
expect(cam.id, equals('test_tn_001'));
|
|
expect(cam.type, equals('speed_trap'));
|
|
expect(cam.roadName, contains('Tail of the Dragon'));
|
|
});
|
|
|
|
test('Returns empty list when no cameras are in radius', () {
|
|
final db = UsSpeedCameraDatabase.instance;
|
|
db.loadFromJsonList(sampleData);
|
|
|
|
// Query middle of ocean / remote desert
|
|
final results = db.findCamerasNear(
|
|
latitude: 30.0000,
|
|
longitude: -60.0000,
|
|
radiusMeters: 1000.0,
|
|
);
|
|
|
|
expect(results, isEmpty);
|
|
});
|
|
|
|
test('Results are sorted by distance ascending', () {
|
|
final multiNearData = [
|
|
{
|
|
'id': 'near_1',
|
|
'lat': 40.7258,
|
|
'lng': -73.8617,
|
|
'road': 'Camera 1',
|
|
'state': 'NY',
|
|
},
|
|
{
|
|
'id': 'near_2',
|
|
'lat': 40.7280,
|
|
'lng': -73.8617,
|
|
'road': 'Camera 2',
|
|
'state': 'NY',
|
|
},
|
|
];
|
|
|
|
final db = UsSpeedCameraDatabase.instance;
|
|
db.loadFromJsonList(multiNearData);
|
|
|
|
final results = db.findCamerasNear(
|
|
latitude: 40.7257,
|
|
longitude: -73.8617,
|
|
radiusMeters: 1000.0,
|
|
);
|
|
|
|
expect(results.length, equals(2));
|
|
expect(results[0].distanceMeters!, lessThan(results[1].distanceMeters!));
|
|
});
|
|
});
|
|
|
|
group('OsmRoadAlertProvider Tests', () {
|
|
tearDown(() {
|
|
UsSpeedCameraDatabase.instance.reset();
|
|
});
|
|
|
|
test('Advertises supported categories and id', () {
|
|
final provider = OsmRoadAlertProvider();
|
|
expect(provider.id, equals('osm_us_cameras'));
|
|
expect(provider.name, equals('US Speed Cameras (Offline OSM)'));
|
|
expect(provider.supportedCategories, contains(RoadAlertCategory.speedCamera));
|
|
expect(provider.supportedCategories, contains(RoadAlertCategory.police));
|
|
});
|
|
|
|
test('TestConnection always returns connected (offline)', () async {
|
|
final provider = OsmRoadAlertProvider();
|
|
final status = await provider.testConnection('');
|
|
expect(status, equals(RoadAlertProviderStatus.connected));
|
|
});
|
|
|
|
test('Maps fixed speed camera correctly to RoadAlert', () async {
|
|
final db = UsSpeedCameraDatabase.instance;
|
|
db.loadFromJsonList([
|
|
{
|
|
'id': 'test_fixed_1',
|
|
'lat': 40.0987,
|
|
'lng': -75.0234,
|
|
'road': 'Roosevelt Blvd',
|
|
'limit': 45,
|
|
'type': 'speed_camera',
|
|
'state': 'PA',
|
|
}
|
|
]);
|
|
|
|
final provider = OsmRoadAlertProvider(database: db);
|
|
final alerts = await provider.getAlerts(
|
|
latitude: 40.0987,
|
|
longitude: -75.0234,
|
|
radiusMeters: 500.0,
|
|
);
|
|
|
|
expect(alerts.length, equals(1));
|
|
final alert = alerts.first;
|
|
expect(alert.id, equals('test_fixed_1'));
|
|
expect(alert.providerId, equals('osm_us_cameras'));
|
|
expect(alert.category, equals(RoadAlertCategory.speedCamera));
|
|
expect(alert.roadName, equals('Roosevelt Blvd'));
|
|
expect(alert.description, contains('45 MPH'));
|
|
expect(alert.expiresAt, isNull); // Static infrastructure does not expire
|
|
expect(alert.confidence, equals(0.98));
|
|
expect(alert.source, equals('OpenStreetMap US'));
|
|
expect(alert.sourceType, equals(RoadAlertSourceType.providerReported));
|
|
});
|
|
|
|
test('Maps speed trap to police RoadAlertCategory', () async {
|
|
final db = UsSpeedCameraDatabase.instance;
|
|
db.loadFromJsonList([
|
|
{
|
|
'id': 'test_trap_1',
|
|
'lat': 34.0989,
|
|
'lng': -118.7892,
|
|
'road': 'Mulholland Hwy',
|
|
'limit': 35,
|
|
'type': 'speed_trap',
|
|
'state': 'CA',
|
|
}
|
|
]);
|
|
|
|
final provider = OsmRoadAlertProvider(database: db);
|
|
final alerts = await provider.getAlerts(
|
|
latitude: 34.0989,
|
|
longitude: -118.7892,
|
|
radiusMeters: 500.0,
|
|
);
|
|
|
|
expect(alerts.length, equals(1));
|
|
final alert = alerts.first;
|
|
expect(alert.id, equals('test_trap_1'));
|
|
expect(alert.category, equals(RoadAlertCategory.police));
|
|
expect(alert.description, contains('Speed enforcement checkpoint'));
|
|
});
|
|
});
|
|
|
|
group('Riverpod Provider Wiring Tests', () {
|
|
test('usSpeedCamerasEnabledProvider defaults to true', () {
|
|
final container = ProviderContainer();
|
|
addTearDown(container.dispose);
|
|
|
|
final isEnabled = container.read(usSpeedCamerasEnabledProvider);
|
|
expect(isEnabled, isTrue);
|
|
});
|
|
|
|
test('activeRoadAlertProviders includes OsmRoadAlertProvider when enabled', () {
|
|
final container = ProviderContainer();
|
|
addTearDown(container.dispose);
|
|
|
|
final providers = container.read(activeRoadAlertProviders);
|
|
expect(providers.any((p) => p.id == 'osm_us_cameras'), isTrue);
|
|
});
|
|
|
|
test('activeRoadAlertProviders excludes OsmRoadAlertProvider when disabled', () async {
|
|
final container = ProviderContainer();
|
|
addTearDown(container.dispose);
|
|
|
|
await container.read(usSpeedCamerasEnabledProvider.notifier).setEnabled(false);
|
|
|
|
final providers = container.read(activeRoadAlertProviders);
|
|
expect(providers.any((p) => p.id == 'osm_us_cameras'), isFalse);
|
|
});
|
|
|
|
test('osmSpeedCameraStatusProvider reflects toggle', () async {
|
|
final container = ProviderContainer();
|
|
addTearDown(container.dispose);
|
|
|
|
expect(
|
|
container.read(osmSpeedCameraStatusProvider),
|
|
equals(RoadAlertProviderStatus.connected),
|
|
);
|
|
|
|
await container.read(usSpeedCamerasEnabledProvider.notifier).setEnabled(false);
|
|
|
|
expect(
|
|
container.read(osmSpeedCameraStatusProvider),
|
|
equals(RoadAlertProviderStatus.notConfigured),
|
|
);
|
|
});
|
|
});
|
|
}
|