feat(dev-test): 添加集成测试模板 + TG2 检测规则

- 新增 templates/go-integration-test.md 集成测试代码骨架模板
- SKILL.md 增加 TG2 集成测试检测:跨 handlers/middleware/routes 变更自动触发

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-16 14:28:44 +10:30
parent 2309e31e74
commit 0724357ff4
2 changed files with 145 additions and 0 deletions

View File

@@ -0,0 +1,127 @@
# Go 集成测试模板
## 适用场景
当 git diff 显示同一功能的 **handlers/ + middleware/ + routes/** 文件均有变更时,除单元测试外应额外生成集成测试。
适用于:
- 多步骤 API 流程(登录→操作→验证→退出)
- 中间件拦截验证(权限、限流、模拟状态限制)
- 租户隔离 / 数据隔离验证
- 跨模块交互handler ↔ middleware ↔ repository
## 检测规则
通过命名模式识别同一功能的跨文件变更:
```
handlers/{feature}_handler.go
middleware/{feature}_middleware.go
routes/{feature}_routes.go
```
例如 `impersonation_handler.go` + `impersonation_middleware.go` + `impersonation_routes.go` 同时变更 → 生成 `tests/impersonation_integration_test.go`
## 生成规则
1. 测试文件放在 `backend/tests/` 目录(与 `test_helpers.go` 同包)
2. 使用 `SetupTestApp` + `defer TeardownTestApp` 初始化真实路由和数据库
3. 每个测试完全自包含:独立创建/清理数据
4. 异步持久化操作goroutine 写 DB`time.Sleep(200-500ms)` 后再验证
## 代码骨架
```go
package tests
import (
"encoding/json"
"fmt"
"net/http"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestFeature_Scenario(t *testing.T) {
testApp := SetupTestApp(t)
defer TeardownTestApp(t, testApp)
// === Setup: 创建测试数据 ===
// 使用 test_helpers.go 中的 helper 函数
sysAdmin := CreateTestSystemUser(t, testApp, "admin_scenario")
// tenantID := CreateTestTenant(t, testApp, "scenario_tenant")
// enterprise := CreateTestEnterpriseWithTenant(t, testApp, "Corp", "CODE", tenantID)
// tenantAdmin := CreateTestTenantAdmin(t, testApp, "tadmin", enterprise.ID, tenantID)
// === Cleanup: 按外键约束顺序清理 ===
defer CleanupImpersonationTestData(t, testApp,
[]int{sysAdmin.ID},
[]int{}, // enterpriseIDs
[]int64{}, // tenantIDs
)
// === 状态变量:跨步骤传递 ===
var token string
t.Run("Step1_InitialAction", func(t *testing.T) {
w := MakeAuthenticatedRequest(t, testApp, http.MethodPost,
"/api/v1/...",
map[string]string{"key": "value"},
sysAdmin,
)
require.Equal(t, http.StatusOK, w.Code, "Step1 failed: %s", w.Body.String())
var resp map[string]interface{}
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
// 提取后续步骤需要的数据
token = resp["data"].(map[string]interface{})["token"].(string)
})
t.Run("Step2_VerifyState", func(t *testing.T) {
if token == "" {
t.Skip("Step1 failed")
}
// 使用 Step1 产出的 token 继续验证
})
t.Run("StepN_AsyncVerification", func(t *testing.T) {
// 异步写入的数据需要等待
time.Sleep(300 * time.Millisecond)
// 然后验证 DB 数据
})
}
```
## 4 种必测场景
| 场景类型 | 说明 | 示例 |
|----------|------|------|
| **Happy Path** | 完整正常流程 | 开始模拟 → 访问 API → 查状态 → 退出 → 确认恢复 |
| **权限拒绝** | 无权限用户尝试操作 | 普通用户尝试模拟 → 403 |
| **隔离验证** | 跨租户/跨企业数据隔离 | tenant2 admin 访问 tenant1 企业 → 403 |
| **边界条件** | 输入校验、状态冲突 | 原因太短 → 400已在模拟中再次模拟 → 403 |
## Helper 函数参考
```go
// 来自 test_helpers.go
SetupTestApp(t) // 初始化完整应用(路由 + DB
CreateTestSystemUser(t, app, username) // 系统管理员 + JWT
CreateTestTenant(t, app, name) // 创建租户,返回 int64 ID
CreateTestEnterpriseWithTenant(t, app, name, code, tenantID) // 带租户的企业
CreateTestTenantAdmin(t, app, username, enterpriseID, tenantID...) // 租户管理员
CreateTestEnterpriseUser(t, app, username, enterpriseID) // 普通企业用户
ExtractImpersonationToken(t, response) // 从模拟响应提取 token
MakeAuthenticatedRequest(t, app, method, path, body, user) // 发送认证请求
CleanupImpersonationTestData(t, app, userIDs, enterpriseIDs, tenantIDs) // 按FK顺序清理
```
## 注意事项
- **Token 链式传递**:模拟 API 返回新 token后续请求必须用新 token
- **异步持久化**handler 用 goroutine 写 session/audit测试需 Sleep 后再查
- **数据隔离**:每个测试用唯一的 username/code避免测试间干扰
- **清理顺序**:外键约束要求先删子表再删父表