docs: 交付文档、维护指引与端到端回归

补齐 README、开发计划、维护交接与 E2E 用例,执行最终验收。
This commit is contained in:
WangShen007 committed 2026-10-21 21:40:00 +08:00
1 parent 18732801b5
commit 6cd904d460
216 files changed
+37038

No files matched your search

@@ -0,0 +1,94 @@
import {
test, expect, connectWhatsAppNumber, expectUnchanged, listChannelAccounts, routeToAgent,
ApiClient, FakeExternals, IntegrationsPage, WhatsAppChat, AI_REPLY,
} from '@e2e'
// When Meta stops accepting a number's access token (an expired temporary
// token, a revoked system user), every reply fails until someone reconnects
// the number. The account is flagged so the Integrations card can say so,
// rather than replies failing with nothing on screen.
//
// Other tests share the org and its WhatsApp card, so the card is checked for
// this test's number by name, never for the absence of any warning at all.
const TOKEN_REFUSED = { error: { message: 'Error validating access token: Session has expired', type: 'OAuthException', code: 190, fbtrace_id: 'e2e' } }
// Each read lists every account in the shared org; once a second is plenty
const FLAG_POLL = { ...AI_REPLY, intervals: [1_000] }
const WINDOW_CLOSED = { error: { message: 'Re-engagement message', type: 'OAuthException', code: 131047, fbtrace_id: 'e2e' } }
async function needsReconnect(api: ApiClient, accountId: string): Promise<boolean> {
const account = (await listChannelAccounts(api)).find((a) => a.id === accountId)
return account?.needs_reconnect ?? false
}
test.describe('a WhatsApp number whose token Meta refuses', () => {
test('is flagged, the Integrations card names it, and reconnecting clears it @channels', async ({ agent, llm, adminApi, request, page }) => {
await llm.script(agent, (s) => s.otherwise().reply('Happy to help!'))
const number = await connectWhatsAppNumber(adminApi)
await routeToAgent(adminApi, number.account.id, agent.id)
const externals = new FakeExternals(request)
const chat = new WhatsAppChat(request, externals, number, 'Tess')
const restore = await externals.fail({ provider: 'graph', action: 'messages', contains: chat.waId, status: 401, body: TOKEN_REFUSED })
await chat.send('Hello?')
await expect.poll(() => needsReconnect(adminApi, number.account.id), FLAG_POLL).toBe(true)
expect(await chat.replies()).toEqual([])
const integrations = new IntegrationsPage(page)
const warning = integrations.expiredAccessFor(number.account.display_name ?? '')
await integrations.goto()
await expect(integrations.card('WhatsApp')).toContainText(warning)
// Other numbers are connected too; Manage still leads to the fix, not the first number
await integrations.manage('WhatsApp')
await expect(integrations.reconnectPrompt()).toBeVisible()
// The customer swaps in a working token: same number, connected again
await restore()
const reconnected = await connectWhatsAppNumber(adminApi, number.phoneNumberId)
expect(reconnected.account.id).toBe(number.account.id)
expect(reconnected.account.needs_reconnect).toBe(false)
await integrations.goto()
await expect(integrations.card('WhatsApp')).not.toContainText(warning)
// Replies now go out with the new token
const afterReconnect = new WhatsAppChat(request, externals, reconnected, 'Tess')
await afterReconnect.send('Are you there now?')
await expect.poll(() => afterReconnect.replies(), AI_REPLY).toEqual(['Happy to help!'])
})
test('is healthy again as soon as a reply goes through @channels', async ({ agent, llm, adminApi, request }) => {
await llm.script(agent, (s) => s.otherwise().reply('Happy to help!'))
const number = await connectWhatsAppNumber(adminApi)
await routeToAgent(adminApi, number.account.id, agent.id)
const externals = new FakeExternals(request)
const chat = new WhatsAppChat(request, externals, number, 'Tess')
// Meta refuses the token once, then accepts it (it was refreshed on Meta's side)
await externals.fail({ provider: 'graph', action: 'messages', contains: chat.waId, status: 401, body: TOKEN_REFUSED, times: 1 })
await chat.send('Hello?')
await expect.poll(() => needsReconnect(adminApi, number.account.id), FLAG_POLL).toBe(true)
await chat.send('Hello again?')
await expect.poll(() => chat.replies(), AI_REPLY).toEqual(['Happy to help!'])
expect(await needsReconnect(adminApi, number.account.id)).toBe(false)
})
test('is not flagged when Meta refuses one message for its own reason @channels', async ({ agent, llm, adminApi, request }) => {
await llm.script(agent, (s) => s.otherwise().reply('Happy to help!'))
const number = await connectWhatsAppNumber(adminApi)
await routeToAgent(adminApi, number.account.id, agent.id)
const externals = new FakeExternals(request)
const chat = new WhatsAppChat(request, externals, number, 'Tess')
// A closed 24-hour window refuses the message, not the token
const restore = await externals.fail({ provider: 'graph', action: 'messages', contains: chat.waId, status: 400, body: WINDOW_CLOSED })
await chat.send('Hello?')
const refused = async () => (await externals.calls('graph', 'messages', { contains: chat.waId }))
.filter((c) => c.status === 400).length
await expect.poll(refused, AI_REPLY).toBeGreaterThan(0)
await expectUnchanged('the number\'s reconnect flag', () => needsReconnect(adminApi, number.account.id))
expect(await needsReconnect(adminApi, number.account.id)).toBe(false)
await restore()
})
})