174 lines
4.0 KiB
TypeScript
174 lines
4.0 KiB
TypeScript
|
|
import crypto from 'crypto'
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Webhook 请求构造器
|
|||
|
|
* 用于 Playwright API 测试中模拟乐读派 Webhook 回调
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
/** 测试配置(与 application-dev.yml 对齐) */
|
|||
|
|
export const LEAI_TEST_CONFIG = {
|
|||
|
|
orgId: 'gdlib',
|
|||
|
|
appSecret: 'leai_mnoi9q1a_mtcawrn8y',
|
|||
|
|
apiUrl: 'http://192.168.1.72:8080',
|
|||
|
|
h5Url: 'http://192.168.1.72:3001',
|
|||
|
|
testPhone: '13800001111',
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/** 后端 API 地址(前端 vite proxy 转发) */
|
|||
|
|
export const API_BASE = process.env.API_BASE_URL || 'http://localhost:8580/api'
|
|||
|
|
|
|||
|
|
/** HMAC-SHA256 签名 */
|
|||
|
|
function hmacSha256(data: string, secret: string): string {
|
|||
|
|
return crypto.createHmac('sha256', secret).update(data).digest('hex')
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/** 生成随机事件 ID */
|
|||
|
|
export function randomEventId(): string {
|
|||
|
|
return `evt_test_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/** 生成随机作品 ID */
|
|||
|
|
export function randomWorkId(): string {
|
|||
|
|
return `wk_test_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 构造完整的 Webhook 请求参数
|
|||
|
|
* 包括 headers + body
|
|||
|
|
*/
|
|||
|
|
export function buildWebhookRequest(
|
|||
|
|
payload: Record<string, unknown>,
|
|||
|
|
options: {
|
|||
|
|
eventId?: string
|
|||
|
|
eventType?: string
|
|||
|
|
timestamp?: string
|
|||
|
|
appSecret?: string
|
|||
|
|
validSignature?: boolean
|
|||
|
|
} = {},
|
|||
|
|
) {
|
|||
|
|
const {
|
|||
|
|
eventId = randomEventId(),
|
|||
|
|
eventType = 'work.status_changed',
|
|||
|
|
timestamp = Date.now().toString(),
|
|||
|
|
appSecret = LEAI_TEST_CONFIG.appSecret,
|
|||
|
|
validSignature = true,
|
|||
|
|
} = options
|
|||
|
|
|
|||
|
|
const body = JSON.stringify(payload)
|
|||
|
|
|
|||
|
|
let signature: string
|
|||
|
|
if (validSignature) {
|
|||
|
|
const signData = `${eventId}.${timestamp}.${body}`
|
|||
|
|
signature = `HMAC-SHA256=${hmacSha256(signData, appSecret)}`
|
|||
|
|
} else {
|
|||
|
|
signature = 'HMAC-SHA256=invalid_signature'
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
url: `${API_BASE}/webhook/leai`,
|
|||
|
|
headers: {
|
|||
|
|
'X-Webhook-Id': eventId,
|
|||
|
|
'X-Webhook-Event': eventType,
|
|||
|
|
'X-Webhook-Timestamp': timestamp,
|
|||
|
|
'X-Webhook-Signature': signature,
|
|||
|
|
'Content-Type': 'application/json',
|
|||
|
|
},
|
|||
|
|
body,
|
|||
|
|
eventId,
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 构造 Webhook payload(标准格式)
|
|||
|
|
*/
|
|||
|
|
export function buildWebhookPayload(
|
|||
|
|
remoteWorkId: string,
|
|||
|
|
data: Record<string, unknown>,
|
|||
|
|
event = 'work.status_changed',
|
|||
|
|
) {
|
|||
|
|
return {
|
|||
|
|
event,
|
|||
|
|
data: {
|
|||
|
|
work_id: remoteWorkId,
|
|||
|
|
...data,
|
|||
|
|
},
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 构造标准状态变更 payload
|
|||
|
|
*/
|
|||
|
|
export function buildStatusPayload(
|
|||
|
|
remoteWorkId: string,
|
|||
|
|
status: number,
|
|||
|
|
extra: Record<string, unknown> = {},
|
|||
|
|
) {
|
|||
|
|
return buildWebhookPayload(remoteWorkId, { status, ...extra })
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 构造进度更新 payload
|
|||
|
|
*/
|
|||
|
|
export function buildProgressPayload(
|
|||
|
|
remoteWorkId: string,
|
|||
|
|
progress: number,
|
|||
|
|
progressMessage: string,
|
|||
|
|
) {
|
|||
|
|
return buildWebhookPayload(
|
|||
|
|
remoteWorkId,
|
|||
|
|
{ status: 2, progress, progressMessage },
|
|||
|
|
'work.progress',
|
|||
|
|
)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 构造包含页面列表的完成 payload
|
|||
|
|
*/
|
|||
|
|
export function buildCompletedPayload(
|
|||
|
|
remoteWorkId: string,
|
|||
|
|
pageCount = 6,
|
|||
|
|
) {
|
|||
|
|
const pageList = Array.from({ length: pageCount }, (_, i) => ({
|
|||
|
|
imageUrl: `https://cdn.example.com/pages/${remoteWorkId}/page_${i + 1}.png`,
|
|||
|
|
text: `第${i + 1}页的文字内容`,
|
|||
|
|
}))
|
|||
|
|
|
|||
|
|
return buildStatusPayload(remoteWorkId, 3, {
|
|||
|
|
title: `测试绘本_${remoteWorkId.slice(-6)}`,
|
|||
|
|
phone: LEAI_TEST_CONFIG.testPhone,
|
|||
|
|
style: 'cartoon',
|
|||
|
|
pageList,
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 构造配音完成 payload(含 audioUrl)
|
|||
|
|
*/
|
|||
|
|
export function buildDubbedPayload(
|
|||
|
|
remoteWorkId: string,
|
|||
|
|
pageCount = 6,
|
|||
|
|
) {
|
|||
|
|
const pageList = Array.from({ length: pageCount }, (_, i) => ({
|
|||
|
|
imageUrl: `https://cdn.example.com/pages/${remoteWorkId}/page_${i + 1}.png`,
|
|||
|
|
text: `第${i + 1}页的文字内容`,
|
|||
|
|
audioUrl: `https://cdn.example.com/audio/${remoteWorkId}/page_${i + 1}.mp3`,
|
|||
|
|
}))
|
|||
|
|
|
|||
|
|
return buildStatusPayload(remoteWorkId, 5, {
|
|||
|
|
title: `测试绘本_${remoteWorkId.slice(-6)}`,
|
|||
|
|
author: '测试作者',
|
|||
|
|
phone: LEAI_TEST_CONFIG.testPhone,
|
|||
|
|
pageList,
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 构造失败 payload
|
|||
|
|
*/
|
|||
|
|
export function buildFailedPayload(
|
|||
|
|
remoteWorkId: string,
|
|||
|
|
failReason = 'AI 处理超时',
|
|||
|
|
) {
|
|||
|
|
return buildStatusPayload(remoteWorkId, -1, { failReason })
|
|||
|
|
}
|