88 lines
2.7 KiB
Go
88 lines
2.7 KiB
Go
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)
|
|
}
|
|
}
|
|
}
|