476 lines
11 KiB
TypeScript
476 lines
11 KiB
TypeScript
import { DefaultParamsProviderPort } from '@modules/geography/core/application/ports/default-params-provider.port';
|
|
import { GeodesicPort } from '@modules/geography/core/application/ports/geodesic.port';
|
|
import {
|
|
GeorouterUnavailableException,
|
|
RouteNotFoundException,
|
|
} from '@modules/geography/core/domain/route.errors';
|
|
import { Route, Step } from '@modules/geography/core/domain/route.types';
|
|
import {
|
|
GEODESIC,
|
|
PARAMS_PROVIDER,
|
|
} from '@modules/geography/geography.di-tokens';
|
|
import { GraphhopperGeorouter } from '@modules/geography/infrastructure/graphhopper-georouter';
|
|
import { HttpService } from '@nestjs/axios';
|
|
import { Test, TestingModule } from '@nestjs/testing';
|
|
import { AxiosError } from 'axios';
|
|
import { of, throwError } from 'rxjs';
|
|
|
|
const mockHttpService = {
|
|
get: jest
|
|
.fn()
|
|
.mockImplementationOnce(() => {
|
|
return throwError(
|
|
() => new AxiosError('Axios error', AxiosError.ERR_BAD_REQUEST),
|
|
);
|
|
})
|
|
.mockImplementationOnce(() => {
|
|
return throwError(() => 'Router unavailable');
|
|
})
|
|
.mockImplementationOnce(() => {
|
|
return of({
|
|
status: 200,
|
|
data: {
|
|
paths: [
|
|
{
|
|
distance: 50000,
|
|
time: 1800000,
|
|
snapped_waypoints: {
|
|
coordinates: [
|
|
[0, 0],
|
|
[10, 10],
|
|
],
|
|
},
|
|
},
|
|
],
|
|
},
|
|
});
|
|
})
|
|
.mockImplementationOnce(() => {
|
|
return of({
|
|
status: 200,
|
|
data: {
|
|
paths: [
|
|
{
|
|
distance: 50000,
|
|
time: 1800000,
|
|
points: {
|
|
coordinates: [
|
|
[0, 0],
|
|
[1, 1],
|
|
[2, 2],
|
|
[3, 3],
|
|
[4, 4],
|
|
[5, 5],
|
|
[6, 6],
|
|
[7, 7],
|
|
[8, 8],
|
|
[9, 9],
|
|
[10, 10],
|
|
],
|
|
},
|
|
snapped_waypoints: {
|
|
coordinates: [
|
|
[0, 0],
|
|
[10, 10],
|
|
],
|
|
},
|
|
},
|
|
],
|
|
},
|
|
});
|
|
})
|
|
.mockImplementationOnce(() => {
|
|
return of({
|
|
status: 200,
|
|
data: {
|
|
paths: [
|
|
{
|
|
distance: 50000,
|
|
time: 1800000,
|
|
points: {
|
|
coordinates: [
|
|
[0, 0],
|
|
[1, 1],
|
|
[2, 2],
|
|
[3, 3],
|
|
[4, 4],
|
|
[5, 5],
|
|
[6, 6],
|
|
[7, 7],
|
|
[8, 8],
|
|
[9, 9],
|
|
[10, 10],
|
|
],
|
|
},
|
|
details: {
|
|
time: [
|
|
[0, 1, 180000],
|
|
[1, 2, 180000],
|
|
[2, 3, 180000],
|
|
[3, 4, 180000],
|
|
[4, 5, 180000],
|
|
[5, 6, 180000],
|
|
[6, 7, 180000],
|
|
[7, 9, 360000],
|
|
[9, 10, 180000],
|
|
],
|
|
},
|
|
snapped_waypoints: {
|
|
coordinates: [
|
|
[0, 0],
|
|
[10, 10],
|
|
],
|
|
},
|
|
},
|
|
],
|
|
},
|
|
});
|
|
})
|
|
.mockImplementationOnce(() => {
|
|
return of({
|
|
status: 200,
|
|
data: {
|
|
paths: [
|
|
{
|
|
distance: 50000,
|
|
time: 1800000,
|
|
points: {
|
|
coordinates: [
|
|
[0, 0],
|
|
[1, 1],
|
|
[2, 2],
|
|
[3, 3],
|
|
[4, 4],
|
|
[7, 7],
|
|
[8, 8],
|
|
[9, 9],
|
|
[10, 10],
|
|
],
|
|
},
|
|
snapped_waypoints: {
|
|
coordinates: [
|
|
[0, 0],
|
|
[5, 5],
|
|
[10, 10],
|
|
],
|
|
},
|
|
details: {
|
|
time: [
|
|
[0, 1, 180000],
|
|
[1, 2, 180000],
|
|
[2, 3, 180000],
|
|
[3, 4, 180000],
|
|
[4, 7, 540000],
|
|
[7, 9, 360000],
|
|
[9, 10, 180000],
|
|
],
|
|
},
|
|
},
|
|
],
|
|
},
|
|
});
|
|
})
|
|
.mockImplementationOnce(() => {
|
|
return of({
|
|
status: 200,
|
|
data: {
|
|
paths: [
|
|
{
|
|
distance: 50000,
|
|
time: 1800000,
|
|
points: {
|
|
coordinates: [
|
|
[0, 0],
|
|
[1, 1],
|
|
[2, 2],
|
|
[3, 3],
|
|
[4, 4],
|
|
[5, 5],
|
|
[6, 6],
|
|
[7, 7],
|
|
[8, 8],
|
|
[9, 9],
|
|
[10, 10],
|
|
],
|
|
},
|
|
snapped_waypoints: {
|
|
coordinates: [
|
|
[0, 0],
|
|
[5, 5],
|
|
[10, 10],
|
|
],
|
|
},
|
|
details: {
|
|
time: [
|
|
[0, 1, 180000],
|
|
[1, 2, 180000],
|
|
[2, 3, 180000],
|
|
[3, 4, 180000],
|
|
[4, 7, 540000],
|
|
[7, 9, 360000],
|
|
[9, 10, 180000],
|
|
],
|
|
},
|
|
instructions: [
|
|
{
|
|
distance: 25000,
|
|
sign: 0,
|
|
interval: [0, 5],
|
|
text: 'Some instructions',
|
|
time: 900000,
|
|
},
|
|
{
|
|
distance: 0,
|
|
sign: 5,
|
|
interval: [5, 5],
|
|
text: 'Waypoint 1',
|
|
time: 0,
|
|
},
|
|
{
|
|
distance: 25000,
|
|
sign: 2,
|
|
interval: [5, 10],
|
|
text: 'Some instructions',
|
|
time: 900000,
|
|
},
|
|
{
|
|
distance: 0.0,
|
|
sign: 4,
|
|
interval: [10, 10],
|
|
text: 'Arrive at destination',
|
|
time: 0,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
});
|
|
}),
|
|
};
|
|
|
|
const mockGeodesic: GeodesicPort = {
|
|
inverse: jest.fn().mockImplementation(() => ({
|
|
azimuth: 45,
|
|
distance: 50000,
|
|
})),
|
|
};
|
|
|
|
const mockDefaultParamsProvider: DefaultParamsProviderPort = {
|
|
getParams: jest.fn().mockImplementation(() => ({
|
|
GEOROUTER_URL: 'http://localhost:8989',
|
|
})),
|
|
};
|
|
|
|
describe('Graphhopper Georouter', () => {
|
|
let graphhopperGeorouter: GraphhopperGeorouter;
|
|
|
|
beforeAll(async () => {
|
|
const module: TestingModule = await Test.createTestingModule({
|
|
imports: [],
|
|
providers: [
|
|
GraphhopperGeorouter,
|
|
{
|
|
provide: HttpService,
|
|
useValue: mockHttpService,
|
|
},
|
|
{
|
|
provide: PARAMS_PROVIDER,
|
|
useValue: mockDefaultParamsProvider,
|
|
},
|
|
{
|
|
provide: GEODESIC,
|
|
useValue: mockGeodesic,
|
|
},
|
|
],
|
|
}).compile();
|
|
|
|
graphhopperGeorouter =
|
|
module.get<GraphhopperGeorouter>(GraphhopperGeorouter);
|
|
});
|
|
|
|
it('should be defined', () => {
|
|
expect(graphhopperGeorouter).toBeDefined();
|
|
});
|
|
|
|
it('should fail if route is not found', async () => {
|
|
await expect(
|
|
graphhopperGeorouter.route(
|
|
[
|
|
{
|
|
position: 0,
|
|
lon: 0,
|
|
lat: 0,
|
|
},
|
|
{
|
|
position: 1,
|
|
lon: 1,
|
|
lat: 1,
|
|
},
|
|
],
|
|
{
|
|
detailedDistance: false,
|
|
detailedDuration: false,
|
|
points: false,
|
|
},
|
|
),
|
|
).rejects.toBeInstanceOf(RouteNotFoundException);
|
|
});
|
|
|
|
it('should fail if georouter is unavailable', async () => {
|
|
await expect(
|
|
graphhopperGeorouter.route(
|
|
[
|
|
{
|
|
position: 0,
|
|
lon: 0,
|
|
lat: 0,
|
|
},
|
|
{
|
|
position: 1,
|
|
lon: 1,
|
|
lat: 1,
|
|
},
|
|
],
|
|
{
|
|
detailedDistance: false,
|
|
detailedDuration: false,
|
|
points: false,
|
|
},
|
|
),
|
|
).rejects.toBeInstanceOf(GeorouterUnavailableException);
|
|
});
|
|
|
|
it('should create a basic route', async () => {
|
|
const route: Route = await graphhopperGeorouter.route(
|
|
[
|
|
{
|
|
position: 0,
|
|
lon: 0,
|
|
lat: 0,
|
|
},
|
|
{
|
|
position: 1,
|
|
lon: 10,
|
|
lat: 10,
|
|
},
|
|
],
|
|
{
|
|
detailedDistance: false,
|
|
detailedDuration: false,
|
|
points: false,
|
|
},
|
|
);
|
|
expect(route.distance).toBe(50000);
|
|
});
|
|
|
|
it('should create a route with points', async () => {
|
|
const route: Route = await graphhopperGeorouter.route(
|
|
[
|
|
{
|
|
position: 0,
|
|
lon: 0,
|
|
lat: 0,
|
|
},
|
|
{
|
|
position: 1,
|
|
lon: 10,
|
|
lat: 10,
|
|
},
|
|
],
|
|
{
|
|
detailedDistance: false,
|
|
detailedDuration: false,
|
|
points: true,
|
|
},
|
|
);
|
|
expect(route.distance).toBe(50000);
|
|
expect(route.duration).toBe(1800);
|
|
expect(route.fwdAzimuth).toBe(45);
|
|
expect(route.backAzimuth).toBe(225);
|
|
expect(route.points).toHaveLength(11);
|
|
});
|
|
|
|
it('should create a route with points and time', async () => {
|
|
const route: Route = await graphhopperGeorouter.route(
|
|
[
|
|
{
|
|
position: 0,
|
|
lon: 0,
|
|
lat: 0,
|
|
},
|
|
{
|
|
position: 1,
|
|
lon: 10,
|
|
lat: 10,
|
|
},
|
|
],
|
|
{
|
|
detailedDistance: false,
|
|
detailedDuration: true,
|
|
points: true,
|
|
},
|
|
);
|
|
expect(route.steps).toHaveLength(2);
|
|
expect((route.steps as Step[])[1].duration).toBe(1800);
|
|
expect((route.steps as Step[])[1].distance).toBeUndefined();
|
|
});
|
|
|
|
it('should create one route with points and missed waypoints extrapolations', async () => {
|
|
const route: Route = await graphhopperGeorouter.route(
|
|
[
|
|
{
|
|
position: 0,
|
|
lon: 0,
|
|
lat: 0,
|
|
},
|
|
{
|
|
position: 1,
|
|
lon: 5,
|
|
lat: 5,
|
|
},
|
|
{
|
|
position: 2,
|
|
lon: 10,
|
|
lat: 10,
|
|
},
|
|
],
|
|
{
|
|
detailedDistance: false,
|
|
detailedDuration: true,
|
|
points: true,
|
|
},
|
|
);
|
|
expect(route.steps).toHaveLength(3);
|
|
expect(route.distance).toBe(50000);
|
|
expect(route.duration).toBe(1800);
|
|
expect(route.fwdAzimuth).toBe(45);
|
|
expect(route.backAzimuth).toBe(225);
|
|
expect(route.points.length).toBe(9);
|
|
});
|
|
|
|
it('should create a route with points, time and distance', async () => {
|
|
const route: Route = await graphhopperGeorouter.route(
|
|
[
|
|
{
|
|
position: 0,
|
|
lon: 0,
|
|
lat: 0,
|
|
},
|
|
{
|
|
position: 1,
|
|
lon: 10,
|
|
lat: 10,
|
|
},
|
|
],
|
|
{
|
|
detailedDistance: true,
|
|
detailedDuration: true,
|
|
points: true,
|
|
},
|
|
);
|
|
expect(route.steps).toHaveLength(3);
|
|
expect((route.steps as Step[])[1].duration).toBe(990);
|
|
expect((route.steps as Step[])[1].distance).toBe(25000);
|
|
});
|
|
});
|