Release / update-version (push) Has been cancelled
Release / build-frontend (push) Has been cancelled
Release / release (push) Has been cancelled
Release / sync-version-file (push) Has been cancelled
CI / shell (push) Canceled after 0s
CI / test (push) Canceled after 0s
CI / frontend (push) Canceled after 0s
CI / golangci-lint (push) Canceled after 0s
Security Scan / backend-security (push) Canceled after 0s
Security Scan / frontend-security (push) Canceled after 0s
114 lines
4.4 KiB
Go
114 lines
4.4 KiB
Go
package handler
|
|
|
|
import (
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/Wei-Shaw/sub2api/internal/config"
|
|
middleware2 "github.com/Wei-Shaw/sub2api/internal/server/middleware"
|
|
"github.com/Wei-Shaw/sub2api/internal/service"
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestOpenAIGatewayHandlerResponses_GrokPassiveImageToolDeclarationBypassesPermissionGate(t *testing.T) {
|
|
body := `{"model":"grok-4.5","tools":[{"type":"namespace","name":"image_gen","tools":[{"type":"function","name":"imagegen"}]}],"tool_choice":"auto","input":"write code"}`
|
|
rec := runOpenAIResponsesImagePermissionGateTest(t, service.PlatformGrok, body)
|
|
|
|
require.NotEqual(t, http.StatusForbidden, rec.Code)
|
|
require.NotContains(t, rec.Body.String(), service.ImageGenerationPermissionMessage())
|
|
}
|
|
|
|
func TestOpenAIGatewayHandlerResponses_GrokResponsesLiteImageToolDeclarationBypassesPermissionGate(t *testing.T) {
|
|
body := `{"model":"grok-4.5","tool_choice":"auto","input":[{"type":"additional_tools","tools":[{"type":"namespace","name":"image_gen","tools":[{"type":"function","name":"imagegen"}]}]},{"type":"message","role":"user","content":"write code"}]}`
|
|
rec := runOpenAIResponsesImagePermissionGateTest(t, service.PlatformGrok, body)
|
|
|
|
require.NotEqual(t, http.StatusForbidden, rec.Code)
|
|
require.NotContains(t, rec.Body.String(), service.ImageGenerationPermissionMessage())
|
|
}
|
|
|
|
func TestOpenAIGatewayHandlerResponses_ImagePermissionHardSignalsStillRejected(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
platform string
|
|
body string
|
|
}{
|
|
{
|
|
name: "Grok native image_generation declaration",
|
|
platform: service.PlatformGrok,
|
|
body: `{"model":"grok-4.5","tools":[{"type":"image_generation"}],"input":"draw"}`,
|
|
},
|
|
{
|
|
name: "Grok explicit image_gen tool choice",
|
|
platform: service.PlatformGrok,
|
|
body: `{"model":"grok-4.5","tools":[{"type":"namespace","name":"image_gen"}],"tool_choice":{"type":"namespace","name":"image_gen"},"input":"draw"}`,
|
|
},
|
|
{
|
|
name: "OpenAI native image_generation tool",
|
|
platform: service.PlatformOpenAI,
|
|
body: `{"model":"gpt-5.5","tools":[{"type":"image_generation","model":"gpt-image-2"}],"input":"draw a cat"}`,
|
|
},
|
|
{
|
|
name: "OpenAI image model",
|
|
platform: service.PlatformOpenAI,
|
|
body: `{"model":"gpt-image-2","input":"draw a cat"}`,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
rec := runOpenAIResponsesImagePermissionGateTest(t, tt.platform, tt.body)
|
|
|
|
require.Equal(t, http.StatusForbidden, rec.Code)
|
|
require.Contains(t, rec.Body.String(), service.ImageGenerationPermissionMessage())
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestOpenAIGatewayHandlerResponses_PassiveNamespaceDoesNotTrigger403(t *testing.T) {
|
|
passiveNamespace := `{"model":"gpt-5.5","tools":[{"type":"namespace","name":"image_gen","tools":[{"type":"function","name":"imagegen"}]}],"tool_choice":"auto","input":"write code"}`
|
|
rec := runOpenAIResponsesImagePermissionGateTest(t, service.PlatformOpenAI, passiveNamespace)
|
|
|
|
require.NotEqual(t, http.StatusForbidden, rec.Code,
|
|
"passive image_gen namespace with tool_choice=auto should not trigger 403 (#4447)")
|
|
}
|
|
|
|
func runOpenAIResponsesImagePermissionGateTest(t *testing.T, platform string, body string) *httptest.ResponseRecorder {
|
|
t.Helper()
|
|
gin.SetMode(gin.TestMode)
|
|
rec := httptest.NewRecorder()
|
|
c, _ := gin.CreateTestContext(rec)
|
|
c.Request = httptest.NewRequest(http.MethodPost, "/v1/responses", strings.NewReader(body))
|
|
c.Request.Header.Set("Content-Type", "application/json")
|
|
|
|
groupID := int64(6301)
|
|
userID := int64(6302)
|
|
c.Set(string(middleware2.ContextKeyAPIKey), &service.APIKey{
|
|
ID: 6303,
|
|
GroupID: &groupID,
|
|
Group: &service.Group{
|
|
ID: groupID,
|
|
Platform: platform,
|
|
AllowImageGeneration: false,
|
|
},
|
|
User: &service.User{ID: userID, Status: service.StatusActive},
|
|
})
|
|
c.Set(string(middleware2.ContextKeyUser), middleware2.AuthSubject{UserID: userID, Concurrency: 1})
|
|
|
|
h := &OpenAIGatewayHandler{
|
|
gatewayService: &service.OpenAIGatewayService{},
|
|
billingCacheService: service.NewBillingCacheService(nil, nil, nil, nil, nil, nil, &config.Config{RunMode: config.RunModeSimple}, nil),
|
|
apiKeyService: &service.APIKeyService{},
|
|
concurrencyHelper: &ConcurrencyHelper{concurrencyService: service.NewConcurrencyService(
|
|
&helperConcurrencyCacheStub{userSeq: []bool{true}},
|
|
)},
|
|
cfg: &config.Config{},
|
|
imageLimiter: &imageConcurrencyLimiter{},
|
|
}
|
|
|
|
h.Responses(c)
|
|
return rec
|
|
}
|