fix: 固化QQBot插件测试审查
This commit is contained in:
parent
1c768c1e79
commit
ae2231d508
@ -24,7 +24,7 @@
|
|||||||
- 生成验证进程清理计划:按项目路径和端口给出 PowerShell 检查命令,不直接杀进程。
|
- 生成验证进程清理计划:按项目路径和端口给出 PowerShell 检查命令,不直接杀进程。
|
||||||
- 清理历史产物:统一治理 `.kt-workspace` 下的测试/验证产物,按目录最近修改时间只保留最近 3 轮,模板目录永久保留;CLI 默认 dry-run,真实清理必须显式传 `--execute`。
|
- 清理历史产物:统一治理 `.kt-workspace` 下的测试/验证产物,按目录最近修改时间只保留最近 3 轮,模板目录永久保留;CLI 默认 dry-run,真实清理必须显式传 `--execute`。
|
||||||
- 检查 env 策略和变更风险:区分后端真实 env 与前端客户端 `.env*`,提醒锁文件、核心表格组件、API 时间序列化 KtDateTime 列/DTO 装饰器入口、部署链路和 Vue TSX 插槽写法等高风险改动。
|
- 检查 env 策略和变更风险:区分后端真实 env 与前端客户端 `.env*`,提醒锁文件、核心表格组件、API 时间序列化 KtDateTime 列/DTO 装饰器入口、部署链路和 Vue TSX 插槽写法等高风险改动。
|
||||||
- 全局 CodeReview 只读扫描:汇总全部 KT 子仓库的 Git 状态、敏感文件跟踪、冲突标记、运行时调试输出、疑似凭据字面量、NapCat `latest` 镜像漂移风险、QQBot 重型服务单测导入风险、QQBot OneBot/QQ 登录态边界回归、NapCat 密码登录验证码 wait 阶段、日志窗口锚点、验证码状态早于日志 URL 的短轮询、验证码 pending 被状态轮询误清理、API Pod SSH 日志读取超时和清理前 pending 回归、普通处理中状态误吃旧验证码日志回归、根目录生成产物、`TASKS.md` 最近记录字段结构和当前变更风险;默认只对变更文件做内容扫描,并放过短中文显示标签和明显测试占位 token,避免历史误报污染上下文;任何文件改动后都要跑一遍。
|
- 全局 CodeReview 只读扫描:汇总全部 KT 子仓库的 Git 状态、敏感文件跟踪、冲突标记、运行时调试输出、疑似凭据字面量、NapCat `latest` 镜像漂移风险、QQBot 重型服务单测导入风险、QQBot 插件 registry/controller smoke 未 mock 具体插件实现风险、QQBot OneBot/QQ 登录态边界回归、NapCat 密码登录验证码 wait 阶段、日志窗口锚点、验证码状态早于日志 URL 的短轮询、验证码 pending 被状态轮询误清理、API Pod SSH 日志读取超时和清理前 pending 回归、普通处理中状态误吃旧验证码日志回归、根目录生成产物、`TASKS.md` 最近记录字段结构和当前变更风险;默认只对变更文件做内容扫描,并放过短中文显示标签和明显测试占位 token,避免历史误报污染上下文;任何文件改动后都要跑一遍。
|
||||||
- 生成或写入 `TASKS.md` 最近记录:默认 `dryRun=true`,确认后再落盘。
|
- 生成或写入 `TASKS.md` 最近记录:默认 `dryRun=true`,确认后再落盘。
|
||||||
- 生成提交前检查清单:校验 KT commit message 约定。
|
- 生成提交前检查清单:校验 KT commit message 约定。
|
||||||
|
|
||||||
@ -109,7 +109,7 @@ pnpm run admin-login -- --url http://127.0.0.1:5999/#/auth/login
|
|||||||
| 脚本 | 用途 |
|
| 脚本 | 用途 |
|
||||||
| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| `pnpm run admin-login` | 使用可见 Edge 打开 Admin 登录页,填写账号密码,拖动滑块,保存登录态和截图。默认账号来自初始化数据 `admin/123456`,生产或个人账号用 `KT_ADMIN_USERNAME` / `KT_ADMIN_PASSWORD` 或 CLI 参数覆盖。 |
|
| `pnpm run admin-login` | 使用可见 Edge 打开 Admin 登录页,填写账号密码,拖动滑块,保存登录态和截图。默认账号来自初始化数据 `admin/123456`,生产或个人账号用 `KT_ADMIN_USERNAME` / `KT_ADMIN_PASSWORD` 或 CLI 参数覆盖。 |
|
||||||
| `pnpm run global-review` | 对 KT 全部子仓库做只读全局 CodeReview 扫描,默认仅对变更文件做内容深扫,输出 JSON 复审报告,并校验 `TASKS.md` 最近记录只保留范围、关键词、验证字段、NapCat `latest` 漂移、QQBot 重型服务单测导入风险、OneBot/QQ 登录态边界、NapCat 密码登录验证码 wait 阶段、日志窗口锚点、验证码状态早于日志 URL 的短轮询、验证码 pending 被状态轮询误清理、API Pod SSH 日志读取超时及清理前 pending 回归,以及普通处理中状态误吃旧验证码日志回归;明显测试占位 token 不报凭据风险,确认误报时优先升级 `src/tools/review.ts`,不要把误报沉积到上下文。 |
|
| `pnpm run global-review` | 对 KT 全部子仓库做只读全局 CodeReview 扫描,默认仅对变更文件做内容深扫,输出 JSON 复审报告,并校验 `TASKS.md` 最近记录只保留范围、关键词、验证字段、NapCat `latest` 漂移、QQBot 重型服务单测导入风险、QQBot 插件 registry/controller smoke 未 mock 具体插件实现风险、OneBot/QQ 登录态边界、NapCat 密码登录验证码 wait 阶段、日志窗口锚点、验证码状态早于日志 URL 的短轮询、验证码 pending 被状态轮询误清理、API Pod SSH 日志读取超时及清理前 pending 回归,以及普通处理中状态误吃旧验证码日志回归;明显测试占位 token 不报凭据风险,确认误报时优先升级 `src/tools/review.ts`,不要把误报沉积到上下文。 |
|
||||||
| `pnpm run deploy-observation` | 默认 dry-run 输出只读 NAS 观测命令;传 `--execute` 时执行 Jenkins/K8s/health/smoke 观测并写入 `.kt-workspace/test-artifacts/deploy-observation`。 |
|
| `pnpm run deploy-observation` | 默认 dry-run 输出只读 NAS 观测命令;传 `--execute` 时执行 Jenkins/K8s/health/smoke 观测并写入 `.kt-workspace/test-artifacts/deploy-observation`。 |
|
||||||
| `pnpm run obsidian-context` | 输出 Obsidian 索引上下文;可传 `--module Admin`、`--query QQBot`、`--max-documents 10`。 |
|
| `pnpm run obsidian-context` | 输出 Obsidian 索引上下文;可传 `--module Admin`、`--query QQBot`、`--max-documents 10`。 |
|
||||||
| `pnpm run obsidian-validate` | 校验 KT Obsidian vault 结构和链接;默认 warning 不让脚本失败,需要严格模式时传 `--fail-on-warnings`。 |
|
| `pnpm run obsidian-validate` | 校验 KT Obsidian vault 结构和链接;默认 warning 不让脚本失败,需要严格模式时传 `--fail-on-warnings`。 |
|
||||||
|
|||||||
@ -29,6 +29,7 @@ import {
|
|||||||
findNapcatImageGovernanceFindings,
|
findNapcatImageGovernanceFindings,
|
||||||
findQqbotNapcatCaptchaFlowFindings,
|
findQqbotNapcatCaptchaFlowFindings,
|
||||||
findQqbotCommandServiceTestImportFindings,
|
findQqbotCommandServiceTestImportFindings,
|
||||||
|
findQqbotPluginSmokeImportFindings,
|
||||||
findQqbotStatusBoundaryFindings,
|
findQqbotStatusBoundaryFindings,
|
||||||
findTaskRecordGovernanceFindings,
|
findTaskRecordGovernanceFindings,
|
||||||
isBenignCredentialReviewValue,
|
isBenignCredentialReviewValue,
|
||||||
@ -115,6 +116,42 @@ export async function runSelfTest(): Promise<void> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const qqbotPluginSmokeImportFindings = findQqbotPluginSmokeImportFindings(
|
||||||
|
[
|
||||||
|
"import { QqbotBangDreamPluginService } from '../../../../src/modules/qqbot/plugins/bangDream/qqbot-bangdream.plugin';",
|
||||||
|
],
|
||||||
|
{
|
||||||
|
file: "test/modules/qqbot/plugins/plugin-controller-http-smoke.spec.ts",
|
||||||
|
project: "Node/kt-template-online-api",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (
|
||||||
|
!qqbotPluginSmokeImportFindings.some(
|
||||||
|
(item) => item.category === "qqbot-plugin-smoke-heavy-import",
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
throw new Error("QQBot plugin smoke import governance self-check failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
const qqbotPluginSmokeMockedFindings = findQqbotPluginSmokeImportFindings(
|
||||||
|
[
|
||||||
|
"jest.mock(",
|
||||||
|
" '@/modules/qqbot/plugins/bangDream/qqbot-bangdream.plugin',",
|
||||||
|
" () => ({}),",
|
||||||
|
");",
|
||||||
|
"import { QqbotBangDreamPluginService } from '../../../../src/modules/qqbot/plugins/bangDream/qqbot-bangdream.plugin';",
|
||||||
|
],
|
||||||
|
{
|
||||||
|
file: "test/modules/qqbot/plugins/plugin-controller-http-smoke.spec.ts",
|
||||||
|
project: "Node/kt-template-online-api",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (qqbotPluginSmokeMockedFindings.length > 0) {
|
||||||
|
throw new Error(
|
||||||
|
"QQBot plugin smoke import mocked self-check produced false positive",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const qqbotStatusBoundaryFindings = [
|
const qqbotStatusBoundaryFindings = [
|
||||||
...findQqbotStatusBoundaryFindings(
|
...findQqbotStatusBoundaryFindings(
|
||||||
[
|
[
|
||||||
|
|||||||
@ -230,6 +230,61 @@ export function findQqbotCommandServiceTestImportFindings(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function findQqbotPluginSmokeImportFindings(
|
||||||
|
lines: string[],
|
||||||
|
context: {
|
||||||
|
file: string;
|
||||||
|
project: string;
|
||||||
|
},
|
||||||
|
): ReviewFinding[] {
|
||||||
|
const normalized = context.file.replaceAll("\\", "/");
|
||||||
|
if (
|
||||||
|
context.project !== "Node/kt-template-online-api" ||
|
||||||
|
!normalized.startsWith("test/modules/qqbot/") ||
|
||||||
|
!normalized.endsWith(".ts") ||
|
||||||
|
!/(plugin-(registry|controller).*\.spec\.ts|qqbot-core-module-contract\.spec\.ts)$/.test(
|
||||||
|
normalized,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const source = lines.join("\n");
|
||||||
|
const pluginImportPattern =
|
||||||
|
/from\s+['"]([^'"]*modules\/qqbot\/plugins\/(bangDream|ff14Market|fflogs|repeater)\/[^'"]*(?:\.plugin|client\.service|renderer\.facade|application\.service))['"]/g;
|
||||||
|
const findings: ReviewFinding[] = [];
|
||||||
|
const seenPlugins = new Set<string>();
|
||||||
|
let match: RegExpExecArray | null;
|
||||||
|
|
||||||
|
while ((match = pluginImportPattern.exec(source))) {
|
||||||
|
const pluginName = match[2];
|
||||||
|
if (seenPlugins.has(pluginName)) continue;
|
||||||
|
seenPlugins.add(pluginName);
|
||||||
|
|
||||||
|
const escapedPluginName = pluginName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||||
|
const hasMock = new RegExp(
|
||||||
|
`jest\\.mock\\s*\\(\\s*['"][^'"]*/modules/qqbot/plugins/${escapedPluginName}/`,
|
||||||
|
"m",
|
||||||
|
).test(source);
|
||||||
|
if (hasMock) continue;
|
||||||
|
|
||||||
|
const lineIndex = lines.findIndex((line) => line.includes(match?.[1] || ""));
|
||||||
|
findings.push({
|
||||||
|
category: "qqbot-plugin-smoke-heavy-import",
|
||||||
|
file: context.file,
|
||||||
|
line: lineIndex >= 0 ? lineIndex + 1 : undefined,
|
||||||
|
level: "P2" as const,
|
||||||
|
message:
|
||||||
|
"QQBot 插件 registry/controller 契约或 HTTP smoke 导入真实插件实现但未 mock,容易拖入渲染、定时器或外部请求依赖。",
|
||||||
|
project: context.project,
|
||||||
|
suggestion:
|
||||||
|
"用 jest.mock mock 具体插件 service token,只测试 registry/controller 的别名解析和路由包装边界。",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return findings;
|
||||||
|
}
|
||||||
|
|
||||||
export function findQqbotStatusBoundaryFindings(
|
export function findQqbotStatusBoundaryFindings(
|
||||||
lines: string[],
|
lines: string[],
|
||||||
context: {
|
context: {
|
||||||
@ -777,6 +832,10 @@ function collectProjectContentFindings(
|
|||||||
file,
|
file,
|
||||||
project: project.relativePath,
|
project: project.relativePath,
|
||||||
}),
|
}),
|
||||||
|
...findQqbotPluginSmokeImportFindings(lines, {
|
||||||
|
file,
|
||||||
|
project: project.relativePath,
|
||||||
|
}),
|
||||||
...findQqbotStatusBoundaryFindings(lines, {
|
...findQqbotStatusBoundaryFindings(lines, {
|
||||||
file,
|
file,
|
||||||
project: project.relativePath,
|
project: project.relativePath,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user