fix/status-review-sync-20260409 #1

Merged
long merged 65 commits from fix/status-review-sync-20260409 into main 2026-04-18 15:05:51 +00:00
2 changed files with 98 additions and 75 deletions
Showing only changes of commit 71d4dcc441 - Show all commits

View File

@@ -1,5 +1,60 @@
# REAL PROJECT STATUS
## 2026-04-09 最低验证矩阵 & Service层测试增强
### 本轮验证结果 (2026-04-09)
| 验证项 | 状态 | 说明 |
|--------|------|------|
| `go build ./cmd/server` | ✅ | 构建成功 |
| `go test ./internal/... -short` | ✅ | 全部38个packages通过 |
| `go vet ./internal/...` | ✅ | 无警告 |
| `npm run build` (frontend) | ✅ | 构建成功 |
### 本轮修复内容
- **go vet 警告修复**: `webhook_handler_test.go` 中的 `resp` 错误检查问题
- 添加 `doRequestWithCheck` 辅助函数统一错误处理
- 所有 HTTP 请求现通过辅助函数执行,自动处理错误
- **Service层测试增强**: 新增6个测试文件
- `webhook_service_test.go`: `isPrivateIP`, `isSafeURL`, `computeHMAC` 安全函数
- `request_metadata_test.go`: Context元数据函数
- `classified_error_test.go`: 错误类型测试
- `config_defaults_test.go`: 配置默认值测试
- `email_config_test.go`: 邮箱配置测试
- `auth_runtime_test.go`: `isUserNotFoundError` 测试
### 覆盖率状态
| 模块 | 覆盖率 |
|------|--------|
| api/handler | 15.6% |
| api/middleware | 21.5% |
| auth | 28.1% |
| auth/providers | **80.6%** |
| cache | **77.3%** |
| config | **85.2%** |
| database | **74.1%** |
| repository | 47.2% |
| middleware (internal) | **65.4%** |
| service | 14.7% |
### Govulncheck 漏洞状态
| 漏洞 | 影响 | 状态 |
|------|------|------|
| GO-2026-4866 (crypto/x509) | 需要 Go 1.26.2 修复 | ⚠️ 当前 Go 1.26.1 |
| GO-2026-4865 (html/template) | 需要 Go 1.26.2 修复 | ⚠️ 当前 Go 1.26.1 |
**说明**: Go 1.26.2 下载失败(网络问题),待环境恢复后升级。
### 提交记录
- `a3e090e` - test: add service layer unit tests for webhook/metadata/error/config
- `a6a0e58` - test: add more UserHandler tests for RBAC coverage
- `3ffce94` - test: add WebhookHandler tests
## 2026-04-02 E2E 测试扩展
### E2E 测试场景扩展

View File

