201 lines
5.3 KiB
Markdown
201 lines
5.3 KiB
Markdown
|
|
# 工程规则补充:虚假完成防范
|
|||
|
|
|
|||
|
|
版本:1.0
|
|||
|
|
更新时间:2026-04-11
|
|||
|
|
|
|||
|
|
本规则是 `QUALITY_STANDARD.md` 和 `PROJECT_EXPERIENCE_SUMMARY.md` 的补充,专门针对虚假完成的防范。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 1. 虚假完成的定义
|
|||
|
|
|
|||
|
|
虚假完成是指:
|
|||
|
|
- 声称"已修复"但实际未修复
|
|||
|
|
- 声称"已测试"但测试不运行或不验证真实行为
|
|||
|
|
- 声称"已完成"但遗漏关键部分(如缺少 swagger 注解、缺少边界条件测试)
|
|||
|
|
- 声称"已统一"但实际存在不一致
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 2. 必须逐项验证的检查点
|
|||
|
|
|
|||
|
|
### 2.1 Swagger 注解完整性
|
|||
|
|
|
|||
|
|
**每添加一个 handler 方法,必须同时添加完整的 swagger 注解。**
|
|||
|
|
|
|||
|
|
验证方法:
|
|||
|
|
```bash
|
|||
|
|
# 统计方法数 vs @Summary 数
|
|||
|
|
for f in internal/api/handler/*_handler.go; do
|
|||
|
|
methods=$(grep -E "^func \(h \*[A-Za-z]+.*\) [A-Z]" "$f" | wc -l)
|
|||
|
|
annotations=$(grep -c "@Summary" "$f" || echo 0)
|
|||
|
|
echo "$(basename $f): $methods methods, $annotations @Summary"
|
|||
|
|
done
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**当前缺口(截至 2026-04-11):**
|
|||
|
|
|
|||
|
|
| Handler | 方法数 | @Summary 数 | 缺口 |
|
|||
|
|
|---------|--------|-----------|------|
|
|||
|
|
| password_reset_handler.go | 5 | 1 | 4 |
|
|||
|
|
| totp_handler.go | 5 | 1 | 4 |
|
|||
|
|
| log_handler.go | 5 | 3 | 2 |
|
|||
|
|
|
|||
|
|
**每次提交前必须确保所有 handler 方法都有 @Summary。**
|
|||
|
|
|
|||
|
|
### 2.2 响应格式统一性
|
|||
|
|
|
|||
|
|
**所有 API 必须使用统一响应格式:**
|
|||
|
|
```go
|
|||
|
|
c.JSON(http.StatusOK, gin.H{
|
|||
|
|
"code": 0,
|
|||
|
|
"message": "success",
|
|||
|
|
"data": <实际数据>,
|
|||
|
|
})
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**例外情况**:
|
|||
|
|
- OAuth Token 端点(RFC 6749 要求直接返回 token)
|
|||
|
|
- 认证挑战响应(WWW-Authenticate)
|
|||
|
|
|
|||
|
|
**当前缺口(截至 2026-04-11):**
|
|||
|
|
- `sso_handler.go` 的 `Token` 端点 (line 213) 返回 `TokenResponse` 而非包装格式
|
|||
|
|
- `sso_handler.go` 的 `Introspect` 端点 (line 257, 261) 返回 `IntrospectResponse` 而非包装格式
|
|||
|
|
|
|||
|
|
### 2.3 集成测试基础设施
|
|||
|
|
|
|||
|
|
**IntegrationRedisSuite 类型必须在代码库中定义。**
|
|||
|
|
|
|||
|
|
当前问题:多个 `*_integration_test.go` 文件引用 `IntegrationRedisSuite`,但该类型从未定义。
|
|||
|
|
|
|||
|
|
验证方法:
|
|||
|
|
```bash
|
|||
|
|
# 检查 IntegrationRedisSuite 是否定义
|
|||
|
|
grep -r "type IntegrationRedisSuite" internal/repository/
|
|||
|
|
|
|||
|
|
# 检查哪些文件依赖它
|
|||
|
|
grep -l "IntegrationRedisSuite" internal/repository/*_integration_test.go
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**缺口(截至 2026-04-11):**
|
|||
|
|
- `internal/repository/` 下 7 个 `*_integration_test.go` 文件依赖未定义的 `IntegrationRedisSuite`
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 3. 验证命令
|
|||
|
|
|
|||
|
|
### 3.1 强制验证命令(在任何 PR 合并前)
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# 1. Swagger 注解完整性检查
|
|||
|
|
for f in internal/api/handler/*_handler.go; do
|
|||
|
|
methods=$(grep -E "^func \(h \*[A-Za-z]+.*\) [A-Z]" "$f" | wc -l)
|
|||
|
|
annotations=$(grep -c "@Summary" "$f" || echo 0)
|
|||
|
|
if [ "$methods" != "$annotations" ]; then
|
|||
|
|
echo "FAIL: $(basename $f) - methods:$methods annotations:$annotations"
|
|||
|
|
fi
|
|||
|
|
done
|
|||
|
|
|
|||
|
|
# 2. 响应格式检查(排除白名单)
|
|||
|
|
grep -rn "c.JSON.*TokenResponse\|c.JSON.*IntrospectResponse" internal/api/handler/
|
|||
|
|
|
|||
|
|
# 3. 集成测试类型检查
|
|||
|
|
grep -r "type IntegrationRedisSuite" internal/repository/
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3.2 测试覆盖验证
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# 运行测试并验证覆盖率
|
|||
|
|
go test ./internal/repository/... -cover -count=1
|
|||
|
|
|
|||
|
|
# 验证覆盖率数字真实性
|
|||
|
|
# 81.1% 意味着运行 go test 时会打印 coverage 数字
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3.3 E2E 验证
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# 真实浏览器 E2E(涉及认证、导航、主流程时必须)
|
|||
|
|
cd frontend/admin && npm.cmd run e2e:full:win
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 4. 常见虚假完成模式
|
|||
|
|
|
|||
|
|
### 模式 1:部分 swagger 注解
|
|||
|
|
|
|||
|
|
**错误做法**:只给部分方法添加 @Summary
|
|||
|
|
```go
|
|||
|
|
// ForgotPassword ✅
|
|||
|
|
func (h *PasswordResetHandler) ForgotPassword(c *gin.Context) { ... }
|
|||
|
|
|
|||
|
|
// ValidateResetToken ❌ 没有 @Summary
|
|||
|
|
func (h *PasswordResetHandler) ValidateResetToken(c *gin.Context) { ... }
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**正确做法**:每个方法都要注解
|
|||
|
|
```go
|
|||
|
|
// ForgotPassword 请求密码重置
|
|||
|
|
// @Summary 忘记密码
|
|||
|
|
// @Description ...
|
|||
|
|
func (h *PasswordResetHandler) ForgotPassword(c *gin.Context) { ... }
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 模式 2:响应格式不一致
|
|||
|
|
|
|||
|
|
**错误做法**:
|
|||
|
|
```go
|
|||
|
|
// SSO Token 端点直接返回 TokenResponse
|
|||
|
|
c.JSON(http.StatusOK, TokenResponse{...})
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**正确做法**:
|
|||
|
|
```go
|
|||
|
|
c.JSON(http.StatusOK, gin.H{"code": 0, "message": "success", "data": TokenResponse{...}})
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 模式 3:测试引用未定义类型
|
|||
|
|
|
|||
|
|
**错误做法**:
|
|||
|
|
```go
|
|||
|
|
type UpdateCacheSuite struct {
|
|||
|
|
IntegrationRedisSuite // 未定义!
|
|||
|
|
cache *updateCache
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**正确做法**:
|
|||
|
|
- 要么定义 `IntegrationRedisSuite`
|
|||
|
|
- 要么删除引用它的集成测试文件
|
|||
|
|
- 要么添加 `//go:build ignore` 标签并确保不编译
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 5. 防范承诺
|
|||
|
|
|
|||
|
|
在提交任何 PR 之前,必须:
|
|||
|
|
|
|||
|
|
1. **Swagger 注解**:确保每个 handler 方法都有 @Summary/@Description/@Param/@Success/@Router
|
|||
|
|
2. **响应格式**:确保使用统一的 `{"code": 0, "message": "success", "data": ...}` 格式
|
|||
|
|
3. **测试类型**:确保所有引用的类型都已定义
|
|||
|
|
4. **覆盖率数字**:确保声称的覆盖率数字是真实测试结果
|
|||
|
|
5. **文档同步**:确保文档中的声明与代码状态一致
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 6. 发现虚假完成时的处理
|
|||
|
|
|
|||
|
|
当发现虚假完成时:
|
|||
|
|
|
|||
|
|
1. **记录**:在发现问题的 PR 或 issue 中记录
|
|||
|
|
2. **修复**:立即修复虚假完成的部分
|
|||
|
|
3. **同步**:同步更新所有相关文档
|
|||
|
|
4. **防范**:将防範措施添加到本文件
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
**维护日期**: 2026-04-11
|
|||
|
|
**下次审查**: 每次 PR 合并前
|