import { buildSystemPrompt, buildUserContent } from '../../src/intake/draftPrompt'; describe('buildSystemPrompt', () => { const prompt = buildSystemPrompt(['Ceramics', 'Textiles'], ['vintage', 'blue']); // The whole reason this function is tested rather than inlined. On a // one-of-a-kind item an invented age or maker is a false claim on a // storefront, and nothing downstream can tell an invented detail from an // observed one. it('forbids inventing what is neither visible nor in the note', () => { expect(prompt).toMatch(/do not (state|invent)/i); expect(prompt).toMatch(/material/i); expect(prompt).toMatch(/age/i); expect(prompt).toMatch(/maker/i); expect(prompt).toMatch(/provenance/i); }); it('offers the categories it may choose from', () => { expect(prompt).toContain('Ceramics'); expect(prompt).toContain('Textiles'); }); it('offers the tags it may choose from', () => { expect(prompt).toContain('vintage'); expect(prompt).toContain('blue'); }); // Otherwise a model with no matching option picks the closest wrong one. it('permits declining a category', () => { expect(prompt).toMatch(/null/i); }); it('survives a shop with no categories or tags yet', () => { expect(() => buildSystemPrompt([], [])).not.toThrow(); }); }); describe('buildUserContent', () => { const photo = { mediaType: 'image/jpeg', base64: 'AAAA' }; type Block = { type: string; source?: { media_type: string; data: string } }; it('sends every photo as an image block', () => { const images = (buildUserContent([photo, photo], 'a note') as Block[]).filter( (block) => block.type === 'image' ); expect(images).toHaveLength(2); expect(images[0]?.source?.media_type).toBe('image/jpeg'); expect(images[0]?.source?.data).toBe('AAAA'); }); it('includes the note verbatim', () => { const text = JSON.stringify(buildUserContent([photo], 'Chipped base, bought 1998')); expect(text).toContain('Chipped base, bought 1998'); }); // A submission with no note is ordinary — the field is optional — and the // model has to be told so rather than left to read an empty string as a fact // about the item. it('says so when there is no note', () => { const text = JSON.stringify(buildUserContent([photo], null)); expect(text).toMatch(/no note/i); }); // A whitespace-only note is the same thing as no note. Sending it would let // the model treat an accidental space as something the sender meant to say. it('treats a blank note as no note', () => { const text = JSON.stringify(buildUserContent([photo], ' ')); expect(text).toMatch(/no note/i); }); });