Files
Neo-ZQYY/apps/admin-web/e2e/helpers.ts
Neo 6f8f12314f feat: 累积功能变更 — 聊天集成、租户管理、小程序更新、ETL 增强、迁移脚本
包含多个会话的累积代码变更:
- backend: AI 聊天服务、触发器调度、认证增强、WebSocket、调度器最小间隔
- admin-web: ETL 状态页、任务管理、调度配置、登录优化
- miniprogram: 看板页面、聊天集成、UI 组件、导航更新
- etl: DWS 新任务(finance_area_daily/board_cache)、连接器增强
- tenant-admin: 项目初始化
- db: 19 个迁移脚本(etl_feiqiu 11 + zqyy_app 8)
- packages/shared: 枚举和工具函数更新
- tools: 数据库工具、报表生成、健康检查
- docs: PRD/架构/部署/合约文档更新

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-06 00:03:48 +08:00

231 lines
6.5 KiB
TypeScript
Raw 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.
/**
* E2E 测试公共辅助:注入 JWT 令牌 + 通用 API mock。
*
* 认证方式:向 localStorage 写入伪造的 access_token / refresh_token
* 与 authStore.hydrate() 逻辑一致,页面加载后自动识别为已登录状态。
*/
import { type Page } from '@playwright/test';
/* ------------------------------------------------------------------ */
/* 伪造 JWTpayload 可被 authStore.parseJwtPayload 正确解析) */
/* ------------------------------------------------------------------ */
function makeFakeJwt(payload: Record<string, unknown>): string {
const header = btoa(JSON.stringify({ alg: 'HS256', typ: 'JWT' }));
const body = btoa(JSON.stringify(payload));
const sig = 'fake_signature';
return `${header}.${body}.${sig}`;
}
const FAKE_ACCESS_TOKEN = makeFakeJwt({
user_id: 1,
username: 'admin',
display_name: '测试管理员',
site_id: 1,
roles: ['admin'],
exp: Math.floor(Date.now() / 1000) + 3600,
});
const FAKE_REFRESH_TOKEN = 'fake_refresh_token';
/* ------------------------------------------------------------------ */
/* 注入登录状态 */
/* ------------------------------------------------------------------ */
/**
* 在页面导航前注入 localStorage 令牌,模拟已登录状态。
* 必须在 page.goto() 之前调用。
*/
export async function injectAuth(page: Page): Promise<void> {
// 先访问 baseURL 以获得同源 localStorage 访问权限
await page.goto('/login', { waitUntil: 'commit' });
await page.evaluate(
([accessToken, refreshToken]) => {
localStorage.setItem('access_token', accessToken);
localStorage.setItem('refresh_token', refreshToken);
},
[FAKE_ACCESS_TOKEN, FAKE_REFRESH_TOKEN] as const,
);
}
/* ------------------------------------------------------------------ */
/* 通用 API mock — 拦截所有 /api/** 请求返回空数据 */
/* ------------------------------------------------------------------ */
/** 为所有 /api/ 请求注册兜底 mock避免真实网络调用 */
export async function mockAllApis(page: Page): Promise<void> {
// 运维面板
await page.route('**/api/ops/system-info', (route) =>
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
code: 0,
data: {
cpu_percent: 25.0,
memory_percent: 60.0,
disk_percent: 45.0,
uptime_seconds: 86400,
platform: 'Windows',
python_version: '3.10.0',
},
}),
}),
);
await page.route('**/api/ops/services', (route) =>
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
code: 0,
data: [
{ name: 'backend', env: 'prod', status: 'running', pid: 1234, port: 8000 },
],
}),
}),
);
await page.route('**/api/ops/git-info', (route) =>
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
code: 0,
data: [
{ env: 'prod', branch: 'main', commit: 'abc1234', dirty: false },
],
}),
}),
);
// 数据库健康
await page.route('**/api/db-health**', (route) =>
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
code: 0,
data: [
{ db_name: 'etl_feiqiu', status: 'healthy', latency_ms: 5, details: null },
{ db_name: 'zqyy_app', status: 'healthy', latency_ms: 3, details: null },
],
}),
}),
);
// AI 调度摘要trigger jobs
await page.route('**/api/admin/ai/trigger-jobs**', (route) =>
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
code: 0,
data: { items: [], total: 0 },
}),
}),
);
// AI Dashboard 相关
await page.route('**/api/admin/ai/**', (route) =>
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ code: 0, data: [] }),
}),
);
// 统一触发器
await page.route('**/api/triggers/unified**', (route) =>
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
code: 0,
data: [
{
id: 1, name: '测试触发器', source: 'biz',
trigger_condition: 'cron', status: 'running',
last_run_at: '2026-01-01T00:00:00', next_run_at: '2026-01-02T00:00:00',
last_error: null,
},
],
}),
}),
);
// 业务触发器
await page.route('**/api/trigger-jobs**', (route) =>
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
code: 0,
data: [
{
id: 1, job_name: 'test_job', description: '测试任务',
trigger_condition: 'cron', trigger_config: { cron_expression: '0 */2 * * *' },
status: 'enabled', last_run_at: null, next_run_at: null, last_error: null,
},
],
}),
}),
);
// ETL 调度任务
await page.route('**/api/schedules**', (route) =>
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
code: 0,
data: [
{
id: 1, name: 'ETL 日常同步', task_codes: ['ODS_LOAD'],
enabled: true, last_status: 'success',
last_run_at: '2026-01-01T00:00:00', next_run_at: '2026-01-02T00:00:00',
run_count: 100, created_at: '2025-01-01T00:00:00',
},
],
}),
}),
);
// 执行队列Footer 状态栏)
await page.route('**/api/execution/queue**', (route) =>
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ code: 0, data: [] }),
}),
);
// ETL 任务配置
await page.route('**/api/task-config**', (route) =>
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ code: 0, data: [] }),
}),
);
// ETL 状态
await page.route('**/api/etl-status**', (route) =>
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ code: 0, data: [] }),
}),
);
// 兜底:其他未匹配的 /api/ 请求返回空成功
await page.route('**/api/**', (route) =>
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ code: 0, data: [] }),
}),
);
}