// Seam tests for the two helpers that replaced the per-service // try/catch + console.error + throw pattern: handleApiError (extracts a // user-facing message via a priority chain) and apiCall (HOF wrapper // around axios that hides the verb-signature branching). import { describe, it, expect, vi, beforeEach } from 'vitest' vi.mock('@/api/index.js', () => ({ default: { get: vi.fn(), post: vi.fn(), patch: vi.fn(), delete: vi.fn() }, })) import apiClient from '@/api/index.js' import { handleApiError, apiCall } from '@/api/apiError.js' beforeEach(() => { vi.clearAllMocks() }) describe('handleApiError — priority chain', () => { it("prefers error.response.data.detail when present", () => { const e = { response: { data: { detail: 'Bad id' } } } expect(handleApiError(e).message).toBe('Bad id') }) it("falls back to .message when .detail is absent", () => { const e = { response: { data: { message: 'msg-form' } } } expect(handleApiError(e).message).toBe('msg-form') }) it("falls back to .error when neither .detail nor .message is present", () => { const e = { response: { data: { error: 'err-form' } } } expect(handleApiError(e).message).toBe('err-form') }) it("falls back to 'HTTP {status}: {statusText}' when the body has no usable field", () => { const e = { response: { status: 500, statusText: 'Server Error', data: {} } } expect(handleApiError(e).message).toBe('HTTP 500: Server Error') }) it("falls back to error.message when there is no response at all", () => { expect(handleApiError({ message: 'network down' }).message).toBe('network down') }) it("falls back to the caller's fallback on a totally empty error", () => { expect(handleApiError({}, 'Failed to do thing').message).toBe('Failed to do thing') }) }) describe('apiCall — verb dispatch + return', () => { it('GET passes (url, {params, ...config}) and returns response.data', async () => { apiClient.get.mockResolvedValueOnce({ data: [1, 2, 3] }) const out = await apiCall('GET', '/api/foo/', { params: { a: 1 } }, 'Failed') expect(apiClient.get).toHaveBeenCalledWith('/api/foo/', { params: { a: 1 } }) expect(out).toEqual([1, 2, 3]) }) it('POST passes (url, data, {params, ...config})', async () => { apiClient.post.mockResolvedValueOnce({ data: { id: 7 } }) const out = await apiCall('POST', '/api/bar/', { data: { name: 'x' } }, 'Failed') expect(apiClient.post).toHaveBeenCalledWith('/api/bar/', { name: 'x' }, {}) expect(out).toEqual({ id: 7 }) }) it('PATCH passes (url, data, {params, ...config})', async () => { apiClient.patch.mockResolvedValueOnce({ data: { ok: true } }) const out = await apiCall('PATCH', '/api/foo/7/', { data: { name: 'y' } }, 'Failed') expect(apiClient.patch).toHaveBeenCalledWith('/api/foo/7/', { name: 'y' }, {}) expect(out).toEqual({ ok: true }) }) it('DELETE accepts null opts without crashing', async () => { // services pass `null` for delete calls with no body/params; the helper // must destructure safely. apiClient.delete.mockResolvedValueOnce({ data: '' }) const out = await apiCall('DELETE', '/api/foo/7/', null, 'Failed') expect(apiClient.delete).toHaveBeenCalledWith('/api/foo/7/', {}) expect(out).toBe('') }) it('forwards custom config (responseType + headers) for GET', async () => { apiClient.get.mockResolvedValueOnce({ data: new Blob(['x']) }) const out = await apiCall( 'GET', '/api/foo/', { config: { responseType: 'blob', headers: { Accept: '*/*' } } }, 'Failed', ) expect(apiClient.get).toHaveBeenCalledWith('/api/foo/', { responseType: 'blob', headers: { Accept: '*/*' }, }) expect(out).toBeInstanceOf(Blob) }) it('throws a wrapped Error on failure, taking the message from the priority chain', async () => { apiClient.get.mockRejectedValueOnce({ response: { status: 404, data: { detail: 'Not here' } }, message: 'Request failed with status code 404', }) await expect(apiCall('GET', '/api/foo/9/', null, 'Failed to load')).rejects.toThrow( 'Not here', ) }) it('falls back to HTTP {status}: {statusText} when the rejection has no body fields', async () => { apiClient.post.mockRejectedValueOnce({ response: { status: 503, statusText: 'Service Unavailable', data: {} }, message: 'boom', }) await expect( apiCall('POST', '/api/foo/', { data: {} }, 'Failed to save'), ).rejects.toThrow('HTTP 503: Service Unavailable') }) })