docs: 交付文档、维护指引与端到端回归
补齐 README、开发计划、维护交接与 E2E 用例,执行最终验收。
This commit is contained in:
1 parent
18732801b5
commit
6cd904d460
216 files changed
+37038
No files matched your search
@@ -0,0 +1,63 @@
|
||||
import { test, expect, connectCrm, disconnectCrm, findPerson, FakeExternals, runWorkerOnce, syncPersonToCrm, unique, AI_REPLY } from '@e2e'
|
||||
|
||||
// One HubSpot connection per org, so this runs in the serial `org` project and
|
||||
// disconnects afterwards.
|
||||
|
||||
test.describe('HubSpot', () => {
|
||||
test.use({ agentSettings: { leadCapture: true } })
|
||||
|
||||
test.afterEach(async ({ adminApi }) => { await disconnectCrm(adminApi, 'hubspot') })
|
||||
|
||||
test('a captured lead is pushed to HubSpot with what they told us @crm', async ({ agent, widget, llm, adminApi, request }) => {
|
||||
const code = unique('hs-code')
|
||||
await connectCrm(adminApi, 'hubspot', code)
|
||||
|
||||
const { email, name } = widget.customer
|
||||
await llm.script(agent, (s) => s
|
||||
.when(/@/).captureLead('Thanks! Someone from sales will reach out.', { email, name, company: 'Acme Corp' })
|
||||
.otherwise().reply('Happy to set that up — what is your email?'))
|
||||
await widget.embed()
|
||||
await widget.open()
|
||||
await widget.send('I would like a demo')
|
||||
await expect(widget.lastAgentMessage()).toHaveText('Happy to set that up — what is your email?', AI_REPLY)
|
||||
await widget.send(`Sure, I am ${name}, ${email}`)
|
||||
await expect(widget.lastAgentMessage()).toHaveText('Thanks! Someone from sales will reach out.', AI_REPLY)
|
||||
|
||||
const person = await findPerson(adminApi, email)
|
||||
const status = await syncPersonToCrm(adminApi, person.id)
|
||||
expect(status.synced.map((s) => s.provider)).toContain('hubspot')
|
||||
|
||||
// What HubSpot received: the contact keyed by email, as a lead, with their company,
|
||||
// sent with the token this org's install was granted.
|
||||
const externals = new FakeExternals(request)
|
||||
const [upsert] = await externals.calls('hubspot', 'upsert', { contains: email })
|
||||
const [grant] = await externals.calls('hubspot', 'token', { contains: code })
|
||||
expect(upsert.headers.authorization).toBe(`Bearer ${(grant.response as { access_token: string }).access_token}`)
|
||||
expect(upsert.body).toMatchObject({
|
||||
inputs: [{ id: email, idProperty: 'email', properties: { email, company: 'Acme Corp', lifecyclestage: 'lead' } }],
|
||||
})
|
||||
})
|
||||
|
||||
test('with HubSpot as the agent\'s CRM, a captured lead is pushed on its own @crm @workers', async ({ agent, widget, llm, adminApi, request }) => {
|
||||
await connectCrm(adminApi, 'hubspot', unique('hs-code'))
|
||||
await adminApi.put(`/agent/${agent.id}/lead-capture`, { data: { enabled: true, require_consent: false, crm_sync_target: 'hubspot' } })
|
||||
|
||||
const { email, name } = widget.customer
|
||||
await llm.script(agent, (s) => s
|
||||
.when(/@/).captureLead('Thanks! Someone from sales will reach out.', { email, name, company: 'Globex' })
|
||||
.otherwise().reply('Happy to set that up — what is your email?'))
|
||||
await widget.embed()
|
||||
await widget.open()
|
||||
await widget.send('I would like a demo')
|
||||
await expect(widget.lastAgentMessage()).toHaveText('Happy to set that up — what is your email?', AI_REPLY)
|
||||
await widget.send(`Sure, I am ${name}, ${email}`)
|
||||
await expect(widget.lastAgentMessage()).toHaveText('Thanks! Someone from sales will reach out.', AI_REPLY)
|
||||
|
||||
// Capture queues a sync job; the CRM worker pushes it on its next pass.
|
||||
const externals = new FakeExternals(request)
|
||||
expect(await externals.calls('hubspot', 'upsert', { contains: email })).toHaveLength(0)
|
||||
await runWorkerOnce('crm-sync')
|
||||
const [upsert] = await externals.calls('hubspot', 'upsert', { contains: email })
|
||||
expect(upsert.body).toMatchObject({ inputs: [{ id: email, properties: { company: 'Globex', lifecyclestage: 'lead' } }] })
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,57 @@
|
||||
import { test, expect, connectCrm, disconnectCrm, findPerson, FakeExternals, syncPersonToCrm, unique, AI_REPLY } from '@e2e'
|
||||
|
||||
// One Pipedrive connection per org, so this runs in the serial `org` project and
|
||||
// disconnects afterwards.
|
||||
|
||||
interface PipedriveGrant { access_token: string; api_domain: string }
|
||||
|
||||
test.describe('Pipedrive', () => {
|
||||
test.use({ agentSettings: { leadCapture: true } })
|
||||
|
||||
test.afterEach(async ({ adminApi }) => { await disconnectCrm(adminApi, 'pipedrive') })
|
||||
|
||||
test('a captured lead becomes a Pipedrive person, organisation and lead with a note @crm', async ({ agent, widget, llm, adminApi, request }) => {
|
||||
const code = unique('pd-code')
|
||||
await connectCrm(adminApi, 'pipedrive', code)
|
||||
|
||||
const { email, name } = widget.customer
|
||||
const company = unique('Initech')
|
||||
await llm.script(agent, (s) => s
|
||||
.when(/@/).captureLead('Thanks! Someone from sales will reach out.', { email, name, company })
|
||||
.otherwise().reply('Happy to set that up — what is your email?'))
|
||||
await widget.embed()
|
||||
await widget.open()
|
||||
await widget.send('I would like a demo')
|
||||
await expect(widget.lastAgentMessage()).toHaveText('Happy to set that up — what is your email?', AI_REPLY)
|
||||
await widget.send(`Sure, I am ${name}, ${email}`)
|
||||
await expect(widget.lastAgentMessage()).toHaveText('Thanks! Someone from sales will reach out.', AI_REPLY)
|
||||
|
||||
const person = await findPerson(adminApi, email)
|
||||
const status = await syncPersonToCrm(adminApi, person.id)
|
||||
expect(status.synced.map((s) => s.provider)).toContain('pipedrive')
|
||||
|
||||
// The install traded the code with the app's own credentials (Basic auth), and
|
||||
// every API call went to the company's api_domain with the token it was granted.
|
||||
const externals = new FakeExternals(request)
|
||||
const [grant] = await externals.calls('pipedrive-oauth', 'token', { contains: code })
|
||||
const basic = `Basic ${Buffer.from('e2e-pipedrive-client-id:e2e-pipedrive-client-secret').toString('base64')}`
|
||||
expect(grant.headers.authorization).toBe(basic)
|
||||
const { access_token: token } = grant.response as PipedriveGrant
|
||||
|
||||
const [organization] = await externals.calls('pipedrive', 'organizations', { contains: company })
|
||||
expect(organization.body).toEqual({ name: company })
|
||||
const [created] = await externals.calls('pipedrive', 'persons', { contains: email })
|
||||
expect(created.method).toBe('POST')
|
||||
expect(created.headers.authorization).toBe(`Bearer ${token}`)
|
||||
expect(created.body).toMatchObject({
|
||||
name, emails: [{ value: email, primary: true }], org_id: (organization.response as { data: { id: number } }).data.id,
|
||||
})
|
||||
|
||||
const personId = (created.response as { data: { id: number } }).data.id
|
||||
const [lead] = (await externals.calls('pipedrive', 'leads', { contains: `"person_id":${personId}` })).filter((c) => c.method === 'POST')
|
||||
expect(lead.body).toMatchObject({ title: `${name} — ReplyPier lead`, person_id: personId })
|
||||
const leadId = (lead.response as { data: { id: string } }).data.id
|
||||
const [note] = await externals.calls('pipedrive', 'notes', { contains: leadId })
|
||||
expect(note.body).toMatchObject({ lead_id: leadId, content: expect.stringContaining('Lead captured by ReplyPier') })
|
||||
})
|
||||
})
|
||||
Reference in new issue
Block a user