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, 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, event = 'work.status_changed', ) { return { event, data: { work_id: remoteWorkId, ...data, }, } } /** * 构造标准状态变更 payload */ export function buildStatusPayload( remoteWorkId: string, status: number, extra: Record = {}, ) { 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 }) }