import { afterEach, describe, expect, it, vi } from 'vitest' import { StorefrontApiError, storefrontFetch, storefrontFetchOrDefault, storefrontPost } from './api' afterEach(() => { delete process.env.NEXT_PUBLIC_API_URL delete process.env.API_INTERNAL_URL vi.restoreAllMocks() Reflect.deleteProperty(globalThis, 'fetch') }) describe('storefront API helpers', () => { it('unwraps successful GET responses from the data envelope', async () => { const fetchMock = vi.fn(async () => ({ ok: true, json: async () => ({ data: [{ id: 'vehicle_1' }] }) })) Object.defineProperty(globalThis, 'fetch', { configurable: true, value: fetchMock }) await expect(storefrontFetch('/storefront/vehicles')).resolves.toEqual([{ id: 'vehicle_1' }]) expect(fetchMock).toHaveBeenCalledWith(expect.stringMatching(/\/api\/v1\/storefront\/vehicles$/), { cache: 'no-store', credentials: 'include', }) }) it('throws rich storefront API errors', async () => { Object.defineProperty(globalThis, 'fetch', { configurable: true, value: vi.fn(async () => ({ ok: false, status: 409, json: async () => ({ message: 'Vehicle unavailable', error: 'VEHICLE_UNAVAILABLE', nextAvailableAt: '2026-07-01T10:00:00.000Z' }), })), }) await expect(storefrontFetch('/storefront/book')).rejects.toMatchObject({ name: 'StorefrontApiError', message: 'Vehicle unavailable', status: 409, code: 'VEHICLE_UNAVAILABLE', nextAvailableAt: '2026-07-01T10:00:00.000Z', }) }) it('returns fallbacks when GET requests fail', async () => { Object.defineProperty(globalThis, 'fetch', { configurable: true, value: vi.fn(async () => ({ ok: false, status: 500, json: async () => ({ message: 'Down' }) })), }) await expect(storefrontFetchOrDefault('/storefront/homepage', { hero: null })).resolves.toEqual({ hero: null }) }) it('posts JSON payloads and unwraps successful responses', async () => { process.env.NEXT_PUBLIC_API_URL = 'https://api.example.com/api/v1' const fetchMock = vi.fn(async () => ({ ok: true, json: async () => ({ data: { id: 'reservation_1' } }) })) Object.defineProperty(globalThis, 'fetch', { configurable: true, value: fetchMock }) await expect(storefrontPost('/site/reservations', { vehicleId: 'vehicle_1' })).resolves.toEqual({ id: 'reservation_1' }) expect(fetchMock).toHaveBeenCalledWith('https://api.example.com/api/v1/site/reservations', expect.objectContaining({ method: 'POST', credentials: 'include', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ vehicleId: 'vehicle_1' }), })) }) it('uses a generic error message when the response body is not JSON', async () => { Object.defineProperty(globalThis, 'fetch', { configurable: true, value: vi.fn(async () => ({ ok: false, status: 502, json: async () => { throw new Error('bad json') } })), }) await expect(storefrontFetch('/site/homepage')).rejects.toMatchObject({ name: 'StorefrontApiError', message: 'Request failed', status: 502, }) }) })