190 lines
9.0 KiB
TypeScript
190 lines
9.0 KiB
TypeScript
import type { ApiTestPlanInput, BusinessFlow, BusinessTestPlanInput, PageTestCaseInput, PageTestCaseResult } from '../types.js';
|
||
import { resolveProject } from '../core/workspace.js';
|
||
export function createPageTestCase(input: PageTestCaseInput): PageTestCaseResult {
|
||
const project = resolveProject(input.project);
|
||
const title = input.title || `${project.label} 页面级测试`;
|
||
const steps =
|
||
(input.steps?.length ?? 0) > 0
|
||
? input.steps!
|
||
: ['打开入口 URL', '完成登录或注入测试登录态', '执行用户关键路径操作', '保存关键步骤截图'];
|
||
const assertions =
|
||
(input.assertions?.length ?? 0) > 0
|
||
? input.assertions!
|
||
: ['页面无明显渲染错误', '控制台无 error', '关键接口返回成功', '核心 DOM 或业务状态符合预期'];
|
||
|
||
return {
|
||
account: input.account || '按当前环境使用数据库管理员账号或测试专用账号',
|
||
assertions,
|
||
cleanup: [
|
||
'关闭浏览器实例',
|
||
'清理本次启动的 Node/Vite 进程',
|
||
'保存截图和 result.json',
|
||
'先调用 kt_cleanup_history dryRun=true 预览,再执行 dryRun=false 或 pnpm run cleanup-history -- --execute,仅保留最近 3 轮历史产物并保留模板目录',
|
||
],
|
||
entryUrl: input.entryUrl || '待填写',
|
||
maxRounds: 3,
|
||
project,
|
||
protocol: [
|
||
'测试前先写用例。',
|
||
'失败后先记录复现证据,再排查和修复。',
|
||
'按同一用例复测。',
|
||
'第三轮仍失败时停止并等待用户建议。',
|
||
],
|
||
steps,
|
||
title,
|
||
};
|
||
}
|
||
|
||
export function createApiTestPlan(input: ApiTestPlanInput): Record<string, unknown> {
|
||
const project = resolveProject(input.project || 'api');
|
||
const method = (input.method || 'GET').toUpperCase();
|
||
const endpoint = input.endpoint || '/api/待填写';
|
||
const baseUrl = input.baseUrl || 'http://localhost:3000';
|
||
const headers = [];
|
||
|
||
if (input.auth === 'bearer') {
|
||
headers.push('-H "Authorization: Bearer <accessToken>"');
|
||
}
|
||
|
||
if (input.contentType !== false && ['POST', 'PUT', 'PATCH'].includes(method)) {
|
||
headers.push('-H "Content-Type: application/json"');
|
||
}
|
||
|
||
const body =
|
||
input.body && ['POST', 'PUT', 'PATCH', 'DELETE'].includes(method)
|
||
? ` -d '${JSON.stringify(input.body)}'`
|
||
: '';
|
||
const curl = `curl -i -X ${method} ${headers.join(' ')} "${baseUrl}${endpoint}"${body}`;
|
||
|
||
return {
|
||
assertions: [
|
||
'HTTP 状态码符合预期。',
|
||
'成功响应结构为 { code: 200, msg: string, data: any }。',
|
||
'成功响应不返回 err 字段。',
|
||
...(input.expectedFields || []).map((field) => `data 中包含 ${field}`),
|
||
],
|
||
curl,
|
||
endpoint,
|
||
method,
|
||
notes: [
|
||
'接口改动后不要只跑类型检查,必须真实调用对应接口一次。',
|
||
'如依赖登录态,先调用 login 获取 token,再带 Authorization 复测。',
|
||
],
|
||
project,
|
||
};
|
||
}
|
||
|
||
export function buildBusinessTestPlan(input: BusinessTestPlanInput = {}): Record<string, unknown> {
|
||
const environment = input.environment || 'local';
|
||
const baseUrl = input.baseUrl || (environment === 'remote' ? 'https://待填写' : 'http://127.0.0.1');
|
||
const flows: Record<BusinessFlow, Record<string, unknown>> = {
|
||
'admin-login': {
|
||
assertions: [
|
||
'登录接口返回 code=200 且 data 内有 accessToken/userInfo/menu/permissions。',
|
||
'Admin 页面进入后后端路由菜单正常渲染。',
|
||
'WordPress/NAS 不可用时不阻塞 Admin 登录。',
|
||
],
|
||
preflight: ['API 可达', 'Admin dev server 可达', '数据库管理员账号存在'],
|
||
projects: ['api', 'admin'],
|
||
steps: [
|
||
'调用 /auth/login 或使用 admin-login 脚本固化登录态。',
|
||
'进入 Admin 首页,确认菜单与按钮权限。',
|
||
'刷新页面后确认 auth 持久化仍有效。',
|
||
],
|
||
},
|
||
'blog-crud': {
|
||
assertions: [
|
||
'文章、分类、标签列表返回统一 code/msg/data。',
|
||
'新增/编辑/删除后后端刷新 WordPress 缓存。',
|
||
'文章编辑能绑定分类和标签,查询不额外打开新 tab。',
|
||
],
|
||
preflight: ['API 可达', 'WordPress 管理端认证可用或降级逻辑明确', 'Admin 博客菜单有权限'],
|
||
projects: ['api', 'admin', 'blog'],
|
||
steps: ['登录 Admin', '新增分类/标签', '新增文章并绑定分类标签', '编辑后查询列表', '清理 KT_TEST_ 数据'],
|
||
},
|
||
'fflogs-command': {
|
||
assertions: [
|
||
'命令注册表和在线命令表都能看到 FFLogs 查询命令。',
|
||
'中文任务名可查询,回显使用本地化任务名和字段名。',
|
||
'最近 10 次 logs 返回颜色、输出评分、治疗评分、dps、adps、rdps、ndps、hps。',
|
||
'FF14 查价和 FFLogs 映射优先来自字典表,不在插件代码硬编码。',
|
||
],
|
||
preflight: ['API 可达', 'FFLogs client id/secret 由环境变量注入', '字典表存在 FFLogs/FF14Market 映射项', 'QQBot 测试账号在线'],
|
||
projects: ['api', 'admin'],
|
||
steps: ['确认字典映射', '执行 /logs 角色 服务器 中文高难任务', '查询命令日志', '确认失败场景返回可读中文错误'],
|
||
},
|
||
'qqbot-account-scan': {
|
||
assertions: [
|
||
'扫码创建返回 qrcode/containerName/webuiPort。',
|
||
'取消/过期/失败会清理未绑定 NapCat 容器。',
|
||
'删除账号会二次确认并删除专属容器。',
|
||
],
|
||
preflight: ['NAS SSH 可达', 'NapCat WebUI 可达', 'API QQBot env 配置完整'],
|
||
projects: ['api', 'admin'],
|
||
steps: ['打开账号连接页', '点击扫码新增账号', '轮询二维码状态', '确认账号回填', '测试删除联动容器'],
|
||
},
|
||
'qqbot-login-sse': {
|
||
assertions: [
|
||
'更新登录接口快速返回 pending session,不被前端 HTTP 超时截断。',
|
||
'scan/events SSE 能看到重置登录态、生成二维码、等待扫码、成功或错误等步骤。',
|
||
'账号被其他终端踢下线后,更新登录不复用旧二维码或旧 isLogin 状态。',
|
||
'手动刷新二维码不超时,且不会无意义重启容器。',
|
||
],
|
||
preflight: ['API 可达', 'NapCat 容器可达', '账号处于离线或可重置状态', 'Admin QQBot 账号页面可访问'],
|
||
projects: ['api', 'admin'],
|
||
steps: ['触发更新登录', '订阅 scan/events', '观察二维码刷新', '扫码或等待过期', '核对账号状态和容器状态'],
|
||
},
|
||
'qqbot-auto-reply': {
|
||
assertions: [
|
||
'消息日志能看到 OneBot 收到的私聊/群聊/频道消息。',
|
||
'权限名单按 QQ号、群/频道、精确 QQ 过滤生效。',
|
||
'规则命中后发送日志记录成功或失败原因。',
|
||
],
|
||
preflight: ['账号在线', 'MQTT 或 local bus 正常', '规则启用', '名单配置符合测试目标'],
|
||
projects: ['api', 'admin'],
|
||
steps: ['创建 KT_TEST_ 自动回复规则', '发送私聊测试消息', '查询消息日志', '查询发送日志', '清理测试规则'],
|
||
},
|
||
'system-log-visualization': {
|
||
assertions: [
|
||
'API pino 日志能写入 Loki 或在 Loki 不可用时不阻塞业务。',
|
||
'/system/logs、/system/logs/summary、/system/logs/status 返回 Vben 兼容结构。',
|
||
'Admin 系统日志页面能查询、筛选、刷新,控制台无 error。',
|
||
'时间字段统一显示为 YYYY-MM-DD HH:mm:ss。',
|
||
],
|
||
preflight: ['API 可达', 'Loki /ready 返回 200 或降级状态明确', 'Admin 系统日志菜单可见', 'K8s Secret/Jenkins env 已注入 LOKI_*'],
|
||
projects: ['api', 'admin'],
|
||
steps: ['触发一条 API 日志', '查询 Loki query_range', '打开 Admin #/system/logs', '执行筛选刷新', '核对 summary/status'],
|
||
},
|
||
'web-playground-auth': {
|
||
assertions: [
|
||
'无 token 时不主动 refresh,不产生登录循环。',
|
||
'Admin 已登录时能带回授权态并重定向回原页面。',
|
||
'Playground 保存组件时截图上传 MinIO 并把 image 写入 API。',
|
||
],
|
||
preflight: ['Admin/API/Web/Playground 地址可达', '登录态存储为空或可控', 'MinIO 可达'],
|
||
projects: ['api', 'admin', 'web', 'playground'],
|
||
steps: ['清理本地 token', '从 Web/Playground 进入受保护页面', '跳 Admin 登录', '登录后回跳', '保存组件并查询接口'],
|
||
},
|
||
};
|
||
const selected: Array<[string, Record<string, unknown>]> =
|
||
!input.flow || input.flow === 'all'
|
||
? Object.entries(flows)
|
||
: [[input.flow, flows[input.flow] as Record<string, unknown>]];
|
||
|
||
return {
|
||
baseUrl,
|
||
environment,
|
||
maxRounds: 3,
|
||
protocol: [
|
||
'业务测试先写用例,再按环境 -> 账号 -> 数据 -> 接口 -> 页面预检。',
|
||
'失败必须记录复现步骤、证据和初步归因。',
|
||
'同一用例最多三轮,第三轮仍失败则停止等待建议。',
|
||
'每轮结束后执行 kt_cleanup_history 预览;如存在待删历史产物,确认范围后执行清理,只保留最近 3 轮并保留模板目录。',
|
||
],
|
||
selectedFlows: selected.map(([name, plan]) => ({
|
||
name,
|
||
...plan,
|
||
})),
|
||
};
|
||
}
|