262 lines
7.8 KiB
Dart
262 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/routes/route_models.dart';
|
|
import 'package:motorcycle_dash/services/routes/route_provider.dart';
|
|
import 'package:motorcycle_dash/services/routes/valhalla_route_provider.dart';
|
|
|
|
void main() {
|
|
group('ValhallaRouteProvider Tests', () {
|
|
test('throws authenticationRequired when token provider returns null', () async {
|
|
final provider = ValhallaRouteProvider(
|
|
backendUrl: 'http://localhost:3000',
|
|
tokenProvider: () async => null,
|
|
);
|
|
|
|
final stops = [
|
|
const RouteStop(
|
|
id: 's1',
|
|
displayName: 'Start',
|
|
latitude: 37.77,
|
|
longitude: -122.42,
|
|
order: 0,
|
|
),
|
|
const RouteStop(
|
|
id: 's2',
|
|
displayName: 'End',
|
|
latitude: 37.80,
|
|
longitude: -122.27,
|
|
order: 1,
|
|
),
|
|
];
|
|
|
|
expect(
|
|
() => provider.calculateRoute(stops),
|
|
throwsA(isA<RouteProviderException>().having(
|
|
(e) => e.code,
|
|
'code',
|
|
'authenticationRequired',
|
|
)),
|
|
);
|
|
});
|
|
|
|
test('validates minimum 2 stops', () async {
|
|
final provider = ValhallaRouteProvider(
|
|
tokenProvider: () async => 'valid-mock-token',
|
|
);
|
|
|
|
final stops = [
|
|
const RouteStop(
|
|
id: 's1',
|
|
displayName: 'Single Stop',
|
|
latitude: 37.77,
|
|
longitude: -122.42,
|
|
order: 0,
|
|
),
|
|
];
|
|
|
|
expect(
|
|
() => provider.calculateRoute(stops),
|
|
throwsA(isA<RouteProviderException>().having(
|
|
(e) => e.code,
|
|
'code',
|
|
'invalidRequest',
|
|
)),
|
|
);
|
|
});
|
|
|
|
test('validates maximum 25 stops', () async {
|
|
final provider = ValhallaRouteProvider(
|
|
tokenProvider: () async => 'valid-mock-token',
|
|
);
|
|
|
|
final stops = List.generate(
|
|
26,
|
|
(i) => RouteStop(
|
|
id: 's$i',
|
|
displayName: 'Stop $i',
|
|
latitude: 37.0 + i * 0.01,
|
|
longitude: -122.0,
|
|
order: i,
|
|
),
|
|
);
|
|
|
|
expect(
|
|
() => provider.calculateRoute(stops),
|
|
throwsA(isA<RouteProviderException>().having(
|
|
(e) => e.code,
|
|
'code',
|
|
'invalidRequest',
|
|
)),
|
|
);
|
|
});
|
|
|
|
test('calculates 2-stop route and parses normalized result with precision 6 geometry', () async {
|
|
final mockClient = MockClient((request) async {
|
|
expect(request.url.path, '/api/routes/calculate');
|
|
expect(request.headers['authorization'], 'Bearer valid-test-token');
|
|
|
|
final payload = jsonDecode(request.body) as Map<String, dynamic>;
|
|
expect((payload['locations'] as List).length, 2);
|
|
|
|
// Return fixture for SF to Oakland
|
|
return http.Response(
|
|
jsonEncode({
|
|
'provider': 'valhalla_stadia',
|
|
'encodedPolyline': 'gbr`gAnk{nhFwrx@om`H',
|
|
'distanceMeters': 18500.5,
|
|
'durationSeconds': 1320,
|
|
'warnings': ['Motorcycle route preview'],
|
|
'attribution': '© Stadia Maps',
|
|
}),
|
|
200,
|
|
headers: {'content-type': 'application/json'},
|
|
);
|
|
});
|
|
|
|
final provider = ValhallaRouteProvider(
|
|
client: mockClient,
|
|
tokenProvider: () async => 'valid-test-token',
|
|
);
|
|
|
|
final stops = [
|
|
const RouteStop(
|
|
id: 's1',
|
|
displayName: 'SF',
|
|
latitude: 37.7749,
|
|
longitude: -122.4194,
|
|
order: 0,
|
|
),
|
|
const RouteStop(
|
|
id: 's2',
|
|
displayName: 'Oakland',
|
|
latitude: 37.8044,
|
|
longitude: -122.2712,
|
|
order: 1,
|
|
),
|
|
];
|
|
|
|
final result = await provider.calculateRoute(stops);
|
|
|
|
expect(result.isStraightLine, false);
|
|
expect(result.estimatedDistanceMeters, 18500.5);
|
|
expect(result.estimatedDurationSeconds, 1320);
|
|
expect(result.geometry.length, 2);
|
|
expect(result.geometry[0].latitude, closeTo(37.7749, 1e-4));
|
|
expect(result.geometry[1].latitude, closeTo(37.8044, 1e-4));
|
|
expect(result.warnings, contains('Motorcycle route preview'));
|
|
});
|
|
|
|
test('searches places and parses coordinates from search results', () async {
|
|
final mockClient = MockClient((request) async {
|
|
expect(request.url.path, '/api/places/search');
|
|
expect(request.url.queryParameters['query'], 'Alice Restaurant');
|
|
|
|
return http.Response(
|
|
jsonEncode({
|
|
'results': [
|
|
{
|
|
'providerId': 'place_123',
|
|
'primaryText': "Alice's Restaurant",
|
|
'secondaryText': 'Woodside, CA',
|
|
'formattedAddress': "Alice's Restaurant, Skyline Blvd, Woodside, CA",
|
|
'latitude': 37.3868,
|
|
'longitude': -122.2644,
|
|
}
|
|
],
|
|
'attribution': 'Powered by Stadia Maps',
|
|
}),
|
|
200,
|
|
headers: {'content-type': 'application/json'},
|
|
);
|
|
});
|
|
|
|
final provider = ValhallaRouteProvider(
|
|
client: mockClient,
|
|
tokenProvider: () async => 'valid-test-token',
|
|
);
|
|
|
|
final results = await provider.searchPlaces('Alice Restaurant');
|
|
|
|
expect(results.length, 1);
|
|
final place = results.first;
|
|
expect(place.providerId, 'place_123');
|
|
expect(place.primaryText, "Alice's Restaurant");
|
|
expect(place.hasCoordinates, true);
|
|
expect(place.latitude, 37.3868);
|
|
expect(place.longitude, -122.2644);
|
|
|
|
// Verify immediate resolution without second network call
|
|
final resolved = place.toResolvedPlace();
|
|
expect(resolved, isNotNull);
|
|
expect(resolved!.latitude, 37.3868);
|
|
expect(resolved.displayName, "Alice's Restaurant");
|
|
});
|
|
|
|
test('resolves place details by placeId', () async {
|
|
final mockClient = MockClient((request) async {
|
|
expect(request.url.path, '/api/places/resolve');
|
|
expect(request.url.queryParameters['placeId'], 'gid_999');
|
|
|
|
return http.Response(
|
|
jsonEncode({
|
|
'providerId': 'gid_999',
|
|
'displayName': 'Mount Hamilton Observatory',
|
|
'formattedAddress': 'Mount Hamilton Rd, San Jose, CA',
|
|
'latitude': 37.3414,
|
|
'longitude': -121.6429,
|
|
'attribution': 'Powered by Stadia Maps',
|
|
}),
|
|
200,
|
|
headers: {'content-type': 'application/json'},
|
|
);
|
|
});
|
|
|
|
final provider = ValhallaRouteProvider(
|
|
client: mockClient,
|
|
tokenProvider: () async => 'valid-test-token',
|
|
);
|
|
|
|
final resolved = await provider.resolvePlace('gid_999');
|
|
|
|
expect(resolved.providerId, 'gid_999');
|
|
expect(resolved.displayName, 'Mount Hamilton Observatory');
|
|
expect(resolved.latitude, 37.3414);
|
|
expect(resolved.longitude, -121.6429);
|
|
});
|
|
|
|
test('maps backend 404 to routeNotFound exception', () async {
|
|
final mockClient = MockClient((request) async {
|
|
return http.Response(
|
|
jsonEncode({
|
|
'error': 'routeNotFound',
|
|
'message': 'No route could be found',
|
|
}),
|
|
404,
|
|
headers: {'content-type': 'application/json'},
|
|
);
|
|
});
|
|
|
|
final provider = ValhallaRouteProvider(
|
|
client: mockClient,
|
|
tokenProvider: () async => 'valid-test-token',
|
|
);
|
|
|
|
final stops = [
|
|
const RouteStop(id: '1', displayName: '1', latitude: 0, longitude: 0, order: 0),
|
|
const RouteStop(id: '2', displayName: '2', latitude: 1, longitude: 1, order: 1),
|
|
];
|
|
|
|
expect(
|
|
() => provider.calculateRoute(stops),
|
|
throwsA(isA<RouteProviderException>().having(
|
|
(e) => e.code,
|
|
'code',
|
|
'routeNotFound',
|
|
)),
|
|
);
|
|
});
|
|
});
|
|
}
|