d58dc2a251
Introduce Draft → Confirm → Commit flow for WeChat schedule intents behind feature flags, plus h5_tasks dual-write/read aggregation and rollout scripts so reminders and automations get explicit user confirmation before persisting. Co-authored-by: Cursor <cursoragent@cursor.com>
136 lines
4.3 KiB
JavaScript
136 lines
4.3 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import { handleWechatIntentTransaction } from './intent-transaction.mjs';
|
|
|
|
function createDraftStore() {
|
|
let pending = null;
|
|
return {
|
|
async getPendingDraft(userId) {
|
|
return pending?.userId === userId ? pending : null;
|
|
},
|
|
async createDraft(payload) {
|
|
pending = {
|
|
id: 'draft-1',
|
|
userId: payload.userId,
|
|
layer: payload.layer,
|
|
draftType: payload.draftType,
|
|
actionLevel: payload.actionLevel,
|
|
title: payload.title,
|
|
payload: payload.payload,
|
|
cardText: payload.cardText,
|
|
status: 'draft',
|
|
};
|
|
return pending;
|
|
},
|
|
async cancelDraft(id, userId) {
|
|
if (pending?.id === id && pending.userId === userId) pending = null;
|
|
return { id, status: 'cancelled' };
|
|
},
|
|
async markDraftCommitted(id, userId, committedRef) {
|
|
if (pending?.id === id && pending.userId === userId) {
|
|
pending = { ...pending, status: 'committed', committedRef };
|
|
}
|
|
return pending;
|
|
},
|
|
};
|
|
}
|
|
|
|
function createScheduleService() {
|
|
return {
|
|
async createItem(payload) {
|
|
return { id: 'item-1', ...payload };
|
|
},
|
|
async createReminder(payload) {
|
|
return { id: 'reminder-1', ...payload };
|
|
},
|
|
buildTodoDigestText() {
|
|
return '今天有 1 条待办。';
|
|
},
|
|
};
|
|
}
|
|
|
|
const enabledEnv = { H5_INTENT_TRANSACTION_ENABLED: '1', H5_DEFAULT_TIMEZONE: 'Asia/Shanghai' };
|
|
|
|
test('intent transaction returns null when feature disabled', async () => {
|
|
const reply = await handleWechatIntentTransaction({
|
|
intent: { agentText: '下午2点半提醒我开会', msgId: 'm1' },
|
|
user: { userId: 'user-1' },
|
|
intentDraftService: createDraftStore(),
|
|
scheduleService: createScheduleService(),
|
|
env: { H5_INTENT_TRANSACTION_ENABLED: '0' },
|
|
});
|
|
assert.equal(reply, null);
|
|
});
|
|
|
|
test('intent transaction creates action card for simple timed reminder', async () => {
|
|
const drafts = createDraftStore();
|
|
const reply = await handleWechatIntentTransaction({
|
|
intent: { agentText: '下午2点半提醒我开项目计划例会', msgId: 'm2' },
|
|
user: { userId: 'user-1' },
|
|
intentDraftService: drafts,
|
|
scheduleService: createScheduleService(),
|
|
env: enabledEnv,
|
|
});
|
|
assert.match(reply, /我准备执行/);
|
|
assert.match(reply, /确认/);
|
|
const pending = await drafts.getPendingDraft('user-1');
|
|
assert.equal(pending.draftType, 'timed_reminder');
|
|
});
|
|
|
|
test('intent transaction commits draft after user confirms', async () => {
|
|
const drafts = createDraftStore();
|
|
const scheduleService = createScheduleService();
|
|
const calls = [];
|
|
scheduleService.createItem = async (payload) => {
|
|
calls.push(['createItem', payload.title]);
|
|
return { id: 'item-1', ...payload };
|
|
};
|
|
scheduleService.createReminder = async (payload) => {
|
|
calls.push(['createReminder', payload.itemId]);
|
|
return { id: 'reminder-1', ...payload };
|
|
};
|
|
|
|
await handleWechatIntentTransaction({
|
|
intent: { agentText: '下午2点半提醒我开项目计划例会', msgId: 'm3' },
|
|
user: { userId: 'user-1' },
|
|
intentDraftService: drafts,
|
|
scheduleService,
|
|
env: enabledEnv,
|
|
});
|
|
const reply = await handleWechatIntentTransaction({
|
|
intent: { agentText: '确认', msgId: 'm4' },
|
|
user: { userId: 'user-1' },
|
|
intentDraftService: drafts,
|
|
scheduleService,
|
|
env: enabledEnv,
|
|
});
|
|
assert.match(reply, /已设置提醒/);
|
|
assert.equal(calls.length, 2);
|
|
});
|
|
|
|
test('intent transaction asks for slot fill without creating draft', async () => {
|
|
const drafts = createDraftStore();
|
|
const reply = await handleWechatIntentTransaction({
|
|
intent: { agentText: '设置提醒', msgId: 'm6' },
|
|
user: { userId: 'user-2' },
|
|
intentDraftService: drafts,
|
|
scheduleService: createScheduleService(),
|
|
env: enabledEnv,
|
|
});
|
|
assert.match(reply, /补充/);
|
|
assert.equal(await drafts.getPendingDraft('user-2'), null);
|
|
});
|
|
|
|
test('intent transaction answers query guard without creating draft', async () => {
|
|
const drafts = createDraftStore();
|
|
const reply = await handleWechatIntentTransaction({
|
|
intent: { agentText: '看看我的待办', msgId: 'm5' },
|
|
user: { userId: 'user-1' },
|
|
intentDraftService: drafts,
|
|
scheduleService: createScheduleService(),
|
|
env: enabledEnv,
|
|
});
|
|
assert.match(reply, /待办/);
|
|
assert.equal(await drafts.getPendingDraft('user-1'), null);
|
|
});
|