diff --git a/lib/services/pax8-client.test.ts b/lib/services/pax8-client.test.ts index 10f781b..cdcfb71 100644 --- a/lib/services/pax8-client.test.ts +++ b/lib/services/pax8-client.test.ts @@ -238,4 +238,101 @@ describe('Pax8Client', () => { expect(authHeader).toMatch(/^Bearer /); } }); + + it('listAllInvoices() concatenates content across all pages in order', async () => { + const pages = [ + { content: [{ id: 'inv-1' }, { id: 'inv-2' }], totalPages: 2 }, + { content: [{ id: 'inv-3' }], totalPages: 2 }, + ]; + const { fetchMock, calls } = makeMultiPageFetchMock({ resource: 'invoices', pages }); + vi.stubGlobal('fetch', fetchMock); + + const client = new Pax8Client({ clientId: 'id1', clientSecret: SECRET }); + const result = await client.listAllInvoices(); + + expect(result).toEqual([{ id: 'inv-1' }, { id: 'inv-2' }, { id: 'inv-3' }]); + + const invoiceCalls = calls.filter(c => c.url.includes('/invoices') && !c.url.includes('/items')); + expect(invoiceCalls).toHaveLength(2); + for (const c of invoiceCalls) { + expect(c.url).toContain('size=200'); + } + }); + + it('listAllInvoiceItems(invoiceId) requests the nested per-invoice path and concatenates its pages', async () => { + const invoiceId = 'inv-123'; + const pages = [ + { content: [{ id: 'item-1' }, { id: 'item-2' }], totalPages: 2 }, + { content: [{ id: 'item-3' }], totalPages: 2 }, + ]; + const calls: Array<{ url: string; init?: RequestInit }> = []; + const fetchMock = vi.fn(async (url: string, init?: RequestInit) => { + calls.push({ url, init }); + + if (url.includes('/token')) { + return { + ok: true, + status: 200, + json: async () => ({ access_token: 'tok', token_type: 'Bearer', expires_in: 86400 }), + text: async () => '', + } as unknown as Response; + } + + if (url.includes(`/invoices/${invoiceId}/items`)) { + const match = url.match(/page=(\d+)/); + const pageNum = match ? parseInt(match[1], 10) : 0; + const p = pages[pageNum]; + return { + ok: true, + status: 200, + json: async () => ({ + content: p.content, + page: { size: 200, totalElements: pages.reduce((n, pg) => n + pg.content.length, 0), totalPages: p.totalPages, number: pageNum }, + }), + text: async () => '', + } as unknown as Response; + } + + return { + ok: true, + status: 200, + json: async () => ({ content: [], page: { size: 200, totalElements: 0, totalPages: 1, number: 0 } }), + text: async () => '', + } as unknown as Response; + }); + vi.stubGlobal('fetch', fetchMock); + + const client = new Pax8Client({ clientId: 'id1', clientSecret: SECRET }); + const result = await client.listAllInvoiceItems(invoiceId); + + expect(result).toEqual([{ id: 'item-1' }, { id: 'item-2' }, { id: 'item-3' }]); + + const itemCalls = calls.filter(c => c.url.includes(`/invoices/${invoiceId}/items`)); + expect(itemCalls).toHaveLength(2); + expect(itemCalls.some(c => c.url.includes('/invoices/inv-123/items'))).toBe(true); + for (const c of itemCalls) { + expect(c.url).toContain('size=200'); + } + }); + + it('listAllInvoices() and listAllInvoiceItems() only issue GET requests with an Authorization: Bearer header', async () => { + const invoicePages = [{ content: [{ id: 'inv-1' }], totalPages: 1 }]; + const { fetchMock: invoiceFetchMock, calls: invoiceCalls } = makeMultiPageFetchMock({ + resource: 'invoices', + pages: invoicePages, + }); + vi.stubGlobal('fetch', invoiceFetchMock); + + const client = new Pax8Client({ clientId: 'id1', clientSecret: SECRET }); + await client.listAllInvoices(); + await client.listAllInvoiceItems('inv-123'); + + const dataCalls = invoiceCalls.filter(c => !c.url.includes('/token')); + expect(dataCalls.length).toBeGreaterThan(0); + for (const c of dataCalls) { + expect(c.init?.method === undefined || c.init?.method === 'GET').toBe(true); + const authHeader = (c.init!.headers as Record)['Authorization']; + expect(authHeader).toMatch(/^Bearer /); + } + }); });