import { describe, expect, it, vi } from 'vitest' import { IamHttpAdapter } from '../index' type A = 'read' type R = 'post' type Ro = 'viewer' type S = 'org-1' function makeResponse(body: string, status = 400): Response { return { ok: false, status, text: async () => body, json: async () => JSON.parse(body), } as unknown as Response } describe('IamHttpAdapter error body cap', () => { it('caps a 10 MiB upstream error body at 200 chars + marker', async () => { const evilBody = 'X'.repeat(10 * 1024 * 1024) const fetch = vi.fn(async () => makeResponse(evilBody)) as unknown as typeof globalThis.fetch const adapter = new IamHttpAdapter({ baseUrl: 'https://api.example.com', fetch, retries: 0 }) try { await adapter.listPolicies() throw new Error('expected throw') } catch (err) { const msg = (err as Error).message // The cap holds even though the upstream returned 10 MiB. expect(msg.length).toBeLessThan(500) expect(msg).toContain('...(truncated)') expect(msg).toContain('HTTP 400') } }) it('preserves short error bodies verbatim (no false truncation)', async () => { const fetch = vi.fn(async () => makeResponse('validation failed for field X', 400), ) as unknown as typeof globalThis.fetch const adapter = new IamHttpAdapter({ baseUrl: 'https://api.example.com', fetch, retries: 0 }) try { await adapter.listPolicies() throw new Error('expected throw') } catch (err) { const msg = (err as Error).message expect(msg).toContain('validation failed for field X') expect(msg).not.toContain('...(truncated)') } }) it('caps body at exactly 200 chars (boundary)', async () => { const body = 'A'.repeat(201) const fetch = vi.fn(async () => makeResponse(body, 400)) as unknown as typeof globalThis.fetch const adapter = new IamHttpAdapter({ baseUrl: 'https://api.example.com', fetch, retries: 0 }) try { await adapter.listPolicies() throw new Error('expected throw') } catch (err) { const msg = (err as Error).message expect(msg).toContain('...(truncated)') // 200-char body + marker, plus the framing prefix. expect(msg).toContain('A'.repeat(200)) } }) it('passes through a 200-char body unchanged', async () => { const body = 'A'.repeat(200) const fetch = vi.fn(async () => makeResponse(body, 400)) as unknown as typeof globalThis.fetch const adapter = new IamHttpAdapter({ baseUrl: 'https://api.example.com', fetch, retries: 0 }) try { await adapter.listPolicies() throw new Error('expected throw') } catch (err) { const msg = (err as Error).message expect(msg).not.toContain('...(truncated)') } }) it('handles res.text() throwing: empty body in the error message, never crash', async () => { const broken = { ok: false, status: 500, text: async () => { throw new Error('body-read failed') }, json: async () => ({}), } as unknown as Response const fetch = vi.fn(async () => broken) as unknown as typeof globalThis.fetch const adapter = new IamHttpAdapter({ baseUrl: 'https://api.example.com', fetch, retries: 0 }) try { await adapter.listPolicies() throw new Error('expected throw') } catch (err) { const msg = (err as Error).message // Caps to empty string rather than crashing on the body read. expect(msg).toContain('HTTP 500') } }) it('returns body-capped messages on the 5xx path too (transient errors)', async () => { const evilBody = 'Z'.repeat(5000) const fetch = vi.fn(async () => makeResponse(evilBody, 503)) as unknown as typeof globalThis.fetch const adapter = new IamHttpAdapter({ baseUrl: 'https://api.example.com', fetch, retries: 0 }) try { await adapter.listPolicies() throw new Error('expected throw') } catch (err) { const msg = (err as Error).message expect(msg.length).toBeLessThan(500) expect(msg).toContain('HTTP 503') expect(msg).toContain('...(truncated)') } }) })