@@ -28,6 +28,31 @@ import (
var webhookDbCounter int64
// doRequestWithCheck 执行HTTP请求并在失败时t.Fatalf
func doRequestWithCheck(t *testing.T, method, url string, token string, body interface{}) *http.Response {
t.Helper()
var bodyReader io.Reader
if body != nil {
jsonBytes, _ := json.Marshal(body)
bodyReader = bytes.NewReader(jsonBytes)
}
req, err := http.NewRequest(method, url, bodyReader)
if err != nil {
t.Fatalf("create request failed: %v", err)
}
if token != "" {
req.Header.Set("Authorization", "Bearer "+token)
}
if body != nil {
req.Header.Set("Content-Type", "application/json")
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
t.Fatalf("request failed: %v", err)
}
return resp
}
func setupWebhookTestServer(t *testing.T) (*httptest.Server, *gorm.DB, string, func()) {
t.Helper()
gin.SetMode(gin.TestMode)
@@ -148,12 +173,7 @@ func TestWebhookHandler_CreateWebhook_Success(t *testing.T) {
"url": "https://example.com/webhook",
"events": []string{"user.created", "user.deleted"},
}
jsonBytes, _ := json.Marshal(reqBody)
req, _ := http.NewRequest("POST", server.URL+"/api/v1/webhooks", bytes.NewReader(jsonBytes))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+token)
resp, _ := http.DefaultClient.Do(req)
resp := doRequestWithCheck(t, "POST", server.URL+"/api/v1/webhooks", token, reqBody)
defer resp.Body.Close()
if resp.StatusCode != http.StatusCreated {
@@ -181,12 +201,8 @@ func TestWebhookHandler_CreateWebhook_InvalidURL(t *testing.T) {
"url": "not-a-valid-url",
"events": []string{"user.created"},
}
jsonBytes, _ := json.Marshal(reqBody)
req, _ := http.NewRequest("POST", server.URL+"/api/v1/webhooks", bytes.NewReader(jsonBytes))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+token)
resp, _ := http.DefaultClient.Do(req)
resp := doRequestWithCheck(t, "POST", server.URL+"/api/v1/webhooks", token, reqBody)
defer resp.Body.Close()
if resp.StatusCode != http.StatusBadRequest {
@@ -202,12 +218,8 @@ func TestWebhookHandler_CreateWebhook_MissingName(t *testing.T) {
"url": "https://example.com/webhook",
"events": []string{"user.created"},
}
jsonBytes, _ := json.Marshal(reqBody)
req, _ := http.NewRequest("POST", server.URL+"/api/v1/webhooks", bytes.NewReader(jsonBytes))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+token)
resp, _ := http.DefaultClient.Do(req)
resp := doRequestWithCheck(t, "POST", server.URL+"/api/v1/webhooks", token, reqBody)
defer resp.Body.Close()
if resp.StatusCode != http.StatusBadRequest {
@@ -225,17 +237,11 @@ func TestWebhookHandler_ListWebhooks_Success(t *testing.T) {
"url": "https://example.com/webhook",
"events": []string{"user.created"},
}
jsonBytes, _ := json.Marshal(reqBody)
req, _ := http.NewRequest("POST", server.URL+"/api/v1/webhooks", bytes.NewReader(jsonBytes))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+token)
http.DefaultClient.Do(req)
resp := doRequestWithCheck(t, "POST", server.URL+"/api/v1/webhooks", token, reqBody)
resp.Body.Close()
// List webhooks
req, _ = http.NewRequest("GET", server.URL+"/api/v1/webhooks?page=1&page_size=10", nil)
req.Header.Set("Authorization", "Bearer "+token)
resp, _ := http.DefaultClient.Do(req)
resp = doRequestWithCheck(t, "GET", server.URL+"/api/v1/webhooks?page=1&page_size=10", token, nil)
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
@@ -267,11 +273,7 @@ func TestWebhookHandler_UpdateWebhook_Success(t *testing.T) {
"url": "https://example.com/webhook",
"events": []string{"user.created"},
}
jsonBytes, _ := json.Marshal(createReq)
req, _ := http.NewRequest("POST", server.URL+"/api/v1/webhooks", bytes.NewReader(jsonBytes))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+token)
resp, _ := http.DefaultClient.Do(req)
resp := doRequestWithCheck(t, "POST", server.URL+"/api/v1/webhooks", token, createReq)
var createResult map[string]interface{}
json.NewDecoder(resp.Body).Decode(&createResult)
resp.Body.Close()
@@ -282,12 +284,8 @@ func TestWebhookHandler_UpdateWebhook_Success(t *testing.T) {
updateReq := map[string]interface{}{
"name": "Updated Name",
}
jsonBytes, _ = json.Marshal(updateReq)
req, _ = http.NewRequest("PUT", server.URL+fmt.Sprintf("/api/v1/webhooks/%.0f", webhookID), bytes.NewReader(jsonBytes))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+token)
resp, _ = http.DefaultClient.Do(req)
resp = doRequestWithCheck(t, "PUT", server.URL+fmt.Sprintf("/api/v1/webhooks/%.0f", webhookID), token, updateReq)
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
@@ -310,12 +308,8 @@ func TestWebhookHandler_UpdateWebhook_InvalidID(t *testing.T) {
updateReq := map[string]interface{}{
"name": "Updated Name",
}
jsonBytes, _ := json.Marshal(updateReq)
req, _ := http.NewRequest("PUT", server.URL+"/api/v1/webhooks/invalid", bytes.NewReader(jsonBytes))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+token)
resp, _ := http.DefaultClient.Do(req)
resp := doRequestWithCheck(t, "PUT", server.URL+"/api/v1/webhooks/invalid", token, updateReq)
defer resp.Body.Close()
if resp.StatusCode != http.StatusBadRequest {
@@ -333,11 +327,7 @@ func TestWebhookHandler_DeleteWebhook_Success(t *testing.T) {
"url": "https://example.com/webhook",
"events": []string{"user.created"},
}
jsonBytes, _ := json.Marshal(createReq)
req, _ := http.NewRequest("POST", server.URL+"/api/v1/webhooks", bytes.NewReader(jsonBytes))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+token)
resp, _ := http.DefaultClient.Do(req)
resp := doRequestWithCheck(t, "POST", server.URL+"/api/v1/webhooks", token, createReq)
var createResult map[string]interface{}
json.NewDecoder(resp.Body).Decode(&createResult)
resp.Body.Close()
@@ -345,10 +335,7 @@ func TestWebhookHandler_DeleteWebhook_Success(t *testing.T) {
webhookID := createResult["data"].(map[string]interface{})["id"].(float64)
// Delete the webhook
req, _ = http.NewRequest("DELETE", server.URL+fmt.Sprintf("/api/v1/webhooks/%.0f", webhookID), nil)
req.Header.Set("Authorization", "Bearer "+token)
resp, _ = http.DefaultClient.Do(req)
resp = doRequestWithCheck(t, "DELETE", server.URL+fmt.Sprintf("/api/v1/webhooks/%.0f", webhookID), token, nil)
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
@@ -368,10 +355,7 @@ func TestWebhookHandler_DeleteWebhook_NotFound(t *testing.T) {
server, _, token, cleanup := setupWebhookTestServer(t)
defer cleanup()
req, _ := http.NewRequest("DELETE", server.URL+"/api/v1/webhooks/99999", nil)
req.Header.Set("Authorization", "Bearer "+token)
resp, _ := http.DefaultClient.Do(req)
resp := doRequestWithCheck(t, "DELETE", server.URL+"/api/v1/webhooks/99999", token, nil)
defer resp.Body.Close()
// Delete is idempotent - returns 200 even if not found
@@ -390,11 +374,7 @@ func TestWebhookHandler_GetWebhookDeliveries_Success(t *testing.T) {
"url": "https://example.com/webhook",
"events": []string{"user.created"},
}
jsonBytes, _ := json.Marshal(createReq)
req, _ := http.NewRequest("POST", server.URL+"/api/v1/webhooks", bytes.NewReader(jsonBytes))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+token)
resp, _ := http.DefaultClient.Do(req)
resp := doRequestWithCheck(t, "POST", server.URL+"/api/v1/webhooks", token, createReq)
var createResult map[string]interface{}
json.NewDecoder(resp.Body).Decode(&createResult)
resp.Body.Close()
@@ -402,10 +382,7 @@ func TestWebhookHandler_GetWebhookDeliveries_Success(t *testing.T) {
webhookID := createResult["data"].(map[string]interface{})["id"].(float64)
// Get webhook deliveries
req, _ = http.NewRequest("GET", server.URL+fmt.Sprintf("/api/v1/webhooks/%.0f/deliveries?limit=20", webhookID), nil)
req.Header.Set("Authorization", "Bearer "+token)
resp, _ = http.DefaultClient.Do(req)
resp = doRequestWithCheck(t, "GET", server.URL+fmt.Sprintf("/api/v1/webhooks/%.0f/deliveries?limit=20", webhookID), token, nil)
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
@@ -428,10 +405,7 @@ func TestWebhookHandler_GetWebhookDeliveries_InvalidID(t *testing.T) {
server, _, token, cleanup := setupWebhookTestServer(t)
defer cleanup()
req, _ := http.NewRequest("GET", server.URL+"/api/v1/webhooks/invalid/deliveries", nil)
req.Header.Set("Authorization", "Bearer "+token)
resp, _ := http.DefaultClient.Do(req)
resp := doRequestWithCheck(t, "GET", server.URL+"/api/v1/webhooks/invalid/deliveries", token, nil)
defer resp.Body.Close()
if resp.StatusCode != http.StatusBadRequest {
@@ -444,25 +418,19 @@ func TestWebhookHandler_ListWebhooks_Pagination(t *testing.T) {
defer cleanup()
// Create multiple webhooks
var resp *http.Response
for i := 0; i < 3; i++ {
reqBody := map[string]interface{}{
"name": fmt.Sprintf("Pagination Test Webhook %d", i),
"url": "https://example.com/webhook",
"events": []string{"user.created"},
}
jsonBytes, _ := json.Marshal(reqBody)
req, _ := http.NewRequest("POST", server.URL+"/api/v1/webhooks", bytes.NewReader(jsonBytes))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+token)
resp, _ := http.DefaultClient.Do(req)
resp = doRequestWithCheck(t, "POST", server.URL+"/api/v1/webhooks", token, reqBody)
resp.Body.Close()
}
// Test pagination
req, _ := http.NewRequest("GET", server.URL+"/api/v1/webhooks?page=1&page_size=2", nil)
req.Header.Set("Authorization", "Bearer "+token)
resp, _ := http.DefaultClient.Do(req)
resp = doRequestWithCheck(t, "GET", server.URL+"/api/v1/webhooks?page=1&page_size=2", token, nil)
defer resp.Body.Close()
var result map[string]interface{}