trainlog/web/src/api/webPreferences.test.ts

48 lines
2 KiB
TypeScript

import { describe, expect, it, vi } from 'vitest'
import { fetchWebPreferences, saveWebPreferences } from './webPreferences'
const token = 'a'.repeat(64)
function response(value: unknown, revision: number, status = 200) {
return Promise.resolve({
ok: status >= 200 && status < 300,
status,
headers: new Headers({ ETag: `"${revision}"`, 'X-Trainlog-CSRF-Token': token }),
json: () => Promise.resolve(value),
})
}
describe('Web presentation preferences API', () => {
it('loads the French default and persists ISO with revision and CSRF guards', async () => {
const initial = {
format: 'trainlog-web-preferences', version: 1, revision: 0,
date_format: 'fr', source: 'default',
}
const persisted = { ...initial, revision: 1, date_format: 'iso', source: 'persisted' }
const fetchMock = vi.fn()
.mockImplementationOnce(() => response(initial, 0))
.mockImplementationOnce(() => response(persisted, 1))
vi.stubGlobal('fetch', fetchMock)
const snapshot = await fetchWebPreferences()
expect(snapshot.date_format).toBe('fr')
const saved = await saveWebPreferences(snapshot, 'iso')
expect(saved).toMatchObject({ revision: 1, date_format: 'iso', source: 'persisted' })
const request = fetchMock.mock.calls[1][1] as RequestInit
expect(request.method).toBe('PUT')
expect(request.headers).toMatchObject({
'If-Match': '"0"',
'X-Trainlog-CSRF-Token': token,
})
expect(JSON.parse(String(request.body))).toMatchObject({ revision: 0, date_format: 'iso' })
})
it('reports revision conflicts explicitly', async () => {
const snapshot = {
format: 'trainlog-web-preferences' as const, version: 1 as const, revision: 4,
date_format: 'fr' as const, source: 'persisted' as const, etag: '"4"', csrfToken: token,
}
vi.stubGlobal('fetch', vi.fn(() => response({ error: 'revision_conflict' }, 4, 412)))
await expect(saveWebPreferences(snapshot, 'iso')).rejects.toThrow('revision_conflict')
})
})