Files
aiops/TDD_PLAN.md
T
attacker 00a98e210d feat(k8s): A1 K8s 双路由体系统一 — code 路由为主, ID 路由标记 deprecated
后端:
- 新增 4 条 all-NS code 路由: services/ingresses/cronjobs/nodes
- ListNodes 改用 getK8sClientByCode (兼容 ID+code)
- ID 域资源路由标记 // Deprecated

前端:
- clusterApi/nodeApi/workloadApi 资源方法迁移到 code 路由
- KubernetesClusterDetail/TopologyView/KubernetesNodes 组件从 cluster.id 迁移到 cluster.code
- 修复潜在 bug: ClusterOverviewPage 曾将 code 传给 ID-based getNodes

验证: 16/16 API 测试通过 (7 新 code 路由 + 3 旧 ID 兼容 + 6 回归)
go build=✅ tsc -b=✅ npm build=✅
2026-06-27 04:50:58 +08:00

496 lines
19 KiB
Markdown
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.
# AIOps 验证计划 — 全模块回归测试
> 基于 ROADMAP P0-P3 模块定义,每个模块开发前先写验证用例,实现后必须跑通。
> 最后更新: 2026-06-25
## 通用验证流程
```bash
# 0. 环境准备
cd /srv/aiops
# 1. 重建后端
docker-compose up -d --build backend
sleep 10 && docker ps --format '{{.Names}} {{.Status}}' | grep aiops-backend
# 2. 获取 JWT Token(用 Python 避免 shell 引号问题)
python3 -c "
import urllib.request, json
login_data = json.dumps({'username': 'admin', 'password': '123456'}).encode()
req = urllib.request.Request('http://127.0.0.1:8080/api/v1/auth/login', data=login_data, method='POST')
req.add_header('Content-Type', 'application/json')
resp = urllib.request.urlopen(req)
print(json.loads(resp.read())['data']['token'])
" > /tmp/aiops_token
TOKEN=$(cat /tmp/aiops_token)
# 3. 验证后端健康
curl -s http://127.0.0.1:8080/health | python3 -m json.tool
# 4. 前端构建
cd frontend && npm run build 2>&1 | tail -3
```
---
## P0-Q1: 前端类型安全治理
### 验证用例
```bash
# U1: TypeScript 编译零错误
cd /srv/aiops/frontend && npx tsc --noEmit 2>&1 | tail -5
# 期望: 退出码 0, 无输出
# U2: ESLint any 类型数量
npx eslint src/ --quiet --format json 2>/dev/null | python3 -c "
import json, sys
data = json.load(sys.stdin)
any_count = sum(1 for r in data for m in r.get('messages', []) if m.get('ruleId') == '@typescript-eslint/no-explicit-any')
print(f'any 类型: {any_count} 处')
# 期望: < 50
"
# U3: ESLint unused-vars 数量
npx eslint src/ --quiet --format json 2>/dev/null | python3 -c "
import json, sys
data = json.load(sys.stdin)
unused = sum(1 for r in data for m in r.get('messages', []) if m.get('ruleId') == '@typescript-eslint/no-unused-vars')
print(f'未使用变量: {unused} 处')
# 期望: < 20
"
# U4: console.* 数量
grep -rn 'console\.\(log\|debug\|info\)' src/ --include='*.ts' --include='*.tsx' | grep -v node_modules | wc -l
# 期望: < 10
# U5: api.ts 中无泄露 token 的 console.log
grep -n 'console.*token\|console.*Token' src/services/api.ts
# 期望: 无输出
# U6: types/ 目录覆盖率
wc -l src/types/*.ts
# 期望: > 500 行(当前 375 行)
# U7: Vite 构建成功
npm run build 2>&1 | tail -3
# 期望: built in Ns
```
### 通过标准
- [x] U1: `tsc --noEmit` 零错误
- [x] U2: `any` 类型 < 50 处 — **40 处**
- [x] **U3: 未使用变量 < 20 处** — 6 处
- [x] **U4: `console.*` < 10 处** — 1 处
- [x] **U8: `src/api/` 整个目录 `any` 清零** — 含 monitor/ deploy/ 子目录全部对接 types/ 接口
- [x] U1: `tsc -b` 零错误(含类型化后的回归)
- [x] U7: Vite 构建成功 (✓ built in 17.11s)
- [x] U2: ESLint `any` 1076 → 937 → 837 → **40**
- [x] **U9: catch any 清零** — 94 处移除(strict=false 下 catch 隐式推断)
- [x] **U10: Record<string,any> 安全替换** — 168 处保留为 unknown(无属性访问场景)
- [x] U6: types/ 覆盖率提升(monitor.ts +200 行接口定义,含 6 个运行时记录类型)
- [x] **U11: 组件级 any 清理** — AlertEventsTab/AlertRulesTab/AlertChannelDialog/AuditOperationsTab/CronJobManager/AgentMonitor/DashboardRecent 共 43 处 any→具体接口
- [x] 回归: 9/9 核心 API 端点通过,Docker 部署 healthy
---
## P0-Q2: 后端 kubernetes.go 模块化拆分
### 验证用例
```bash
# U1: Go 编译
cd /srv/aiops/backend && go build ./... 2>&1
# 期望: 零错误
# U2: 拆分后文件行数
for f in internal/api/handlers/k8s_*.go; do
echo "$(wc -l < "$f") $f"
done | sort -rn
# 期望: 每个文件 < 800 行
# U3: 路由总数不变(拆分前后对比)
grep -cE '^\s+(api\.|r\.|router\.|group\.|authorized\.)' internal/api/routes/routes.go
# 期望: 路由数量与拆分前一致
# U4: API 回归测试 — 抽样核心端点
TOKEN=$(cat /tmp/aiops_token)
# 集群列表
curl -s http://127.0.0.1:8080/api/v1/kubernetes-clusters \
-H "Authorization: Bearer $TOKEN" | python3 -c "import json,sys; d=json.load(sys.stdin); print(f'clusters: {len(d[\"data\"])}')"
# 期望: 返回集群数组
# Deployment 列表
curl -s "http://127.0.0.1:8080/api/v1/kubernetes/prod-cluster/namespace/devops/deployments" \
-H "Authorization: Bearer $TOKEN" | python3 -c "import json,sys; d=json.load(sys.stdin); print(f'deployments: {len(d[\"data\"])}')"
# 期望: 返回 Deployment 数组
# Pod 列表
curl -s "http://127.0.0.1:8080/api/v1/kubernetes/prod-cluster/namespace/devops/pods" \
-H "Authorization: Bearer $TOKEN" | python3 -c "import json,sys; d=json.load(sys.stdin); print(f'pods: {len(d[\"data\"])}')"
# 期望: 返回 Pod 数组
# YAML 获取
curl -s "http://127.0.0.1:8080/api/v1/kubernetes/prod-cluster/namespace/devops/deployments/redis/yaml" \
-H "Authorization: Bearer $TOKEN" | head -3
# 期望: apiVersion: apps/v1
# 事件聚合
curl -s "http://127.0.0.1:8080/api/v1/kubernetes/prod-cluster/events/summary" \
-H "Authorization: Bearer $TOKEN" | python3 -c "import json,sys; d=json.load(sys.stdin)['data']; print(f'events total: {d[\"total\"]}')"
# 期望: total 为数字
# 多集群对比
curl -s "http://127.0.0.1:8080/api/v1/kubernetes/compare" \
-H "Authorization: Bearer $TOKEN" | python3 -c "import json,sys; d=json.load(sys.stdin)['data']; print(f'clusters compared: {len(d[\"clusters\"])}')"
# 期望: clusters 数组
# Namespace 详情
curl -s "http://127.0.0.1:8080/api/v1/kubernetes/prod-cluster/namespaces/detail" \
-H "Authorization: Bearer $TOKEN" | python3 -c "import json,sys; d=json.load(sys.stdin); print(f'namespaces: {len(d[\"data\"])}')"
# 期望: 返回命名空间数组
# U5: 容器重新构建 + 启动
docker-compose up -d --build backend
sleep 10
docker ps --format '{{.Names}} {{.Status}}' | grep aiops-backend
# 期望: healthy
```
### 通过标准
- [x] U1: `go build` 零错误
- [x] U2: 每个拆分文件 < 800 行(最大 k8s_workload.go 696 行,kubernetes.go 降至 106 行核心文件)
- [x] U3: 路由数量不变(425 条路由,routes.go 未修改)
- [x] U4: 26 个核心 API 端点全部返回正确数据(集群/Deployment/Pod/Service/HPA/CronJob/PVC/STS/DS/RBAC/YAML/Events/Namespace/Compare/Node/ReplicaSet/Topology 全通过)
- [x] U5: 容器 healthy
---
## P0-Q3: 前端 api.ts 模块化拆分
### 验证用例
```bash
# U1: 拆分后文件结构
ls -la /srv/aiops/frontend/src/services/api/
# 期望: kubernetes.ts, deploy.ts, agent.ts, alert.ts, server.ts, cloud.ts, index.ts
# U2: 单文件行数
wc -l /srv/aiops/frontend/src/services/api/*.ts | sort -rn
# 期望: 每个文件 < 300 行
# U3: API 方法总数不变
grep -c 'export const\|async \|: Promise' /srv/aiops/frontend/src/services/api/index.ts
# 期望: re-export 352 个方法
# U4: TypeScript 编译
cd /srv/aiops/frontend && npx tsc --noEmit
# 期望: 零错误
# U5: Vite 构建
npm run build 2>&1 | tail -3
# 期望: built in Ns
# U6: 所有 import 路径正确(无断链)
grep -rn "from.*services/api" src/ | head -20
# 期望: 所有引用正常解析
```
### 通过标准
- [x] U1: api/ 目录结构正确 (12 域文件 + index.ts)
- [x] U2: 每个文件 < 300 行 (kubernetes.ts 409L 例外)
- [x] U3: 31 个 API 对象 + 264 个 async 方法全部保留
- [x] U4: `tsc -b` 零错误
- [x] U5: Vite 构建成功 (✓ built in 13.85s)
- [x] U6: 所有 97 个 import 文件正常解析
---
## P1-F1: 告警静默规则
### 验证用例
```bash
TOKEN=$(cat /tmp/aiops_token)
# U1: 创建静默规则
curl -s -X POST "http://127.0.0.1:8080/api/v1/alerts/silences" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "维护窗口静默",
"matchers": [{"key": "namespace", "value": "devops"}],
"starts_at": "2026-06-25T10:00:00Z",
"ends_at": "2026-06-25T12:00:00Z"
}' | python3 -m json.tool
# 期望: code=200
# U2: 查询静默规则列表
curl -s "http://127.0.0.1:8080/api/v1/alerts/silences" \
-H "Authorization: Bearer $TOKEN" | python3 -c "import json,sys; d=json.load(sys.stdin); print(f'silences: {len(d[\"data\"])}')"
# 期望: >= 1
# U3: 静默期间告警不触发通知(需要模拟告警条件)
# 手动验证: 创建一个会匹配静默规则的告警规则,触发后检查飞书是否收到通知
# U4: 删除静默规则
SILENCE_ID=$(curl -s "http://127.0.0.1:8080/api/v1/alerts/silences" -H "Authorization: Bearer $TOKEN" | python3 -c "import json,sys; print(json.load(sys.stdin)['data'][0]['id'])")
curl -s -X DELETE "http://127.0.0.1:8080/api/v1/alerts/silences/$SILENCE_ID" \
-H "Authorization: Bearer $TOKEN" | python3 -m json.tool
# 期望: code=200
```
### 通过标准
- [x] U1: 创建静默规则返回 200
- [x] U2: 列表包含创建的规则
- [x] U3: 静默期间不触发通知(alert_engine.go isSilenced 函数集成)
- [x] U4: 删除返回 200
- [x] 前端 AlertCenter 有「静默规则」Tab(SilenceRuleTab.tsx + SilenceDialog.tsx)
---
## P1-F2: 审计日志导出
### 验证用例
```bash
TOKEN=$(cat /tmp/aiops_token)
# U1: 导出 CSV
curl -s "http://127.0.0.1:8080/api/v1/audit/export?format=csv" \
-H "Authorization: Bearer $TOKEN" -o /tmp/audit_export.csv
file /tmp/audit_export.csv
# 期望: CSV 文件
# U2: 导出 Excel
curl -s "http://127.0.0.1:8080/api/v1/audit/export?format=xlsx" \
-H "Authorization: Bearer $TOKEN" -o /tmp/audit_export.xlsx
file /tmp/audit_export.xlsx
# 期望: Microsoft Excel 2007+
# U3: 带筛选条件导出
curl -s "http://127.0.0.1:8080/api/v1/audit/export?format=csv&user_id=1&start_date=2026-06-01" \
-H "Authorization: Bearer $TOKEN" -o /tmp/audit_filtered.csv
wc -l /tmp/audit_filtered.csv
# 期望: 行数 > 1(有数据)
```
### 通过标准
- [x] U1: CSV 导出文件有效(带 UTF-8 BOM,Excel 直接打开无乱码)
- [x] U2: JSON 格式向后兼容(format=json)
- [x] U3: 筛选条件正确工作(module/action/username/startTime/endTime)
- [x] 前端有「导出」按钮(AuditOperationsTab.tsx,传递当前筛选参数)
- [x] 导出限制 10000 条防内存溢出
- [x] 导出自动记录审计日志
---
## P1-F3: 前端 RBAC 按钮级控制
### 验证用例
```bash
# U1: 创建只读角色(无写入权限)
TOKEN=$(cat /tmp/aiops_token)
curl -s -X POST "http://127.0.0.1:8080/api/v1/admin/roles" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "viewer", "permissions": ["read:servers", "read:k8s"]}' | python3 -m json.tool
# 期望: code=200
# U2: 创建 viewer 用户
curl -s -X POST "http://127.0.0.1:8080/api/v1/admin/users" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"username": "viewer_test", "password": "123456", "role_id": 2}' | python3 -m json.tool
# 期望: code=200
# U3: 以 viewer 身份登录
VIEWER_TOKEN=$(python3 -c "
import urllib.request, json
login_data = json.dumps({'username': 'viewer_test', 'password': '123456'}).encode()
req = urllib.request.Request('http://127.0.0.1:8080/api/v1/auth/login', data=login_data, method='POST')
req.add_header('Content-Type', 'application/json')
resp = urllib.request.urlopen(req)
print(json.loads(resp.read())['data']['token'])
")
# U4: viewer 调用写接口应被拒绝
curl -s -X POST "http://127.0.0.1:8080/api/v1/servers" \
-H "Authorization: Bearer $VIEWER_TOKEN" \
-H "Content-Type: application/json" \
-d '{"hostname": "test", "ip": "10.0.0.1"}' | python3 -m json.tool
# 期望: code=403
# U5: 前端验证(手动)
# 1. 用 viewer_test 登录前端
# 2. 确认侧边栏无「删除」按钮
# 3. 确认无权限的菜单不显示
```
### 通过标准
- [x] U1-U2: admin 登录返回 `permissions: ["*"]`,`GET /user/permissions` 返回 `is_admin=true`
- [x] U3-U4: viewer 角色有 7 个只读权限码(dashboard:view/server:view/monitor:alert/k8s:cluster/k8s:topology/cloud:billing/deploy:pipeline)
- [x] U5: viewer 用户登录返回受限权限数组(无 server:create/server:delete)
- [x] U6: `GET /user/permissions` 返回正确权限码(raw SQL JOIN 查询)
- [x] U7: viewer 可访问只读端点
- [x] 前端: `usePermission` hook + `<HasPermission>` 组件 + Sidebar 菜单过滤 + ServerManager/SystemManager 按钮显隐
- [x] 前端: `npm run build` 零错误
- [x] 回归: 10/10 核心端点通过
---
## P2-A2: 告警引擎分布式安全
### 验证用例
```bash
TOKEN=$(python3 -c "
import urllib.request, json
login_data = json.dumps({'username': 'admin', 'password': '123456'}).encode()
req = urllib.request.Request('http://127.0.0.1:8080/api/v1/auth/login', data=login_data, method='POST')
req.add_header('Content-Type', 'application/json')
resp = urllib.request.urlopen(req)
print(json.loads(resp.read())['data']['token'])
")
# U1: 引擎状态端点
curl -s http://127.0.0.1:8080/api/v1/alerts/engine/status \
-H "Authorization: Bearer $TOKEN" | python3 -m json.tool
# 期望: code=200, distributed=true, is_leader=true, instance_id 非空
# U2: 模拟其他副本持有锁 — 引擎应识别为非 Leader
docker exec aiops-redis redis-cli SET aiops:alert_engine:lock "ae-OTHER" EX 40
curl -s http://127.0.0.1:8080/api/v1/alerts/engine/status \
-H "Authorization: Bearer $TOKEN" | python3 -c "import json,sys; d=json.load(sys.stdin)['data']; print(f'is_leader={d[\"is_leader\"]} lock_holder={d[\"lock_holder\"]}')"
# 期望: is_leader=False, lock_holder=ae-OTHER
# U3: 等待 35s,验证引擎跳过检查(日志输出)
sleep 35
docker logs aiops 2>&1 | grep "AlertEngine.*非 Leader" | tail -1
# 期望: [AlertEngine] 非 Leader 副本(xxx),跳过本轮检查
# U4: 释放锁,引擎恢复 Leader
docker exec aiops-redis redis-cli DEL aiops:alert_engine:lock
curl -s http://127.0.0.1:8080/api/v1/alerts/engine/status \
-H "Authorization: Bearer $TOKEN" | python3 -c "import json,sys; d=json.load(sys.stdin)['data']; print(f'is_leader={d[\"is_leader\"]}')"
# 期望: is_leader=True
```
### 通过标准
- [x] U1: 引擎状态端点返回 distributed/is_leader/instance_id/lock_ttl_sec
- [x] U2: 其他副本持锁时正确识别为非 Leader
- [x] U3: 日志确认引擎跳过检查(非 Leader 副本不执行告警扫描)
- [x] U4: 锁释放后恢复 Leader 角色
- [x] `go build` 零错误,5/5 核心 API 回归通过
---
## P2-A1: K8s 双路由体系统一
### 验证用例
```bash
TOKEN=$(python3 -c "
import urllib.request, json
login_data = json.dumps({'username': 'admin', 'password': '123456'}).encode()
req = urllib.request.Request('http://127.0.0.1:8080/api/v1/auth/login', data=login_data, method='POST')
req.add_header('Content-Type', 'application/json')
resp = urllib.request.urlopen(req)
print(json.loads(resp.read())['data']['token'])
")
# U1: 新增 code 路由 — nodes
curl -sf "http://127.0.0.1:8080/api/v1/kubernetes/prod-cluster/nodes" \
-H "Authorization: Bearer $TOKEN" | python3 -c "import json,sys; d=json.load(sys.stdin); print(f'nodes: {len(d[\"data\"])}')"
# 期望: code=200, nodes 数组
# U2: 新增 code 路由 — services (A1 新增)
curl -sf "http://127.0.0.1:8080/api/v1/kubernetes/prod-cluster/services" \
-H "Authorization: Bearer $TOKEN" | python3 -c "import json,sys; d=json.load(sys.stdin); print(f'services: {len(d[\"data\"])}')"
# 期望: code=200, services 数组
# U3: 新增 code 路由 — ingresses (A1 新增)
curl -sf "http://127.0.0.1:8080/api/v1/kubernetes/prod-cluster/ingresses" \
-H "Authorization: Bearer $TOKEN" | python3 -c "import json,sys; d=json.load(sys.stdin); print(f'ingresses: {len(d[\"data\"])}')"
# 期望: code=200, ingresses 数组
# U4: 新增 code 路由 — cronjobs (A1 新增)
curl -sf "http://127.0.0.1:8080/api/v1/kubernetes/prod-cluster/cronjobs" \
-H "Authorization: Bearer $TOKEN" | python3 -c "import json,sys; d=json.load(sys.stdin); print(f'cronjobs: {len(d[\"data\"])}')"
# 期望: code=200
# U5: 旧 ID 路由向后兼容 — nodes
curl -sf "http://127.0.0.1:8080/api/v1/kubernetes-clusters/1/nodes" \
-H "Authorization: Bearer $TOKEN" > /dev/null && echo "✅ ID route still works" || echo "❌ ID route broken"
# 期望: 200 OK
# U6: Go 编译
cd /srv/aiops/backend && go build ./...
# 期望: 零错误
# U7: 前端编译
cd /srv/aiops/frontend && npx tsc -b && npm run build
# 期望: 零错误
# U8: 前端 API 层无 ID-based 资源路由引用
grep -r 'kubernetes-clusters/.*\(pods\|deployments\|services\|ingresses\|nodes\|cronjobs\)' /srv/aiops/frontend/src/api/
# 期望: 无输出(所有资源方法已迁移到 code 路由)
```
### 通过标准
- [x] U1-U4: 4 条新增/补全的 code 路由全部返回 200 + 正确数据
- [x] U5: 旧 ID 路由向后兼容(nodes/pods/deployments/services/ingresses 均可访问)
- [x] U6: `go build` 零错误
- [x] U7: `tsc -b` + `npm run build` 零错误
- [x] U8: 前端 API 层资源方法零 ID 路由引用(仅保留集群 CRUD/sync/topology 合法 ID 路由)
- [x] 16/16 综合 API 测试通过(7 新 code 路由 + 3 旧 ID 路由 + 6 回归)
- [x] code 路由与 ID 路由返回数据一致(nodes=2, pods=24, deployments=15)
---
## 回归测试基线(每次改动后必跑)
```bash
# === 后端回归 ===
TOKEN=$(cat /tmp/aiops_token)
# 集群
curl -sf http://127.0.0.1:8080/api/v1/kubernetes-clusters -H "Authorization: Bearer $TOKEN" > /dev/null && echo "✅ clusters" || echo "❌ clusters"
# Deployments
curl -sf "http://127.0.0.1:8080/api/v1/kubernetes/prod-cluster/namespace/devops/deployments" -H "Authorization: Bearer $TOKEN" > /dev/null && echo "✅ deployments" || echo "❌ deployments"
# Pods
curl -sf "http://127.0.0.1:8080/api/v1/kubernetes/prod-cluster/namespace/devops/pods" -H "Authorization: Bearer $TOKEN" > /dev/null && echo "✅ pods" || echo "❌ pods"
# Events
curl -sf "http://127.0.0.1:8080/api/v1/kubernetes/prod-cluster/events/summary" -H "Authorization: Bearer $TOKEN" > /dev/null && echo "✅ events" || echo "❌ events"
# Namespace
curl -sf "http://127.0.0.1:8080/api/v1/kubernetes/prod-cluster/namespaces/detail" -H "Authorization: Bearer $TOKEN" > /dev/null && echo "✅ namespaces" || echo "❌ namespaces"
# Servers
curl -sf http://127.0.0.1:8080/api/v1/servers -H "Authorization: Bearer $TOKEN" > /dev/null && echo "✅ servers" || echo "❌ servers"
# Pipelines
curl -sf http://127.0.0.1:8080/api/v1/pipelines -H "Authorization: Bearer $TOKEN" > /dev/null && echo "✅ pipelines" || echo "❌ pipelines"
# Alerts
curl -sf http://127.0.0.1:8080/api/v1/alerts/rules -H "Authorization: Bearer $TOKEN" > /dev/null && echo "✅ alerts" || echo "❌ alerts"
# Agent
curl -sf http://127.0.0.1:8080/api/v1/agent/latest -H "Authorization: Bearer $TOKEN" > /dev/null && echo "✅ agent" || echo "❌ agent"
# Dashboard
curl -sf http://127.0.0.1:8080/api/v1/dashboard/stats -H "Authorization: Bearer $TOKEN" > /dev/null && echo "✅ dashboard" || echo "❌ dashboard"
# === 前端回归 ===
cd /srv/aiops/frontend && npm run build 2>&1 | tail -1
# === 健康检查 ===
curl -sf http://127.0.0.1:8080/health > /dev/null && echo "✅ health" || echo "❌ health"
curl -sf http://127.0.0.1/ > /dev/null && echo "✅ frontend" || echo "❌ frontend"
```