// Seam tests for paginatedGet — the helper that lets each service write // `paginatedGet(apiClient, E.X.LIST)` instead of hand-rolling the DRF // pagination walk. Exercises the happy path, multi-page walk, non-DRF // array fallback, unexpected shape, parameter forwarding, and the // 100-page safety cap. import { describe, it, expect, vi, beforeEach } from 'vitest' vi.mock('@/api/index.js', () => ({ default: { get: vi.fn() }, })) import apiClient from '@/api/index.js' import { paginatedGet } from '@/api/paginate.js' const URL = '/api/test/' const ok = (results, next = null) => ({ data: { count: results.length, next, previous: null, results }, }) beforeEach(() => { vi.clearAllMocks() }) describe('paginatedGet — single DRF page', () => { it('returns flat results from one page with next=null', async () => { apiClient.get.mockResolvedValueOnce(ok([{ id: 1 }, { id: 2 }])) const out = await paginatedGet(apiClient, URL) expect(out).toEqual([{ id: 1 }, { id: 2 }]) expect(apiClient.get).toHaveBeenCalledTimes(1) expect(apiClient.get.mock.calls[0][0]).toBe(URL) expect(apiClient.get.mock.calls[0][1]).toMatchObject({ params: { page: 1, page_size: 100 }, }) }) }) describe('paginatedGet — multi-page walk', () => { it('walks pages until next is null, flattening every results[]', async () => { apiClient.get .mockResolvedValueOnce(ok([{ id: 1 }, { id: 2 }], '/api/test/?page=2')) .mockResolvedValueOnce(ok([{ id: 3 }, { id: 4 }], '/api/test/?page=3')) .mockResolvedValueOnce(ok([{ id: 5 }])) const out = await paginatedGet(apiClient, URL) expect(apiClient.get).toHaveBeenCalledTimes(3) expect(apiClient.get.mock.calls[0][1].params).toMatchObject({ page: 1 }) expect(apiClient.get.mock.calls[1][1].params).toMatchObject({ page: 2 }) expect(apiClient.get.mock.calls[2][1].params).toMatchObject({ page: 3 }) expect(out.map((x) => x.id)).toEqual([1, 2, 3, 4, 5]) }) }) describe('paginatedGet — non-DRF array fallback', () => { it('returns a top-level array response as-is and stops after one call', async () => { apiClient.get.mockResolvedValueOnce({ data: [{ id: 'a' }, { id: 'b' }] }) const out = await paginatedGet(apiClient, URL) expect(out).toEqual([{ id: 'a' }, { id: 'b' }]) expect(apiClient.get).toHaveBeenCalledTimes(1) }) }) describe('paginatedGet — unexpected response shape', () => { it('stops gracefully and returns whatever was collected so far', async () => { apiClient.get .mockResolvedValueOnce(ok([{ id: 1 }], '/api/test/?page=2')) .mockResolvedValueOnce({ data: { unexpected: 'shape' } }) const out = await paginatedGet(apiClient, URL) // Page 1 returned 1 item, page 2 was malformed → loop exits cleanly. expect(out.map((x) => x.id)).toEqual([1]) expect(apiClient.get).toHaveBeenCalledTimes(2) }) }) describe('paginatedGet — extra params', () => { it('forwards caller params to every page request', async () => { apiClient.get .mockResolvedValueOnce(ok([{ id: 1 }], '/api/test/?foo=bar&page=2')) .mockResolvedValueOnce(ok([{ id: 2 }])) await paginatedGet(apiClient, URL, { foo: 'bar' }) expect(apiClient.get.mock.calls[0][1].params).toMatchObject({ foo: 'bar', page: 1 }) expect(apiClient.get.mock.calls[1][1].params).toMatchObject({ foo: 'bar', page: 2 }) }) }) describe('paginatedGet — 100-page safety cap', () => { it('stops after 100 GETs even when next stays truthy', async () => { // Always return a truthy next so the loop would run forever without a cap. apiClient.get.mockResolvedValue(ok([{ id: 1 }], '/api/test/?page=2')) const out = await paginatedGet(apiClient, URL) expect(apiClient.get).toHaveBeenCalledTimes(100) expect(out).toHaveLength(100) }) })