openai_grok_image_intent_gate_test.go 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. package handler
  2. import (
  3. "net/http"
  4. "net/http/httptest"
  5. "strings"
  6. "testing"
  7. "github.com/Wei-Shaw/sub2api/internal/config"
  8. middleware2 "github.com/Wei-Shaw/sub2api/internal/server/middleware"
  9. "github.com/Wei-Shaw/sub2api/internal/service"
  10. "github.com/gin-gonic/gin"
  11. "github.com/stretchr/testify/require"
  12. )
  13. func TestOpenAIGatewayHandlerResponses_GrokPassiveImageToolDeclarationBypassesPermissionGate(t *testing.T) {
  14. body := `{"model":"grok-4.5","tools":[{"type":"namespace","name":"image_gen","tools":[{"type":"function","name":"imagegen"}]}],"tool_choice":"auto","input":"write code"}`
  15. rec := runOpenAIResponsesImagePermissionGateTest(t, service.PlatformGrok, body)
  16. require.NotEqual(t, http.StatusForbidden, rec.Code)
  17. require.NotContains(t, rec.Body.String(), service.ImageGenerationPermissionMessage())
  18. }
  19. func TestOpenAIGatewayHandlerResponses_GrokResponsesLiteImageToolDeclarationBypassesPermissionGate(t *testing.T) {
  20. 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"}]}`
  21. rec := runOpenAIResponsesImagePermissionGateTest(t, service.PlatformGrok, body)
  22. require.NotEqual(t, http.StatusForbidden, rec.Code)
  23. require.NotContains(t, rec.Body.String(), service.ImageGenerationPermissionMessage())
  24. }
  25. func TestOpenAIGatewayHandlerResponses_ImagePermissionHardSignalsStillRejected(t *testing.T) {
  26. tests := []struct {
  27. name string
  28. platform string
  29. body string
  30. }{
  31. {
  32. name: "Grok native image_generation declaration",
  33. platform: service.PlatformGrok,
  34. body: `{"model":"grok-4.5","tools":[{"type":"image_generation"}],"input":"draw"}`,
  35. },
  36. {
  37. name: "Grok explicit image_gen tool choice",
  38. platform: service.PlatformGrok,
  39. body: `{"model":"grok-4.5","tools":[{"type":"namespace","name":"image_gen"}],"tool_choice":{"type":"namespace","name":"image_gen"},"input":"draw"}`,
  40. },
  41. {
  42. name: "OpenAI native image_generation tool",
  43. platform: service.PlatformOpenAI,
  44. body: `{"model":"gpt-5.5","tools":[{"type":"image_generation","model":"gpt-image-2"}],"input":"draw a cat"}`,
  45. },
  46. {
  47. name: "OpenAI image model",
  48. platform: service.PlatformOpenAI,
  49. body: `{"model":"gpt-image-2","input":"draw a cat"}`,
  50. },
  51. }
  52. for _, tt := range tests {
  53. t.Run(tt.name, func(t *testing.T) {
  54. rec := runOpenAIResponsesImagePermissionGateTest(t, tt.platform, tt.body)
  55. require.Equal(t, http.StatusForbidden, rec.Code)
  56. require.Contains(t, rec.Body.String(), service.ImageGenerationPermissionMessage())
  57. })
  58. }
  59. }
  60. func TestOpenAIGatewayHandlerResponses_PassiveNamespaceDoesNotTrigger403(t *testing.T) {
  61. passiveNamespace := `{"model":"gpt-5.5","tools":[{"type":"namespace","name":"image_gen","tools":[{"type":"function","name":"imagegen"}]}],"tool_choice":"auto","input":"write code"}`
  62. rec := runOpenAIResponsesImagePermissionGateTest(t, service.PlatformOpenAI, passiveNamespace)
  63. require.NotEqual(t, http.StatusForbidden, rec.Code,
  64. "passive image_gen namespace with tool_choice=auto should not trigger 403 (#4447)")
  65. }
  66. func runOpenAIResponsesImagePermissionGateTest(t *testing.T, platform string, body string) *httptest.ResponseRecorder {
  67. t.Helper()
  68. gin.SetMode(gin.TestMode)
  69. rec := httptest.NewRecorder()
  70. c, _ := gin.CreateTestContext(rec)
  71. c.Request = httptest.NewRequest(http.MethodPost, "/v1/responses", strings.NewReader(body))
  72. c.Request.Header.Set("Content-Type", "application/json")
  73. groupID := int64(6301)
  74. userID := int64(6302)
  75. c.Set(string(middleware2.ContextKeyAPIKey), &service.APIKey{
  76. ID: 6303,
  77. GroupID: &groupID,
  78. Group: &service.Group{
  79. ID: groupID,
  80. Platform: platform,
  81. AllowImageGeneration: false,
  82. },
  83. User: &service.User{ID: userID, Status: service.StatusActive},
  84. })
  85. c.Set(string(middleware2.ContextKeyUser), middleware2.AuthSubject{UserID: userID, Concurrency: 1})
  86. h := &OpenAIGatewayHandler{
  87. gatewayService: &service.OpenAIGatewayService{},
  88. billingCacheService: service.NewBillingCacheService(nil, nil, nil, nil, nil, nil, &config.Config{RunMode: config.RunModeSimple}, nil),
  89. apiKeyService: &service.APIKeyService{},
  90. concurrencyHelper: &ConcurrencyHelper{concurrencyService: service.NewConcurrencyService(
  91. &helperConcurrencyCacheStub{userSeq: []bool{true}},
  92. )},
  93. cfg: &config.Config{},
  94. imageLimiter: &imageConcurrencyLimiter{},
  95. }
  96. h.Responses(c)
  97. return rec
  98. }