8376236a3c
- httpExternal : client dedie aux API publiques externes (URL absolue, sans cookie de session, timeout), seul point d'entree autorise pour un $fetch externe (regle frontend n°4). - useAddressAutocomplete : implementation BAN (api-adresse.data.gouv.fr), recherche ville (type=municipality) et adresse, mapping GeoJSON, throw en cas d'erreur/timeout (mode degrade cote composant). La recherche d'adresse n'impose pas type=housenumber (sinon 0 resultat tant qu'aucun numero n'est saisi) — spec-front mise a jour en consequence. - Tests Vitest : httpExternal, useAddressAutocomplete, et cas limites supplementaires pour formatPhoneFR.
133 lines
5.1 KiB
TypeScript
133 lines
5.1 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
import {
|
|
useAddressAutocomplete,
|
|
AddressAutocompleteUnavailableError,
|
|
} from '../useAddressAutocomplete'
|
|
|
|
// On mocke le helper d'appel externe : aucun vrai appel reseau a la BAN.
|
|
// vi.mock est hoiste par Vitest au-dessus des imports.
|
|
const mockHttp = vi.hoisted(() => vi.fn())
|
|
vi.mock('~/shared/utils/httpExternal', () => ({ httpExternal: mockHttp }))
|
|
|
|
const BAN_URL = 'https://api-adresse.data.gouv.fr/search/'
|
|
|
|
describe('useAddressAutocomplete', () => {
|
|
beforeEach(() => {
|
|
mockHttp.mockReset()
|
|
})
|
|
|
|
describe('searchCity', () => {
|
|
it('interroge la BAN en type=municipality et mappe { city, postalCode }', async () => {
|
|
mockHttp.mockResolvedValueOnce({
|
|
type: 'FeatureCollection',
|
|
features: [
|
|
{ properties: { city: 'Amiens', postcode: '80000', name: 'Amiens', type: 'municipality' } },
|
|
{ properties: { city: 'Amiens', postcode: '80080', name: 'Amiens', type: 'municipality' } },
|
|
],
|
|
})
|
|
|
|
const { searchCity } = useAddressAutocomplete()
|
|
const res = await searchCity('80000')
|
|
|
|
expect(mockHttp).toHaveBeenCalledWith(
|
|
BAN_URL,
|
|
expect.objectContaining({ query: { q: '80000', type: 'municipality' } }),
|
|
)
|
|
expect(res).toEqual([
|
|
{ city: 'Amiens', postalCode: '80000' },
|
|
{ city: 'Amiens', postalCode: '80080' },
|
|
])
|
|
})
|
|
|
|
it('throw une AddressAutocompleteUnavailableError sur erreur reseau / 5xx', async () => {
|
|
mockHttp.mockRejectedValueOnce(new Error('500 Server Error'))
|
|
|
|
const { searchCity } = useAddressAutocomplete()
|
|
|
|
await expect(searchCity('80000')).rejects.toBeInstanceOf(AddressAutocompleteUnavailableError)
|
|
})
|
|
|
|
it('throw une AddressAutocompleteUnavailableError sur timeout', async () => {
|
|
mockHttp.mockRejectedValueOnce(new Error('The operation was aborted due to timeout'))
|
|
|
|
const { searchCity } = useAddressAutocomplete()
|
|
|
|
await expect(searchCity('80000')).rejects.toBeInstanceOf(AddressAutocompleteUnavailableError)
|
|
})
|
|
})
|
|
|
|
describe('searchAddress', () => {
|
|
it('interroge la BAN avec postcode et mappe la suggestion', async () => {
|
|
mockHttp.mockResolvedValueOnce({
|
|
type: 'FeatureCollection',
|
|
features: [
|
|
{
|
|
properties: {
|
|
label: '8 Boulevard du Port 80000 Amiens',
|
|
name: '8 Boulevard du Port',
|
|
street: 'Boulevard du Port',
|
|
postcode: '80000',
|
|
city: 'Amiens',
|
|
type: 'housenumber',
|
|
},
|
|
},
|
|
],
|
|
})
|
|
|
|
const { searchAddress } = useAddressAutocomplete()
|
|
const res = await searchAddress('8 boulevard du port', '80000')
|
|
|
|
expect(mockHttp).toHaveBeenCalledWith(
|
|
BAN_URL,
|
|
expect.objectContaining({
|
|
query: { q: '8 boulevard du port', postcode: '80000' },
|
|
}),
|
|
)
|
|
expect(res).toEqual([
|
|
{
|
|
label: '8 Boulevard du Port 80000 Amiens',
|
|
street: '8 Boulevard du Port',
|
|
postalCode: '80000',
|
|
city: 'Amiens',
|
|
},
|
|
])
|
|
})
|
|
|
|
it('omet le parametre postcode quand aucun code postal n\'est fourni', async () => {
|
|
mockHttp.mockResolvedValueOnce({ type: 'FeatureCollection', features: [] })
|
|
|
|
const { searchAddress } = useAddressAutocomplete()
|
|
await searchAddress('8 boulevard du port')
|
|
|
|
expect(mockHttp).toHaveBeenCalledWith(
|
|
BAN_URL,
|
|
expect.objectContaining({
|
|
query: { q: '8 boulevard du port' },
|
|
}),
|
|
)
|
|
})
|
|
|
|
it('ne restreint PAS la recherche a type=housenumber (sinon la BAN ne renvoie rien tant qu\'aucun numero n\'est saisi)', async () => {
|
|
// Regression : avec `type=housenumber`, une saisie de nom de rue sans
|
|
// numero (ex: « boulevard du port ») renvoie 0 resultat cote BAN.
|
|
mockHttp.mockResolvedValueOnce({ type: 'FeatureCollection', features: [] })
|
|
|
|
const { searchAddress } = useAddressAutocomplete()
|
|
await searchAddress('boulevard du port', '80000')
|
|
|
|
const sentQuery = mockHttp.mock.calls[0]?.[1]?.query as Record<string, string>
|
|
expect(sentQuery.type).toBeUndefined()
|
|
})
|
|
|
|
it('throw une AddressAutocompleteUnavailableError sur erreur reseau', async () => {
|
|
mockHttp.mockRejectedValueOnce(new Error('network down'))
|
|
|
|
const { searchAddress } = useAddressAutocomplete()
|
|
|
|
await expect(searchAddress('8 boulevard du port', '80000')).rejects.toBeInstanceOf(
|
|
AddressAutocompleteUnavailableError,
|
|
)
|
|
})
|
|
})
|
|
})
|