From b4bec89ed9ffe099b22fd3442054e76f982ff3c7 Mon Sep 17 00:00:00 2001 From: ila Date: Fri, 21 Aug 2026 23:10:16 +0800 Subject: [PATCH] =?UTF-8?q?test:=20=E8=A6=86=E7=9B=96=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E7=AB=AF=20HTTP=20=E5=87=AD=E6=8D=AE=E8=BE=B9=E7=95=8C=20(#23)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../chorus/service_mysql_integration_test.go | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/admin/app/chorus/service_mysql_integration_test.go b/admin/app/chorus/service_mysql_integration_test.go index fc51d94..c065a8e 100644 --- a/admin/app/chorus/service_mysql_integration_test.go +++ b/admin/app/chorus/service_mysql_integration_test.go @@ -6,6 +6,8 @@ import ( "encoding/json" "errors" "fmt" + "net/http" + "net/http/httptest" "os" "strings" "sync/atomic" @@ -14,6 +16,8 @@ import ( "git.ilapage.cn/OPC/chorus/internal/core/model" platformcrypto "git.ilapage.cn/OPC/chorus/internal/platform/crypto" + "github.com/gin-gonic/gin" + jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" mysqldriver "github.com/go-sql-driver/mysql" "gorm.io/driver/mysql" "gorm.io/gorm" @@ -183,6 +187,92 @@ func TestConnectivityChecksRequireAuthorizationAndReserveCooldownMySQL(t *testin } } +func TestChorusAPIMySQLRejectsUnauthorizedAndRedactsCredentials(t *testing.T) { + db := openAdminTestDB(t) + deleteAdminFixture(t, db, "api-") + suffix := fmt.Sprintf("api-%d", time.Now().UnixNano()) + t.Cleanup(func() { deleteAdminFixture(t, db, suffix) }) + service := newAdminTestService(t, db, Config{}) + gin.SetMode(gin.TestMode) + engine := gin.New() + authentication := func(c *gin.Context) { + if c.GetHeader("Authorization") != "Bearer test" { + c.AbortWithStatus(http.StatusUnauthorized) + return + } + c.Set(jwt.JwtPayloadKey, jwt.MapClaims{jwt.IdentityKey: float64(7)}) + c.Next() + } + authorization := func(c *gin.Context) { + if c.GetHeader("X-Chorus-Role") != "operator" { + c.AbortWithStatus(http.StatusForbidden) + return + } + c.Next() + } + RegisterWithService(engine.Group("/api/v1"), authentication, authorization, service) + + request := httptest.NewRequest(http.MethodGet, "/api/v1/chorus/providers", nil) + response := httptest.NewRecorder() + engine.ServeHTTP(response, request) + if response.Code != http.StatusUnauthorized { + t.Fatalf("unauthorized status = %d, want %d", response.Code, http.StatusUnauthorized) + } + + secret := "synthetic-api-secret-" + suffix + payload, err := json.Marshal(ProviderInput{ + Slug: "api-provider-" + suffix, Name: "API Provider " + suffix, + BaseURL: "https://provider.invalid/v1", AuthType: "bearer", Enabled: true, APIKey: &secret, + }) + if err != nil { + t.Fatal(err) + } + request = httptest.NewRequest(http.MethodPost, "/api/v1/chorus/providers", bytes.NewReader(payload)) + request.Header.Set("Content-Type", "application/json") + request.Header.Set("Authorization", "Bearer test") + request.Header.Set("X-Chorus-Role", "operator") + request.Header.Set("X-Request-Id", "api-create-"+suffix) + response = httptest.NewRecorder() + engine.ServeHTTP(response, request) + if response.Code != http.StatusOK { + t.Fatalf("create provider status = %d, body = %s", response.Code, response.Body.String()) + } + if strings.Contains(response.Body.String(), secret) || strings.Contains(response.Body.String(), "ciphertext") { + t.Fatalf("create provider response contains protected credential data: %s", response.Body.String()) + } + var envelope struct { + Data ProviderView `json:"data"` + } + if err := json.Unmarshal(response.Body.Bytes(), &envelope); err != nil { + t.Fatalf("decode provider response: %v", err) + } + modelView, err := service.CreateProviderModel(context.Background(), 7, "api-model-"+suffix, ProviderModelInput{ + ProviderID: envelope.Data.ID, Name: "API Model " + suffix, ModelID: "api-model-" + suffix, + APIType: model.APIChat, Kind: model.KindText, Capabilities: []model.Capability{model.CapabilityText}, + TimeoutMS: 1000, Weight: 100, Enabled: true, + }) + if err != nil { + t.Fatalf("create API connectivity fixture: %v", err) + } + request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("/api/v1/chorus/provider-models/%d/connectivity-checks", modelView.ID), nil) + request.Header.Set("Authorization", "Bearer test") + request.Header.Set("X-Chorus-Role", "operator") + request.Header.Set("X-Request-Id", "api-connectivity-"+suffix) + response = httptest.NewRecorder() + engine.ServeHTTP(response, request) + if response.Code != http.StatusForbidden || !strings.Contains(response.Body.String(), "connectivity_not_authorized") { + t.Fatalf("disabled connectivity response = %d %s", response.Code, response.Body.String()) + } + request = httptest.NewRequest(http.MethodDelete, fmt.Sprintf("/api/v1/chorus/providers/%d", envelope.Data.ID), nil) + request.Header.Set("Authorization", "Bearer test") + request.Header.Set("X-Chorus-Role", "operator") + response = httptest.NewRecorder() + engine.ServeHTTP(response, request) + if response.Code != http.StatusNotFound { + t.Fatalf("provider DELETE status = %d, want 404", response.Code) + } +} + type blockingProbe struct { calls atomic.Int32 started chan struct{}