Files
geointel/frontend/src/services/api/client.test.ts
T
JensandClaude Opus 5 7a5f64e5ca Deel geen antwoorden meer over een identiteitswissel heen
De verzoekdeler uit de vorige commit houdt een antwoord driehonderd
milliseconde vast om de opstartcascade op te vangen. Uitloggen herlaadt de
pagina echter niet, dus die tabel overleeft een sessiewissel: binnen dat venster
zou de volgende gebruiker het antwoord van de vorige kunnen krijgen.

Met de hand is dat nauwelijks te bereiken, maar een verzoekcache die een
identiteitswissel overleeft is hoe dan ook fout. De tabel wordt nu gewist bij
uitloggen, bij aanmelden en zodra de sessie verloopt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 15:04:45 +02:00

129 lines
4.3 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { apiGet, apiPost, vergeetGedeeldeVerzoeken } from './client'
function antwoord(data: unknown) {
return {
ok: true,
status: 200,
json: async () => ({ data }),
} as unknown as Response
}
describe('apiGet deelt verzoeken', () => {
beforeEach(() => {
vi.useFakeTimers()
})
afterEach(() => {
vi.runOnlyPendingTimers()
vi.useRealTimers()
vi.restoreAllMocks()
})
it('stuurt één verzoek wanneer drie aanroepers tegelijk hetzelfde vragen', async () => {
const fetchSpy = vi.fn().mockResolvedValue(antwoord({ items: [1] }))
vi.stubGlobal('fetch', fetchSpy)
const [a, b, c] = await Promise.all([
apiGet<{ items: number[] }>('/api/v1/projects'),
apiGet<{ items: number[] }>('/api/v1/projects'),
apiGet<{ items: number[] }>('/api/v1/projects'),
])
expect(fetchSpy).toHaveBeenCalledTimes(1)
expect(a).toEqual({ items: [1] })
expect(b).toBe(a)
expect(c).toBe(a)
})
it('deelt ook wanneer het tweede verzoek kort ná het eerste komt', async () => {
// Dit was het echte geval bij het opstarten: twee effecten die ongeveer
// negentig milliseconde na elkaar dezelfde gegevens opvroegen, dus de
// eerste was al klaar voordat de tweede begon.
const fetchSpy = vi.fn().mockResolvedValue(antwoord({ items: [] }))
vi.stubGlobal('fetch', fetchSpy)
await apiGet('/api/v1/projects/p-1/datasets')
await vi.advanceTimersByTimeAsync(90)
await apiGet('/api/v1/projects/p-1/datasets')
expect(fetchSpy).toHaveBeenCalledTimes(1)
})
it('haalt opnieuw op zodra het deelvenster voorbij is', async () => {
const fetchSpy = vi.fn().mockResolvedValue(antwoord({ items: [] }))
vi.stubGlobal('fetch', fetchSpy)
await apiGet('/api/v1/projects/p-1/areas')
await vi.advanceTimersByTimeAsync(400)
await apiGet('/api/v1/projects/p-1/areas')
expect(fetchSpy).toHaveBeenCalledTimes(2)
})
it('houdt verschillende paden uit elkaar', async () => {
const fetchSpy = vi.fn().mockResolvedValue(antwoord({ items: [] }))
vi.stubGlobal('fetch', fetchSpy)
await Promise.all([
apiGet('/api/v1/projects'),
apiGet('/api/v1/projects?name=Kempen&limit=1'),
])
expect(fetchSpy).toHaveBeenCalledTimes(2)
})
it('deelt geen POST — twee keer versturen is een andere handeling', async () => {
const fetchSpy = vi.fn().mockResolvedValue(antwoord({ ok: true }))
vi.stubGlobal('fetch', fetchSpy)
await Promise.all([
apiPost('/api/v1/projects', { name: 'a' }),
apiPost('/api/v1/projects', { name: 'a' }),
])
expect(fetchSpy).toHaveBeenCalledTimes(2)
})
it('deelt niets meer over een identiteitswissel heen', async () => {
// Uitloggen herlaadt de pagina niet, dus de tabel leeft door. Zonder deze
// reset zou de volgende gebruiker binnen het deelvenster het antwoord van
// de vorige kunnen krijgen.
const fetchSpy = vi.fn()
.mockResolvedValueOnce(antwoord({ items: ['werkruimte van A'] }))
.mockResolvedValueOnce(antwoord({ items: ['werkruimte van B'] }))
vi.stubGlobal('fetch', fetchSpy)
const vanA = await apiGet<{ items: string[] }>('/api/v1/projects')
vergeetGedeeldeVerzoeken()
const vanB = await apiGet<{ items: string[] }>('/api/v1/projects')
expect(fetchSpy).toHaveBeenCalledTimes(2)
expect(vanA.items).toEqual(['werkruimte van A'])
expect(vanB.items).toEqual(['werkruimte van B'])
})
it('wist de tabel ook wanneer de sessie verloopt', async () => {
const fetchSpy = vi.fn().mockResolvedValue(antwoord({ items: [] }))
vi.stubGlobal('fetch', fetchSpy)
await apiGet('/api/v1/projects')
window.dispatchEvent(new CustomEvent('geointel:session-expired'))
await apiGet('/api/v1/projects')
expect(fetchSpy).toHaveBeenCalledTimes(2)
})
it('laat een mislukt verzoek niet in de tabel achter', async () => {
const fetchSpy = vi.fn()
.mockRejectedValueOnce(new Error('netwerk weg'))
.mockResolvedValue(antwoord({ items: [] }))
vi.stubGlobal('fetch', fetchSpy)
await expect(apiGet('/api/v1/detection/models')).rejects.toThrow('netwerk weg')
await vi.advanceTimersByTimeAsync(400)
await expect(apiGet('/api/v1/detection/models')).resolves.toEqual({ items: [] })
expect(fetchSpy).toHaveBeenCalledTimes(2)
})
})