fix(bridge): 商机漏建兜底与匿名线索

This commit is contained in:
wangliang
2026-10-23 14:22:14 +08:00
parent 22d96edee6
commit 9ce3120648
6 changed files with 238 additions and 18 deletions
+42 -3
View File
@@ -487,15 +487,54 @@ test('search_email_history: scopes to verified email, ignores args.email', async
assert.equal(out.count, 0);
});
test('create_opportunity: requires verified contact', async () => {
test('create_opportunity: unverified lead is created from contact name (policy A)', async () => {
const twenty = fakeTwenty();
const tools = createTools(baseCfg, { rpc: fakeRpc(() => []), twenty });
const tool = tools.find((t) => t.name === 'create_opportunity');
const out = await tool.handler(
{ company: 'Anon Co', contactName: 'Anna Klein', productNeed: '500 rings' },
{ verified: false, conversationUuid: `conv-anon-${Date.now()}` }
);
assert.equal(out.created, true);
assert.equal(twenty.calls.filter((c) => c[0] === 'createOpportunity').length, 1);
const note = twenty.calls.find((c) => c[0] === 'createNote');
assert.match(note[1], /Contact: Anna Klein/);
assert.doesNotMatch(note[1], /<>/);
});
test('create_opportunity: unverified lead keeps the email provided in chat', async () => {
const twenty = fakeTwenty();
const tools = createTools(baseCfg, { rpc: fakeRpc(() => []), twenty });
const tool = tools.find((t) => t.name === 'create_opportunity');
const out = await tool.handler(
{ company: 'Anon Co', contactName: 'Anna Klein', email: 'anna@anon.test', productNeed: '500 rings' },
{ verified: false, conversationUuid: `conv-anon-mail-${Date.now()}` }
);
assert.equal(out.created, true);
const person = twenty.calls.find((c) => c[0] === 'createPerson');
assert.equal(person[1].email, 'anna@anon.test');
});
test('create_opportunity: still requires company + contact + need', async () => {
const tools = createTools(baseCfg, { rpc: fakeRpc(() => []), twenty: fakeTwenty() });
const tool = tools.find((t) => t.name === 'create_opportunity');
await assert.rejects(
() => tool.handler({ company: 'A', contactName: 'B', productNeed: 'C' }, { verified: false }),
(err) => err instanceof ToolError && err.status === 403
() => tool.handler({ company: 'A', productNeed: 'C' }, { verified: false, conversationUuid: 'conv-missing' }),
(err) => err instanceof ToolError && err.status === 400
);
});
test('collect_info: stores the email slot', async () => {
const tools = createTools(baseCfg, { rpc: fakeRpc(() => []), twenty: fakeTwenty() });
const tool = tools.find((t) => t.name === 'collect_info');
const out = await tool.handler(
{ company: 'Acme BV', contactName: 'John Doe', email: 'john@acme.test', productNeed: 'gold hoops' },
{ conversationUuid: 'conv-collect-email', verified: false }
);
assert.equal(out.slots.email, 'john@acme.test');
assert.deepEqual(out.missing, []);
});
test('create_opportunity: dedupes by existing opportunity', async () => {
const twenty = fakeTwenty({ personId: 'person-1', existingOpps: [{ id: 'opp-old', name: 'Old', stage: 'NEW' }] });
const tools = createTools(baseCfg, { rpc: fakeRpc(() => []), twenty });