| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182 |
- import assert from 'node:assert/strict';
- import fs from 'node:fs';
- import os from 'node:os';
- import path from 'node:path';
- import test from 'node:test';
- import { analyzeProductImages } from './analyze.mjs';
- const projectRoot = path.resolve(import.meta.dirname, '..', '..', '..', '..', '..');
- function tempWorkspace(prefix) {
- return fs.mkdtempSync(path.join(os.tmpdir(), `fmode-image-set-${prefix}-`));
- }
- test('analysis rejects missing current-request image without calling provider', async () => {
- let called = false;
- const result = await analyzeProductImages({ allowClaudeAttachments: false }, { provider: { analyze: async () => { called = true; } } });
- assert.equal(result.status, 'needs_input');
- assert.equal(called, false);
- });
- test('analysis validates structured Gemini result', async () => {
- const workspace = tempWorkspace('analysis');
- const fixture = path.join(workspace, 'analysis.png');
- fs.writeFileSync(fixture, Buffer.from([137, 80, 78, 71]));
- try {
- const provider = {
- analyze: async () => ({
- model: 'fake-gemini', usage: {}, finishReason: 'stop',
- analysis: {
- visible_facts: [{ field: 'color', value: 'red', source_image: 1, confidence: 0.9 }],
- visual_inferences: [], unknowns: ['size'],
- product_identity: { product_name: '红瓶精华', category: 'beauty', shape: 'bottle', packaging: 'box', logo: '', visible_text: [], materials: [], colors: ['red'], distinctive_features: [] },
- brand_style: { palette: ['#ff0000'], lighting: 'soft', mood: 'clean', composition: 'centered' },
- quality_risks: [], recommended_reference_roles: [{ image: 1, role: 'identity' }]
- }
- })
- };
- const result = await analyzeProductImages({ imagePath: fixture, workspaceRoot: workspace, cwd: workspace, allowClaudeAttachments: false }, { provider, workspaceRoot: workspace });
- assert.equal(result.status, 'ok');
- assert.equal(result.data.analysis.visible_facts[0].value, 'red');
- assert.equal(result.data.analysis.product_identity.product_name, '红瓶精华');
- } finally {
- fs.rmSync(workspace, { recursive: true, force: true });
- }
- });
- test('analysis requires a complete role mapping for multiple images and passes it to Gemini', async () => {
- const workspace = tempWorkspace('roles');
- const first = path.join(workspace, 'role-1.png');
- const second = path.join(workspace, 'role-2.png');
- fs.writeFileSync(first, Buffer.from([137, 80, 78, 71]));
- fs.writeFileSync(second, Buffer.from([137, 80, 78, 72]));
- let received;
- const provider = { analyze: async (_images, input) => {
- received = input.imageRoles;
- return { model: 'fake', usage: {}, finishReason: 'stop', analysis: {} };
- } };
- try {
- const incomplete = await analyzeProductImages({ images: [first, second], imageRoles: { 1: 'identity' }, workspaceRoot: workspace, cwd: workspace, allowClaudeAttachments: false }, { provider, workspaceRoot: workspace });
- assert.equal(incomplete.status, 'needs_input');
- const complete = await analyzeProductImages({ images: [first, second], imageRoles: { 1: 'identity', 2: 'detail' }, workspaceRoot: workspace, cwd: workspace, allowClaudeAttachments: false }, { provider, workspaceRoot: workspace });
- assert.equal(complete.status, 'ok');
- assert.deepEqual(received.map(item => item.role), ['identity', 'detail']);
- } finally {
- fs.rmSync(workspace, { recursive: true, force: true });
- }
- });
- test('analysis rejects a local image outside the declared workspace', async () => {
- const parent = tempWorkspace('outside-boundary');
- const workspace = path.join(parent, 'workspace');
- const outside = path.join(parent, 'outside.png');
- fs.mkdirSync(workspace, { recursive: true });
- fs.writeFileSync(outside, Buffer.from([137, 80, 78, 71]));
- try {
- const result = await analyzeProductImages({ imagePath: outside, workspaceRoot: workspace, cwd: workspace, allowClaudeAttachments: false }, { workspaceRoot: workspace, provider: { analyze: async () => ({ analysis: {} }) } });
- assert.equal(result.status, 'needs_input');
- assert.match(result.assistantMessage, /当前项目内/);
- } finally {
- fs.rmSync(parent, { recursive: true, force: true });
- }
- });
|