lecheminvert.tech/test/unit/publicTransport.spec.ts

106 lines
3.4 KiB
TypeScript
Raw Permalink Normal View History

import { afterEach, describe, expect, it, vi } from 'vitest'
import { publicTransport } from '../../server/utils/transitous'
const LILLE = { lat: 50.6292, lon: 3.0573 }
const BRUXELLES = { lat: 50.8503, lon: 4.3517 }
const leg = (mode: string, distance: number, minutes: number) => ({
mode,
distance,
duration: minutes * 60,
startTime: '',
endTime: '',
from: LILLE,
to: BRUXELLES,
})
const journey = (legs: ReturnType<typeof leg>[], minutes: number, transfers = 0) => ({
duration: minutes * 60,
transfers,
legs,
})
/**
* Le vrai code passe deux requetes distinctes, une par famille de mode.
* Le faux doit faire la meme distinction, sinon il renvoie un trajet en car
* a la requete ferroviaire et le test valide un comportement impossible.
*/
function mockApi(byFamily: { rail?: unknown[], urban?: unknown[] }) {
const spy = vi.fn().mockImplementation((url: string) => {
const rail = url.includes('HIGHSPEED_RAIL')
return Promise.resolve({ itineraries: (rail ? byFamily.rail : byFamily.urban) ?? [] })
})
vi.stubGlobal('$fetch', spy)
return spy
}
afterEach(() => vi.unstubAllGlobals())
describe('publicTransport', () => {
it('interroge chaque famille de mode separement', async () => {
const spy = mockApi({})
await publicTransport(LILLE, BRUXELLES, new Date())
expect(spy).toHaveBeenCalledTimes(2)
const urls = spy.mock.calls.map(c => c[0] as string)
expect(urls.some(u => u.includes('HIGHSPEED_RAIL'))).toBe(true)
expect(urls.some(u => u.includes('COACH') && !u.includes('HIGHSPEED_RAIL'))).toBe(true)
})
it('separe le meilleur trajet ferroviaire du meilleur trajet urbain', async () => {
mockApi({
rail: [journey([leg('HIGHSPEED_RAIL', 100000, 90)], 90, 1)],
urban: [journey([leg('BUS', 95000, 180)], 180, 3)],
})
const r = await publicTransport(LILLE, BRUXELLES, new Date())
// 100 km de TGV a 2,4 g contre 95 km de bus a 113 g.
expect(r.train?.co2Grams).toBe(240)
expect(r.bus?.co2Grams).toBe(10735)
expect(r.train?.transfers).toBe(1)
})
it('retient le plus rapide quand plusieurs trajets ferroviaires existent', async () => {
mockApi({
rail: [
journey([leg('REGIONAL_RAIL', 100000, 180)], 180, 2),
journey([leg('HIGHSPEED_RAIL', 100000, 90)], 90, 0),
],
})
const r = await publicTransport(LILLE, BRUXELLES, new Date())
expect(r.train?.durationMin).toBe(90)
expect(r.train?.transfers).toBe(0)
})
it('renvoie null pour un mode absent plutot que d inventer un trajet', async () => {
mockApi({ urban: [journey([leg('COACH', 50000, 60)], 60)] })
const r = await publicTransport(LILLE, BRUXELLES, new Date())
expect(r.train).toBeNull()
expect(r.bus).not.toBeNull()
})
it('ecarte un itineraire entierement pietonnier', async () => {
// MOTIS propose parfois de marcher faute de mieux : ce n'est pas
// un trajet en transport en commun et il ne doit pas s'afficher.
mockApi({ rail: [journey([leg('WALK', 3000, 40)], 40)] })
const r = await publicTransport(LILLE, BRUXELLES, new Date())
expect(r.train).toBeNull()
})
it('s identifie aupres de Transitous comme leur politique d usage l exige', async () => {
const spy = mockApi({})
await publicTransport(LILLE, BRUXELLES, new Date())
const [url, options] = spy.mock.calls[0]
expect(url).toContain('fromPlace=50.6292,3.0573')
expect(options.headers['User-Agent']).toContain('lecheminvert.tech')
})
})