import { beforeEach, describe, expect, it, vi } from 'vitest' import { TermsApi } from './TermsApi' import type { ApiClient } from '../../core/ApiClient' import type { Term, CreateTermPayload } from './types' import type { Paginated } from '../../core/types' // ── Mock client factory ────────────────────────────────────────────── function createMockClient(): ApiClient { return { setAccessToken: vi.fn().mockReturnThis(), request: vi.fn(), get: vi.fn(), post: vi.fn(), put: vi.fn(), patch: vi.fn(), delete: vi.fn(), } } // ── Fixtures ───────────────────────────────────────────────────────── const term: Term = { id: 'term-1', title: { en: 'Terms of Service' }, text: { en: 'You agree to...' }, expirationDate: '2025-01-01', version: '1.0', agnusId: 'agnus-1', accountId: null, userId: null, agreementDate: null, createdAt: '2024-01-01T00:00:00Z', updatedAt: '2024-01-01T00:00:00Z', } const paginated: Paginated = { data: [term], total: 1, limit: 10, skip: 0, currentPage: 1, lastPage: 1, from: 1, to: 1, } // ── Tests ──────────────────────────────────────────────────────────── describe('TermsApi', () => { let client: ReturnType let api: TermsApi beforeEach(() => { client = createMockClient() api = new TermsApi(client) }) // ── getMany ────────────────────────────────────────────────────── describe('getMany', () => { it('calls client.get with /terms when no query is provided', async () => { vi.mocked(client.get).mockResolvedValue(paginated) const result = await api.getMany() expect(client.get).toHaveBeenCalledWith('/terms', undefined) expect(result).toEqual(paginated) }) it('serializes query parameters', async () => { vi.mocked(client.get).mockResolvedValue(paginated) await api.getMany({ page: 2 }) const url = vi.mocked(client.get).mock.calls[0][0] as string expect(url).toContain('/terms?') expect(url).toContain('page=2') }) it('forwards custom headers', async () => { vi.mocked(client.get).mockResolvedValue(paginated) const headers = { 'X-Custom': 'value' } await api.getMany(undefined, headers) expect(client.get).toHaveBeenCalledWith('/terms', headers) }) }) // ── getById ────────────────────────────────────────────────────── describe('getById', () => { it('calls client.get with /terms/:id', async () => { vi.mocked(client.get).mockResolvedValue(term) const result = await api.getById('term-1') expect(client.get).toHaveBeenCalledWith('/terms/term-1', undefined) expect(result).toEqual(term) }) it('forwards custom headers', async () => { vi.mocked(client.get).mockResolvedValue(term) const headers = { 'X-Tenant': 'abc' } await api.getById('term-1', undefined, headers) expect(client.get).toHaveBeenCalledWith('/terms/term-1', headers) }) }) // ── create ─────────────────────────────────────────────────────── describe('create', () => { it('calls client.post with /terms and body', async () => { vi.mocked(client.post).mockResolvedValue(term) const body: CreateTermPayload = { title: { en: 'ToS' }, text: { en: 'Text' }, expirationDate: '2025-01-01', version: '1.0', agnusId: 'agnus-1', } const result = await api.create(body) expect(client.post).toHaveBeenCalledWith('/terms', body, undefined) expect(result).toEqual(term) }) }) // ── updateById ─────────────────────────────────────────────────── describe('updateById', () => { it('calls client.put with /terms/:id and body', async () => { vi.mocked(client.put).mockResolvedValue(term) const result = await api.updateById('term-1', { version: '1.1' }) expect(client.put).toHaveBeenCalledWith('/terms/term-1', { version: '1.1' }, undefined) expect(result).toEqual(term) }) it('forwards custom headers', async () => { vi.mocked(client.put).mockResolvedValue(term) const headers = { 'If-Match': 'etag' } await api.updateById('term-1', { version: '2.0' }, headers) expect(client.put).toHaveBeenCalledWith('/terms/term-1', { version: '2.0' }, headers) }) }) // ── deleteById ─────────────────────────────────────────────────── describe('deleteById', () => { it('calls client.delete with /terms/:id', async () => { vi.mocked(client.delete).mockResolvedValue(term) const result = await api.deleteById('term-1') expect(client.delete).toHaveBeenCalledWith('/terms/term-1', undefined) expect(result).toEqual(term) }) it('forwards custom headers', async () => { vi.mocked(client.delete).mockResolvedValue(term) const headers = { 'X-Reason': 'cleanup' } await api.deleteById('term-1', headers) expect(client.delete).toHaveBeenCalledWith('/terms/term-1', headers) }) }) // ── getNotAgreed ───────────────────────────────────────────────── describe('getNotAgreed', () => { it('calls client.get with /terms/terms-not-agreed when no query is provided', async () => { vi.mocked(client.get).mockResolvedValue(paginated) const result = await api.getNotAgreed() expect(client.get).toHaveBeenCalledWith('/terms/terms-not-agreed', undefined) expect(result).toEqual(paginated) }) it('serializes query parameters', async () => { vi.mocked(client.get).mockResolvedValue(paginated) await api.getNotAgreed({ page: 1, perPage: 5 }) const url = vi.mocked(client.get).mock.calls[0][0] as string expect(url).toContain('/terms/terms-not-agreed?') expect(url).toContain('page=1') }) it('forwards custom headers', async () => { vi.mocked(client.get).mockResolvedValue(paginated) const headers = { 'X-Custom': 'h' } await api.getNotAgreed(undefined, headers) expect(client.get).toHaveBeenCalledWith('/terms/terms-not-agreed', headers) }) }) // ── Error propagation ──────────────────────────────────────────── describe('error propagation', () => { it('propagates errors from getMany', async () => { vi.mocked(client.get).mockRejectedValue(new Error('Network error')) await expect(api.getMany()).rejects.toThrow('Network error') }) it('propagates errors from getNotAgreed', async () => { vi.mocked(client.get).mockRejectedValue(new Error('Forbidden')) await expect(api.getNotAgreed()).rejects.toThrow('Forbidden') }) }) })