feat(edit): 编辑上报细节优化

This commit is contained in:
2026-10-06 18:30:05 +08:00
parent e20cd7547f
commit 79b310efe7
92 changed files with 2764 additions and 368 deletions
+54
View File
@@ -0,0 +1,54 @@
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);
});
});