test: 覆盖管理端 HTTP 凭据边界 (#23)

This commit is contained in:
ila
2026-08-21 23:10:16 +08:00
parent 64f917b9c5
commit b4bec89ed9
@@ -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{}