From 043946b8e837bb2cf54815cdeb111454eaac5b7d Mon Sep 17 00:00:00 2001 From: Ricardo-M-L Date: Fri, 23 Oct 2026 00:38:10 +0800 Subject: [PATCH] fix: avoid reporting unsaved empty image responses --- src/sdk/image/index.ts | 17 ++++---- test/sdk/image-empty-save.test.ts | 65 +++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 8 deletions(-) create mode 100644 test/sdk/image-empty-save.test.ts diff --git a/src/sdk/image/index.ts b/src/sdk/image/index.ts index 92998642..2bde5ed4 100644 --- a/src/sdk/image/index.ts +++ b/src/sdk/image/index.ts @@ -48,27 +48,28 @@ export class ImageSDK extends Client { if (options.out) { // Single-image, exact path - const count = (fmt === 'base64' - ? (response.data.image_base64 || []).length - : (response.data.image_urls || []).length); + const images = fmt === 'base64' + ? response.data.image_base64 || [] + : response.data.image_urls || []; - if (count > 1) { + if (images.length > 1) { throw new SDKError( 'Cannot use `out` with multiple images. Use `outDir` instead.', ExitCode.USAGE, ); } + const image = images[0]; + if (!image?.trim()) return saved; + const destPath = resolve(options.out); const dir = dirname(destPath); if (!existsSync(dir)) mkdirSync(dir, { recursive: true }); if (fmt === 'base64') { - const image = (response.data.image_base64 || [])[0]; - if (image) writeFileSync(destPath, image, 'base64'); + writeFileSync(destPath, image, 'base64'); } else { - const imageUrl = (response.data.image_urls || [])[0]; - if (imageUrl) await downloadFile(imageUrl, destPath, { quiet }); + await downloadFile(image, destPath, { quiet }); } saved.push(destPath); } else { diff --git a/test/sdk/image-empty-save.test.ts b/test/sdk/image-empty-save.test.ts new file mode 100644 index 00000000..eac64d89 --- /dev/null +++ b/test/sdk/image-empty-save.test.ts @@ -0,0 +1,65 @@ +import { describe, expect, it } from 'bun:test'; +import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { ImageSDK } from '../../src/sdk/image'; +import type { ImageResponse } from '../../src/types/api'; +import { withStubbedFetch } from '../helpers/fetch-stub'; + +const sdk = new ImageSDK({ apiKey: 'test-key', region: 'global' }); + +function response(format: 'url' | 'base64', images?: string[]): ImageResponse { + return { + base_resp: { status_code: 0, status_msg: 'ok' }, + data: { + ...(format === 'url' ? { image_urls: images } : { image_base64: images }), + task_id: 'empty-images', + success_count: 0, + failed_count: 1, + }, + }; +} + +describe('ImageSDK.save with no image data', () => { + for (const format of ['url', 'base64'] as const) { + for (const images of [undefined, [], [''], [' \n\t ']]) { + it(`does not report a saved path or create directories for ${format} ${JSON.stringify(images)}`, async () => { + const dir = mkdtempSync(join(tmpdir(), 'mmx-image-empty-')); + const out = join(dir, 'nested', 'image.jpg'); + try { + expect(await sdk.save(response(format, images), { out, responseFormat: format })).toEqual([]); + expect(existsSync(join(dir, 'nested'))).toBe(false); + } finally { + rmSync(dir, { recursive: true, force: true }); + } + }); + } + + it(`leaves an existing destination intact without reporting it as saved (${format})`, async () => { + const dir = mkdtempSync(join(tmpdir(), 'mmx-image-existing-')); + const out = join(dir, 'image.jpg'); + try { + writeFileSync(out, 'existing image'); + expect(await sdk.save(response(format, []), { out, responseFormat: format })).toEqual([]); + expect(readFileSync(out, 'utf8')).toBe('existing image'); + } finally { + rmSync(dir, { recursive: true, force: true }); + } + }); + } + + it('still downloads a valid single URL image to the requested nested path', async () => { + const dir = mkdtempSync(join(tmpdir(), 'mmx-image-download-')); + const out = join(dir, 'nested', 'image.jpg'); + const bytes = new Uint8Array([0xff, 0xd8, 0xff, 0xd9]); + try { + await withStubbedFetch(() => new Response(bytes), async (sent) => { + expect(await sdk.save(response('url', ['https://example.com/image']), { out })).toEqual([out]); + expect(sent.url).toBe('https://example.com/image'); + }); + expect(new Uint8Array(readFileSync(out))).toEqual(bytes); + } finally { + rmSync(dir, { recursive: true, force: true }); + } + }); +});