library-picturebook-activity/frontend/e2e/utils/webhook-helper.ts
En 922f650365 feat: 添加乐读派(leai)集成模块及E2E测试基础设施
后端:
- 新增 leai 模块:认证、Webhook、数据同步、定时对账
- 新增 LeaiConfig/RestTemplateConfig/SchedulingConfig 配置
- 新增 FlywayRepairConfig 处理迁移修复
- 新增 V5__leai_integration.sql 迁移脚本
- 扩展所有实体类添加 tenantId 等字段
- 更新 SecurityConfig 放行 leai 公开接口
- 添加 application-test.yml 测试环境配置

前端:
- 添加乐读派认证 API (public.ts)
- 优化 Generating.vue 生成页
- 添加 Playwright E2E 测试配置及依赖
- 添加测试 fixtures、utils、mock-h5.html
- 添加 leai 模块完整 E2E 测试套件

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-07 21:52:32 +08:00

174 lines
4.0 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 })
}