import assert from 'node:assert/strict'; import test from 'node:test'; import { createIntentDraftService } from './intent-draft-service.mjs'; function createMemoryPool() { const rows = new Map(); return { async query(sql, params = []) { if (sql.includes('UPDATE h5_intent_drafts') && sql.includes("status = 'expired'")) { return [{ affectedRows: 0 }]; } if (sql.includes('UPDATE h5_intent_drafts') && sql.includes("status = 'cancelled'") && sql.includes("status = 'draft'")) { for (const row of rows.values()) { if (row.user_id === params[1] && row.status === 'draft') { row.status = 'cancelled'; row.updated_at = params[0]; } } return [{ affectedRows: 1 }]; } if (sql.includes('INSERT INTO h5_intent_drafts')) { const id = params[0]; rows.set(id, { id, user_id: params[1], layer: params[2], draft_type: params[3], action_level: params[4], title: params[5], payload_json: params[6], card_text: params[7], status: 'draft', source_channel: params[9], source_message_id: params[10], source_text: params[11], committed_ref_json: null, event_log_json: params[12], expires_at: params[13], created_at: params[14], updated_at: params[15], }); return [{ affectedRows: 1 }]; } if (sql.includes('SELECT * FROM h5_intent_drafts WHERE id = ? AND user_id = ?')) { const row = rows.get(params[0]); return [[row && row.user_id === params[1] ? row : undefined].filter(Boolean)]; } if (sql.includes('SELECT * FROM h5_intent_drafts') && sql.includes('user_id = ?') && sql.includes("status = 'draft'")) { const userId = params[0]; const draft = [...rows.values()] .filter((row) => row.user_id === userId && row.status === 'draft') .sort((a, b) => b.created_at - a.created_at)[0]; return [[draft ?? undefined].filter(Boolean)]; } if (sql.includes('SELECT * FROM h5_intent_drafts WHERE id = ?')) { const row = rows.get(params[0]); return [[row].filter(Boolean)]; } if (sql.includes("SET status = 'cancelled'") && sql.includes('WHERE id = ?')) { const row = rows.get(params[2]); if (row) { row.status = 'cancelled'; row.event_log_json = params[0]; row.updated_at = params[1]; } return [{ affectedRows: 1 }]; } if (sql.includes("SET status = 'committed'")) { const row = rows.get(params[3]); if (row) { row.status = 'committed'; row.committed_ref_json = params[0]; row.event_log_json = params[1]; row.updated_at = params[2]; } return [{ affectedRows: 1 }]; } throw new Error(`Unexpected SQL: ${sql}`); }, }; } test('createDraft replaces previous pending draft for same user', async () => { const pool = createMemoryPool(); const service = createIntentDraftService(pool, { clock: { now: () => 1000 }, ttlMs: 60000 }); const first = await service.createDraft({ userId: 'user-1', layer: 'L1', draftType: 'timed_reminder', title: '第一次', payload: { title: '第一次' }, cardText: 'card-1', }); const second = await service.createDraft({ userId: 'user-1', layer: 'L1', draftType: 'timed_reminder', title: '第二次', payload: { title: '第二次' }, cardText: 'card-2', }); const pending = await service.getPendingDraft('user-1'); assert.notEqual(first.id, second.id); assert.equal(pending.title, '第二次'); }); test('markDraftCommitted stores committed ref', async () => { const pool = createMemoryPool(); const service = createIntentDraftService(pool, { clock: { now: () => 2000 }, ttlMs: 60000 }); const draft = await service.createDraft({ userId: 'user-1', layer: 'L1', draftType: 'create_todo', title: '跟进合同', payload: { title: '跟进合同' }, cardText: 'card', }); const committed = await service.markDraftCommitted(draft.id, 'user-1', { kind: 'create_todo', item: { id: 'item-1' } }); assert.equal(committed.status, 'committed'); assert.deepEqual(committed.committedRef, { kind: 'create_todo', item: { id: 'item-1' } }); });