fix: resolve go vet warnings in webhook_handler_test.go
- Replace raw http.DefaultClient.Do(req) with doRequestWithCheck helper - Helper function now handles errors via t.Fatalf - Content-Type only set when body is non-nil docs: update REAL_PROJECT_STATUS.md with 2026-04-09 verification Go vet: 0 warnings
This commit is contained in:
@@ -1,5 +1,60 @@
|
|||||||
# REAL PROJECT STATUS
|
# 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 测试扩展
|
## 2026-04-02 E2E 测试扩展
|
||||||
|
|
||||||
### E2E 测试场景扩展
|
### E2E 测试场景扩展
|
||||||
|
|||||||
@@ -28,6 +28,31 @@ import (
|
|||||||
|
|
||||||
var webhookDbCounter int64
|
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()) {
|
func setupWebhookTestServer(t *testing.T) (*httptest.Server, *gorm.DB, string, func()) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
gin.SetMode(gin.TestMode)
|
gin.SetMode(gin.TestMode)
|
||||||
@@ -148,12 +173,7 @@ func TestWebhookHandler_CreateWebhook_Success(t *testing.T) {
|
|||||||
"url": "https://example.com/webhook",
|
"url": "https://example.com/webhook",
|
||||||
"events": []string{"user.created", "user.deleted"},
|
"events": []string{"user.created", "user.deleted"},
|
||||||
}
|
}
|
||||||
jsonBytes, _ := json.Marshal(reqBody)
|
resp := doRequestWithCheck(t, "POST", server.URL+"/api/v1/webhooks", token, 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)
|
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusCreated {
|
if resp.StatusCode != http.StatusCreated {
|
||||||
@@ -181,12 +201,8 @@ func TestWebhookHandler_CreateWebhook_InvalidURL(t *testing.T) {
|
|||||||
"url": "not-a-valid-url",
|
"url": "not-a-valid-url",
|
||||||
"events": []string{"user.created"},
|
"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()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusBadRequest {
|
if resp.StatusCode != http.StatusBadRequest {
|
||||||
@@ -202,12 +218,8 @@ func TestWebhookHandler_CreateWebhook_MissingName(t *testing.T) {
|
|||||||
"url": "https://example.com/webhook",
|
"url": "https://example.com/webhook",
|
||||||
"events": []string{"user.created"},
|
"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()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusBadRequest {
|
if resp.StatusCode != http.StatusBadRequest {
|
||||||
@@ -225,17 +237,11 @@ func TestWebhookHandler_ListWebhooks_Success(t *testing.T) {
|
|||||||
"url": "https://example.com/webhook",
|
"url": "https://example.com/webhook",
|
||||||
"events": []string{"user.created"},
|
"events": []string{"user.created"},
|
||||||
}
|
}
|
||||||
jsonBytes, _ := json.Marshal(reqBody)
|
resp := doRequestWithCheck(t, "POST", server.URL+"/api/v1/webhooks", token, reqBody)
|
||||||
req, _ := http.NewRequest("POST", server.URL+"/api/v1/webhooks", bytes.NewReader(jsonBytes))
|
resp.Body.Close()
|
||||||
req.Header.Set("Content-Type", "application/json")
|
|
||||||
req.Header.Set("Authorization", "Bearer "+token)
|
|
||||||
http.DefaultClient.Do(req)
|
|
||||||
|
|
||||||
// List webhooks
|
// List webhooks
|
||||||
req, _ = http.NewRequest("GET", server.URL+"/api/v1/webhooks?page=1&page_size=10", nil)
|
resp = doRequestWithCheck(t, "GET", server.URL+"/api/v1/webhooks?page=1&page_size=10", token, nil)
|
||||||
req.Header.Set("Authorization", "Bearer "+token)
|
|
||||||
|
|
||||||
resp, _ := http.DefaultClient.Do(req)
|
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
@@ -267,11 +273,7 @@ func TestWebhookHandler_UpdateWebhook_Success(t *testing.T) {
|
|||||||
"url": "https://example.com/webhook",
|
"url": "https://example.com/webhook",
|
||||||
"events": []string{"user.created"},
|
"events": []string{"user.created"},
|
||||||
}
|
}
|
||||||
jsonBytes, _ := json.Marshal(createReq)
|
resp := doRequestWithCheck(t, "POST", server.URL+"/api/v1/webhooks", token, 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)
|
|
||||||
var createResult map[string]interface{}
|
var createResult map[string]interface{}
|
||||||
json.NewDecoder(resp.Body).Decode(&createResult)
|
json.NewDecoder(resp.Body).Decode(&createResult)
|
||||||
resp.Body.Close()
|
resp.Body.Close()
|
||||||
@@ -282,12 +284,8 @@ func TestWebhookHandler_UpdateWebhook_Success(t *testing.T) {
|
|||||||
updateReq := map[string]interface{}{
|
updateReq := map[string]interface{}{
|
||||||
"name": "Updated Name",
|
"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()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
@@ -310,12 +308,8 @@ func TestWebhookHandler_UpdateWebhook_InvalidID(t *testing.T) {
|
|||||||
updateReq := map[string]interface{}{
|
updateReq := map[string]interface{}{
|
||||||
"name": "Updated Name",
|
"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()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusBadRequest {
|
if resp.StatusCode != http.StatusBadRequest {
|
||||||
@@ -333,11 +327,7 @@ func TestWebhookHandler_DeleteWebhook_Success(t *testing.T) {
|
|||||||
"url": "https://example.com/webhook",
|
"url": "https://example.com/webhook",
|
||||||
"events": []string{"user.created"},
|
"events": []string{"user.created"},
|
||||||
}
|
}
|
||||||
jsonBytes, _ := json.Marshal(createReq)
|
resp := doRequestWithCheck(t, "POST", server.URL+"/api/v1/webhooks", token, 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)
|
|
||||||
var createResult map[string]interface{}
|
var createResult map[string]interface{}
|
||||||
json.NewDecoder(resp.Body).Decode(&createResult)
|
json.NewDecoder(resp.Body).Decode(&createResult)
|
||||||
resp.Body.Close()
|
resp.Body.Close()
|
||||||
@@ -345,10 +335,7 @@ func TestWebhookHandler_DeleteWebhook_Success(t *testing.T) {
|
|||||||
webhookID := createResult["data"].(map[string]interface{})["id"].(float64)
|
webhookID := createResult["data"].(map[string]interface{})["id"].(float64)
|
||||||
|
|
||||||
// Delete the webhook
|
// Delete the webhook
|
||||||
req, _ = http.NewRequest("DELETE", server.URL+fmt.Sprintf("/api/v1/webhooks/%.0f", webhookID), nil)
|
resp = doRequestWithCheck(t, "DELETE", server.URL+fmt.Sprintf("/api/v1/webhooks/%.0f", webhookID), token, nil)
|
||||||
req.Header.Set("Authorization", "Bearer "+token)
|
|
||||||
|
|
||||||
resp, _ = http.DefaultClient.Do(req)
|
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
@@ -368,10 +355,7 @@ func TestWebhookHandler_DeleteWebhook_NotFound(t *testing.T) {
|
|||||||
server, _, token, cleanup := setupWebhookTestServer(t)
|
server, _, token, cleanup := setupWebhookTestServer(t)
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
req, _ := http.NewRequest("DELETE", server.URL+"/api/v1/webhooks/99999", nil)
|
resp := doRequestWithCheck(t, "DELETE", server.URL+"/api/v1/webhooks/99999", token, nil)
|
||||||
req.Header.Set("Authorization", "Bearer "+token)
|
|
||||||
|
|
||||||
resp, _ := http.DefaultClient.Do(req)
|
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
// Delete is idempotent - returns 200 even if not found
|
// 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",
|
"url": "https://example.com/webhook",
|
||||||
"events": []string{"user.created"},
|
"events": []string{"user.created"},
|
||||||
}
|
}
|
||||||
jsonBytes, _ := json.Marshal(createReq)
|
resp := doRequestWithCheck(t, "POST", server.URL+"/api/v1/webhooks", token, 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)
|
|
||||||
var createResult map[string]interface{}
|
var createResult map[string]interface{}
|
||||||
json.NewDecoder(resp.Body).Decode(&createResult)
|
json.NewDecoder(resp.Body).Decode(&createResult)
|
||||||
resp.Body.Close()
|
resp.Body.Close()
|
||||||
@@ -402,10 +382,7 @@ func TestWebhookHandler_GetWebhookDeliveries_Success(t *testing.T) {
|
|||||||
webhookID := createResult["data"].(map[string]interface{})["id"].(float64)
|
webhookID := createResult["data"].(map[string]interface{})["id"].(float64)
|
||||||
|
|
||||||
// Get webhook deliveries
|
// Get webhook deliveries
|
||||||
req, _ = http.NewRequest("GET", server.URL+fmt.Sprintf("/api/v1/webhooks/%.0f/deliveries?limit=20", webhookID), nil)
|
resp = doRequestWithCheck(t, "GET", server.URL+fmt.Sprintf("/api/v1/webhooks/%.0f/deliveries?limit=20", webhookID), token, nil)
|
||||||
req.Header.Set("Authorization", "Bearer "+token)
|
|
||||||
|
|
||||||
resp, _ = http.DefaultClient.Do(req)
|
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
@@ -428,10 +405,7 @@ func TestWebhookHandler_GetWebhookDeliveries_InvalidID(t *testing.T) {
|
|||||||
server, _, token, cleanup := setupWebhookTestServer(t)
|
server, _, token, cleanup := setupWebhookTestServer(t)
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
req, _ := http.NewRequest("GET", server.URL+"/api/v1/webhooks/invalid/deliveries", nil)
|
resp := doRequestWithCheck(t, "GET", server.URL+"/api/v1/webhooks/invalid/deliveries", token, nil)
|
||||||
req.Header.Set("Authorization", "Bearer "+token)
|
|
||||||
|
|
||||||
resp, _ := http.DefaultClient.Do(req)
|
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusBadRequest {
|
if resp.StatusCode != http.StatusBadRequest {
|
||||||
@@ -444,25 +418,19 @@ func TestWebhookHandler_ListWebhooks_Pagination(t *testing.T) {
|
|||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
// Create multiple webhooks
|
// Create multiple webhooks
|
||||||
|
var resp *http.Response
|
||||||
for i := 0; i < 3; i++ {
|
for i := 0; i < 3; i++ {
|
||||||
reqBody := map[string]interface{}{
|
reqBody := map[string]interface{}{
|
||||||
"name": fmt.Sprintf("Pagination Test Webhook %d", i),
|
"name": fmt.Sprintf("Pagination Test Webhook %d", i),
|
||||||
"url": "https://example.com/webhook",
|
"url": "https://example.com/webhook",
|
||||||
"events": []string{"user.created"},
|
"events": []string{"user.created"},
|
||||||
}
|
}
|
||||||
jsonBytes, _ := json.Marshal(reqBody)
|
resp = doRequestWithCheck(t, "POST", server.URL+"/api/v1/webhooks", token, 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.Body.Close()
|
resp.Body.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test pagination
|
// Test pagination
|
||||||
req, _ := http.NewRequest("GET", server.URL+"/api/v1/webhooks?page=1&page_size=2", nil)
|
resp = doRequestWithCheck(t, "GET", server.URL+"/api/v1/webhooks?page=1&page_size=2", token, nil)
|
||||||
req.Header.Set("Authorization", "Bearer "+token)
|
|
||||||
|
|
||||||
resp, _ := http.DefaultClient.Do(req)
|
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
var result map[string]interface{}
|
var result map[string]interface{}
|
||||||
|
|||||||
Reference in New Issue
Block a user