From aa3f9dc2da8ec252703b981f601e2f6dca505560 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sat, 28 Mar 2026 11:06:43 +0000 Subject: [PATCH] fix: handle empty successful API responses in client Co-authored-by: Dylan Boudro --- src/core/client.test.ts | 75 +++++++++++++++++++++++++++++++++++++++++ src/core/client.ts | 3 ++ 2 files changed, 78 insertions(+) create mode 100644 src/core/client.test.ts diff --git a/src/core/client.test.ts b/src/core/client.test.ts new file mode 100644 index 0000000..b050b26 --- /dev/null +++ b/src/core/client.test.ts @@ -0,0 +1,75 @@ +import { afterEach, describe, expect, it, vi } from 'vitest' +import { PixelmuseClient } from './client.js' +import { ApiError } from './types.js' + +describe('PixelmuseClient', () => { + afterEach(() => { + vi.restoreAllMocks() + }) + + it('deleteGeneration succeeds for 204 No Content responses', async () => { + const fetchSpy = vi.spyOn(globalThis, 'fetch').mockResolvedValue( + new Response(null, { status: 204 }), + ) + + const client = new PixelmuseClient('pm_live_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') + + await expect(client.deleteGeneration('gen_123')).resolves.toBeUndefined() + expect(fetchSpy).toHaveBeenCalledTimes(1) + }) + + it('getGeneration still parses successful JSON responses', async () => { + const generationPayload = { + id: 'gen_123', + status: 'succeeded', + model: 'nano-banana-2', + prompt: 'test prompt', + output: ['data:image/png;base64,AAAA'], + credits_charged: 1, + error: null, + created_at: '2026-03-28T00:00:00.000Z', + completed_at: '2026-03-28T00:00:01.000Z', + visibility: 'private', + } + vi.spyOn(globalThis, 'fetch').mockResolvedValue( + new Response(JSON.stringify(generationPayload), { + status: 200, + headers: { 'Content-Type': 'application/json' }, + }), + ) + + const client = new PixelmuseClient('pm_live_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') + const result = await client.getGeneration('gen_123') + + expect(result.id).toBe('gen_123') + expect(result.status).toBe('succeeded') + }) + + it('surfaces structured API errors from JSON payloads', async () => { + vi.spyOn(globalThis, 'fetch').mockResolvedValue( + new Response( + JSON.stringify({ + error: { message: 'Quota exceeded', code: 'QUOTA_EXCEEDED' }, + }), + { + status: 429, + headers: { + 'Content-Type': 'application/json', + 'X-RateLimit-Remaining': '0', + 'Retry-After': '12', + }, + }, + ), + ) + + const client = new PixelmuseClient('pm_live_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') + + await expect(client.getAccount()).rejects.toMatchObject({ + message: 'Quota exceeded', + status: 429, + code: 'QUOTA_EXCEEDED', + rateLimitRemaining: 0, + retryAfter: 12, + }) + }) +}) diff --git a/src/core/client.ts b/src/core/client.ts index f53afb6..d3c385f 100644 --- a/src/core/client.ts +++ b/src/core/client.ts @@ -79,6 +79,9 @@ export class PixelmuseClient { ) } + if (res.status === 204 || res.status === 205) { + return undefined as T + } return (await res.json()) as T }