55 lines
4.0 KiB
TypeScript
55 lines
4.0 KiB
TypeScript
import { describe, expect, it, vi } from 'vitest';
|
|
import { PRODUCT_AI_HINT_MAX_LENGTH, requestProductAiTaskSchema } from '@atlas/contracts';
|
|
import type { ProductEditorSnapshot } from '@atlas/contracts';
|
|
import { ProductAiService } from '../src/product-editor/product-ai.service.js';
|
|
|
|
describe('SKU 文案用户要求', () => {
|
|
it('将用户要求与 SKU 输入原子固化,并纳入幂等请求,不改写商品字段', async () => {
|
|
const hint = requestProductAiTaskSchema.parse({ hint: ' 改为红色款,25cm;标题不要写包邮。 ' }).hint;
|
|
let stored: Record<string, unknown> | undefined;
|
|
let outbox: unknown;
|
|
const statements: string[] = [];
|
|
const query = vi.fn(async (sql: string, values: unknown[] = []) => {
|
|
statements.push(sql);
|
|
if (sql.includes('SELECT product_id')) return { rows: [{ product_id: 'product' }] };
|
|
if (sql.includes('SELECT product.revision')) return { rows: [{ product_revision: 3, profile_revision: 4, source_snapshot_revision: 5 }] };
|
|
if (sql.includes('INSERT INTO atlas.product_ai_tasks')) {
|
|
stored = JSON.parse(values[4] as string);
|
|
return { rows: [{ id: 'task' }] };
|
|
}
|
|
if (sql.includes('INSERT INTO atlas.outbox_events')) outbox = JSON.parse(values[1] as string);
|
|
return { rows: [] };
|
|
});
|
|
const transaction = { query };
|
|
const workspaceTransaction = vi.fn(async (_workspace: string, work: (tx: typeof transaction) => Promise<unknown>) => work(transaction));
|
|
const run = vi.fn(async (_input: unknown, work: (tx: typeof transaction) => Promise<unknown>) => work(transaction));
|
|
const snapshot = {
|
|
product: { id: 'product', revision: 3, common: { model: 'model' } },
|
|
profile: { id: 'profile', revision: 4, platform_id: 'platform', target_store: { id: 'store' }, category: { id: null }, title: null, tags: [], description: null },
|
|
source_material: { source_snapshot_revision: 5, title: '采集蓝色 20cm', description: null },
|
|
variants: [{ id: 'sku', display_name: '蓝色款', specification_values: [], listing: { status: 'not_listed', offer_id: 'model-blue' }, tags: [] }],
|
|
} as unknown as ProductEditorSnapshot;
|
|
const service = new ProductAiService(
|
|
{ workspaceTransaction } as unknown as ConstructorParameters<typeof ProductAiService>[0],
|
|
{ run } as unknown as ConstructorParameters<typeof ProductAiService>[1],
|
|
{ read: vi.fn(async () => snapshot) } as unknown as ConstructorParameters<typeof ProductAiService>[2],
|
|
);
|
|
const result = await service.create('workspace', 'user', 'profile', 'platform', 'sku', 'key', hint);
|
|
expect(result).toMatchObject({ status: 202, body: { data: { ai_task_id: 'task', status: 'queued' } } });
|
|
expect(stored).toMatchObject({ hint, variant_id: 'sku', revision: { product: 3, profile: 4, source_snapshot: 5 } });
|
|
expect(stored?.source_material).toEqual(snapshot.source_material);
|
|
expect(run.mock.calls[0][0]).toMatchObject({ request: { hint, platform_id: 'platform' } });
|
|
expect(outbox).toEqual({ task_id: 'task' });
|
|
expect(statements.some((sql) => /UPDATE atlas\.(products|product_variants|collection_items)/.test(sql))).toBe(false);
|
|
expect(workspaceTransaction.mock.calls.every(([workspace]) => workspace === 'workspace')).toBe(true);
|
|
});
|
|
|
|
it('兼容不传要求,接受 1000 字符并拒绝超限和未知字段', () => {
|
|
expect(requestProductAiTaskSchema.parse({})).toEqual({});
|
|
expect(requestProductAiTaskSchema.parse({ hint: ' ' })).toEqual({ hint: '' });
|
|
expect(requestProductAiTaskSchema.safeParse({ hint: '要'.repeat(PRODUCT_AI_HINT_MAX_LENGTH) }).success).toBe(true);
|
|
expect(requestProductAiTaskSchema.safeParse({ hint: '要'.repeat(PRODUCT_AI_HINT_MAX_LENGTH + 1) }).success).toBe(false);
|
|
expect(requestProductAiTaskSchema.safeParse({ hint: '红色款', source_override: {} }).success).toBe(false);
|
|
});
|
|
});
|