import type Anthropic from '@anthropic-ai/sdk'; import { draftListing } from '../../src/intake/draftListing'; // getSettings reaches for the database, which a unit test has no business // doing. The model choice is the only thing needed from it here. jest.mock('../../src/adminSettings', () => ({ getSettings: jest.fn(async () => ({ draftingModel: 'claude-sonnet-5' })) })); const draft = { name: 'Blue stoneware vase', description: 'Hand-thrown, chipped base.', category: null, tags: [], suggestedPriceCents: 4500 }; /** * A stub, always. Every test in this file would otherwise cost money, and a * suite that bills the shop to run is one nobody will run. */ function stubClient(response: unknown): Anthropic { return { messages: { parse: jest.fn(async () => response) } } as unknown as Anthropic; } const input = { photos: [{ mediaType: 'image/jpeg', base64: 'AAAA' }], note: null, categories: [], tags: [] }; describe('draftListing', () => { it('returns the parsed draft with what it cost', async () => { const client = stubClient({ parsed_output: draft, usage: { input_tokens: 1000, output_tokens: 200 } }); const outcome = await draftListing(client, input); expect(outcome.draft).toEqual(draft); expect(outcome.model).toBe('claude-sonnet-5'); // 1000 * $2 + 200 * $10 per million, in micros. expect(outcome.costMicros).toBe(4000); }); // The SDK returns null here when the answer did not satisfy the schema — a // model replying in prose, most likely. Failing cleanly leaves the submission // queued for a retry; asserting on it would crash the worker mid-loop. it('fails rather than assuming a draft came back', async () => { const client = stubClient({ parsed_output: null, usage: { input_tokens: 10, output_tokens: 0 } }); await expect(draftListing(client, input)).rejects.toThrow(/expected shape/); }); // Usage has been absent on some responses. Treating that as free is wrong but // survivable; letting it throw would lose a draft that actually succeeded. it('survives a response with no usage figures', async () => { const client = stubClient({ parsed_output: draft }); const outcome = await draftListing(client, input); expect(outcome.inputTokens).toBe(0); expect(outcome.costMicros).toBe(0); }); it('sends the photographs and the chosen model to the API', async () => { const client = stubClient({ parsed_output: draft, usage: { input_tokens: 1, output_tokens: 1 } }); await draftListing(client, { ...input, categories: ['Ceramics'], note: 'a note' }); const params = (client.messages.parse as jest.Mock).mock.calls[0][0]; expect(params.model).toBe('claude-sonnet-5'); expect(params.system).toContain('Ceramics'); expect(params.messages[0].content.filter((b: { type: string }) => b.type === 'image')).toHaveLength(1); }); });