Files
John Qiu 0724357ff4 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>
2026-03-16 14:28:44 +10:30

128 lines
4.6 KiB
Markdown
Raw Permalink 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.
# 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避免测试间干扰
- **清理顺序**:外键约束要求先删子表再删父表