docs: 交付文档、维护指引与端到端回归
补齐 README、开发计划、维护交接与 E2E 用例,执行最终验收。
This commit is contained in:
1 parent
5be5ca3376
commit
308332eb94
216 files changed
+37038
No files matched your search
@@ -0,0 +1,72 @@
|
||||
import { connectSms, routeToAgent } from '../api/channels'
|
||||
import { env } from '../env'
|
||||
import { FakeExternals } from '../fakes/externals'
|
||||
import { unique } from '../support/ids'
|
||||
import { step } from '../support/step'
|
||||
import { Chat, hmac, phoneNumber, postWebhook, samePhone, type ChannelConnector } from './chat'
|
||||
|
||||
// SMS webhooks are per account and signed over the public webhook URL, which the
|
||||
// backend rebuilds from BACKEND_URL — the same URL the test posts to.
|
||||
|
||||
interface TwilioSend { From: string; To: string; Body: string }
|
||||
interface PlivoSend { src: string; dst: string; text: string }
|
||||
|
||||
/** Twilio: X-Twilio-Signature = base64(HMAC-SHA1(auth token, url + sorted key/value pairs)). */
|
||||
export const twilio: ChannelConnector = ({ api, request, agentId }) =>
|
||||
step('Connect a Twilio number', async () => {
|
||||
const authToken = unique('twilio-token')
|
||||
const businessNumber = phoneNumber()
|
||||
const accountSid = unique('AC')
|
||||
const account = await connectSms(api, 'twilio', businessNumber, { account_sid: accountSid, auth_token: authToken })
|
||||
await routeToAgent(api, account.id, agentId)
|
||||
const externals = new FakeExternals(request)
|
||||
const path = `/webhooks/sms/twilio/${account.id}`
|
||||
|
||||
return {
|
||||
chatWith(customerName) {
|
||||
const customerNumber = phoneNumber()
|
||||
return new Chat('SMS (Twilio)', customerName, async (text) => {
|
||||
const form = { From: customerNumber, To: businessNumber, Body: text, MessageSid: unique('SM') }
|
||||
const signed = `${env.apiUrl}${path}` + Object.keys(form).sort().map((k) => k + form[k as keyof typeof form]).join('')
|
||||
await postWebhook(request, path, { form }, { 'x-twilio-signature': hmac('sha1', authToken, signed).toString('base64') })
|
||||
}, async () => (await externals.calls('twilio', 'Messages.json', { contains: accountSid }))
|
||||
// Sent through this account (its SID in the path) from this number.
|
||||
.filter((c) => c.status === 200 && c.path.includes(`/Accounts/${accountSid}/`))
|
||||
.map((c) => c.body as TwilioSend)
|
||||
.filter((b) => samePhone(b.To, customerNumber) && samePhone(b.From, businessNumber))
|
||||
.map((b) => b.Body))
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
/** Plivo: X-Plivo-Signature-V3 = base64(HMAC-SHA256(auth token, url + nonce)). */
|
||||
export const plivo: ChannelConnector = ({ api, request, agentId }) =>
|
||||
step('Connect a Plivo number', async () => {
|
||||
const authToken = unique('plivo-token')
|
||||
const businessNumber = phoneNumber()
|
||||
const authId = unique('MA')
|
||||
const account = await connectSms(api, 'plivo', businessNumber, { auth_id: authId, auth_token: authToken })
|
||||
await routeToAgent(api, account.id, agentId)
|
||||
const externals = new FakeExternals(request)
|
||||
const path = `/webhooks/sms/plivo/${account.id}`
|
||||
|
||||
return {
|
||||
chatWith(customerName) {
|
||||
// Plivo sends numbers without the leading +.
|
||||
const customerNumber = phoneNumber().slice(1)
|
||||
return new Chat('SMS (Plivo)', customerName, async (text) => {
|
||||
const nonce = unique('nonce')
|
||||
await postWebhook(request, path, {
|
||||
form: { From: customerNumber, To: businessNumber.slice(1), Text: text, MessageUUID: unique('uuid') },
|
||||
}, {
|
||||
'x-plivo-signature-v3': hmac('sha256', authToken, `${env.apiUrl}${path}${nonce}`).toString('base64'),
|
||||
'x-plivo-signature-v3-nonce': nonce,
|
||||
})
|
||||
}, async () => (await externals.calls('plivo', 'Message', { contains: authId }))
|
||||
.filter((c) => c.status === 200 && c.path.includes(`/Account/${authId}/`))
|
||||
.map((c) => c.body as PlivoSend)
|
||||
.filter((b) => samePhone(b.dst, customerNumber) && samePhone(b.src, businessNumber))
|
||||
.map((b) => b.text))
|
||||
},
|
||||
}
|
||||
})
|
||||
Reference in new issue
Block a user