package api import ( "bytes" "errors" "fmt" "log" "net/http" "net/http/httptest" "net/url" "strings" "testing" "github.com/gin-gonic/gin" ) // fakeServiceError 模拟 oci-go-sdk 的 common.ServiceError,驱动 respondOCIError 分支。 type fakeServiceError struct { status int code string message string } func (e fakeServiceError) Error() string { return fmt.Sprintf("Error returned by service. Http Status Code: %d. Error Code: %s. Message: %s", e.status, e.code, e.message) } func (e fakeServiceError) GetHTTPStatusCode() int { return e.status } func (e fakeServiceError) GetMessage() string { return e.message } func (e fakeServiceError) GetCode() string { return e.code } func (e fakeServiceError) GetOpcRequestID() string { return "req-1" } // TestRespondErrorOCIStatus 覆盖上游状态码改写规则: // 401/429 在前端有本地专属语义(登出 / 跳 /blocked),上游同码须改写 502。 func TestRespondErrorOCIStatus(t *testing.T) { gin.SetMode(gin.TestMode) cases := []struct { name string upstream int wantStatus int }{ {"404 原样透传", 404, 404}, {"409 原样透传", 409, 409}, {"上游 401 改写 502", 401, 502}, {"上游 429 改写 502", 429, 502}, {"非错误码兜底 502", 200, 502}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { rec := httptest.NewRecorder() c, _ := gin.CreateTestContext(rec) err := fmt.Errorf("获取流量: %w", fakeServiceError{tc.upstream, "TooManyRequests", "slow down"}) respondError(c, err) if rec.Code != tc.wantStatus { t.Fatalf("status = %d, want %d", rec.Code, tc.wantStatus) } }) } } func TestRespondNetworkErrorHidesProxyCredentials(t *testing.T) { gin.SetMode(gin.TestMode) rec := httptest.NewRecorder() c, _ := gin.CreateTestContext(rec) c.Request = httptest.NewRequest(http.MethodGet, "/api/v1/oci-configs/1/instances", nil) proxyErr := &url.Error{ Op: "Dial", URL: "http://proxy-user:proxy-pass@proxy.example.com:8080", Err: errors.New("connect: connection refused"), } err := &url.Error{Op: "Get", URL: "https://iaas.example.com/instances", Err: fmt.Errorf("proxyconnect tcp: %w", proxyErr)} var logs bytes.Buffer oldWriter := log.Writer() log.SetOutput(&logs) t.Cleanup(func() { log.SetOutput(oldWriter) }) respondError(c, err) if rec.Code != http.StatusBadGateway || !strings.Contains(logs.String(), "[NET ") { t.Fatalf("response/log = %d %q / %q", rec.Code, rec.Body.String(), logs.String()) } for _, output := range []string{rec.Body.String(), logs.String()} { if strings.Contains(output, "proxy-user") || strings.Contains(output, "proxy-pass") { t.Fatalf("proxy credentials leaked: %q", output) } } }