imagex-upload.test.js
1 import * as fs from 'node:fs'; 2 import * as os from 'node:os'; 3 import * as path from 'node:path'; 4 import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; 5 import { CommandExecutionError } from '@jackwener/opencli/errors'; 6 import { imagexUpload } from './imagex-upload.js'; 7 // ── Helpers ────────────────────────────────────────────────────────────────── 8 function makeTempImage(ext = '.jpg') { 9 const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'imagex-test-')); 10 const filePath = path.join(dir, `cover${ext}`); 11 fs.writeFileSync(filePath, Buffer.from([0xff, 0xd8, 0xff, 0xe0])); // minimal JPEG header bytes 12 return filePath; 13 } 14 const FAKE_UPLOAD_INFO = { 15 upload_url: 'https://imagex.bytedance.com/upload/presigned/fake', 16 store_uri: 'tos-cn-i-alisg.example.com/cover/abc123', 17 }; 18 // ── Tests ───────────────────────────────────────────────────────────────────── 19 describe('imagexUpload', () => { 20 let imagePath; 21 beforeEach(() => { 22 imagePath = makeTempImage('.jpg'); 23 }); 24 afterEach(() => { 25 // Clean up temp files 26 try { 27 fs.unlinkSync(imagePath); 28 fs.rmdirSync(path.dirname(imagePath)); 29 } 30 catch { 31 // ignore cleanup errors 32 } 33 vi.restoreAllMocks(); 34 }); 35 it('throws CommandExecutionError when image file does not exist', async () => { 36 await expect(imagexUpload('/nonexistent/path/cover.jpg', FAKE_UPLOAD_INFO)).rejects.toThrow(CommandExecutionError); 37 await expect(imagexUpload('/nonexistent/path/cover.jpg', FAKE_UPLOAD_INFO)).rejects.toThrow('Cover image file not found'); 38 }); 39 it('PUTs the image and returns store_uri on success', async () => { 40 const mockFetch = vi.fn().mockResolvedValue({ 41 ok: true, 42 status: 200, 43 text: vi.fn().mockResolvedValue(''), 44 }); 45 vi.stubGlobal('fetch', mockFetch); 46 const result = await imagexUpload(imagePath, FAKE_UPLOAD_INFO); 47 expect(result).toBe(FAKE_UPLOAD_INFO.store_uri); 48 expect(mockFetch).toHaveBeenCalledOnce(); 49 const [url, init] = mockFetch.mock.calls[0]; 50 expect(url).toBe(FAKE_UPLOAD_INFO.upload_url); 51 expect(init.method).toBe('PUT'); 52 expect(init.headers['Content-Type']).toBe('image/jpeg'); 53 }); 54 it('uses image/png Content-Type for .png files', async () => { 55 const pngPath = makeTempImage('.png'); 56 const mockFetch = vi.fn().mockResolvedValue({ 57 ok: true, 58 status: 200, 59 text: vi.fn().mockResolvedValue(''), 60 }); 61 vi.stubGlobal('fetch', mockFetch); 62 try { 63 await imagexUpload(pngPath, FAKE_UPLOAD_INFO); 64 const [, init] = mockFetch.mock.calls[0]; 65 expect(init.headers['Content-Type']).toBe('image/png'); 66 } 67 finally { 68 try { 69 fs.unlinkSync(pngPath); 70 fs.rmdirSync(path.dirname(pngPath)); 71 } 72 catch { 73 // ignore 74 } 75 } 76 }); 77 it('throws CommandExecutionError on non-2xx PUT response', async () => { 78 const mockFetch = vi.fn().mockResolvedValue({ 79 ok: false, 80 status: 403, 81 text: vi.fn().mockResolvedValue('Forbidden'), 82 }); 83 vi.stubGlobal('fetch', mockFetch); 84 await expect(imagexUpload(imagePath, FAKE_UPLOAD_INFO)).rejects.toThrow(CommandExecutionError); 85 await expect(imagexUpload(imagePath, FAKE_UPLOAD_INFO)).rejects.toThrow('ImageX upload failed with status 403'); 86 }); 87 });