account_test_service_openai_image_test.go 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. package service
  2. import (
  3. "context"
  4. "io"
  5. "net/http"
  6. "net/http/httptest"
  7. "strings"
  8. "testing"
  9. "github.com/Wei-Shaw/sub2api/internal/config"
  10. "github.com/gin-gonic/gin"
  11. "github.com/stretchr/testify/require"
  12. "github.com/tidwall/gjson"
  13. )
  14. func TestAccountTestService_OpenAIImageOAuthHandlesOutputItemDoneFallback(t *testing.T) {
  15. t.Setenv("SUB2API_IMAGES_MAIN_MODEL", "gpt-5.6-sol")
  16. gin.SetMode(gin.TestMode)
  17. rec := httptest.NewRecorder()
  18. c, _ := gin.CreateTestContext(rec)
  19. c.Request = httptest.NewRequest(http.MethodPost, "/api/v1/admin/accounts/1/test", nil)
  20. upstream := &httpUpstreamRecorder{
  21. resp: &http.Response{
  22. StatusCode: http.StatusOK,
  23. Header: http.Header{
  24. "Content-Type": []string{"text/event-stream"},
  25. },
  26. Body: io.NopCloser(strings.NewReader(
  27. "data: {\"type\":\"response.output_item.done\",\"item\":{\"id\":\"ig_123\",\"type\":\"image_generation_call\",\"result\":\"aGVsbG8=\",\"revised_prompt\":\"draw a cat\",\"output_format\":\"png\"}}\n\n" +
  28. "data: {\"type\":\"response.completed\",\"response\":{\"created_at\":1710000006,\"tool_usage\":{\"image_gen\":{\"images\":1}},\"output\":[]}}\n\n" +
  29. "data: [DONE]\n\n",
  30. )),
  31. },
  32. }
  33. svc := &AccountTestService{httpUpstream: upstream}
  34. account := &Account{
  35. ID: 53,
  36. Name: "openai-oauth",
  37. Platform: PlatformOpenAI,
  38. Type: AccountTypeOAuth,
  39. Credentials: map[string]any{
  40. "access_token": "token-123",
  41. },
  42. }
  43. err := svc.testOpenAIImageOAuth(c, context.Background(), account, "gpt-image-2.5-flare", "draw a cat")
  44. require.NoError(t, err)
  45. require.NotNil(t, upstream.lastReq)
  46. require.Equal(t, "gpt-5.6-sol", gjson.GetBytes(upstream.lastBody, "model").String())
  47. require.Equal(t, "gpt-image-2.5-flare", gjson.GetBytes(upstream.lastBody, "tools.0.model").String())
  48. require.Equal(t, HTTPUpstreamProfileOpenAI, HTTPUpstreamProfileFromContext(upstream.lastReq.Context()))
  49. require.Contains(t, rec.Body.String(), "Calling Codex /responses image tool")
  50. require.Contains(t, rec.Body.String(), "data:image/png;base64,aGVsbG8=")
  51. require.Contains(t, rec.Body.String(), "\"success\":true")
  52. }
  53. func TestAccountTestService_OpenAIImageAPIKeyUsesConfiguredV1BaseURL(t *testing.T) {
  54. gin.SetMode(gin.TestMode)
  55. rec := httptest.NewRecorder()
  56. c, _ := gin.CreateTestContext(rec)
  57. c.Request = httptest.NewRequest(http.MethodPost, "/api/v1/admin/accounts/1/test", nil)
  58. upstream := &httpUpstreamRecorder{
  59. resp: &http.Response{
  60. StatusCode: http.StatusOK,
  61. Header: http.Header{
  62. "Content-Type": []string{"application/json"},
  63. },
  64. Body: io.NopCloser(strings.NewReader(`{"data":[{"b64_json":"aGVsbG8=","revised_prompt":"draw a cat"}]}`)),
  65. },
  66. }
  67. svc := &AccountTestService{
  68. httpUpstream: upstream,
  69. cfg: &config.Config{},
  70. }
  71. account := &Account{
  72. ID: 54,
  73. Name: "openai-apikey",
  74. Platform: PlatformOpenAI,
  75. Type: AccountTypeAPIKey,
  76. Credentials: map[string]any{
  77. "api_key": "test-api-key",
  78. "base_url": "https://image-upstream.example/v1",
  79. },
  80. }
  81. err := svc.testOpenAIImageAPIKey(c, context.Background(), account, "gpt-image-2", "draw a cat")
  82. require.NoError(t, err)
  83. require.NotNil(t, upstream.lastReq)
  84. require.Equal(t, HTTPUpstreamProfileOpenAI, HTTPUpstreamProfileFromContext(upstream.lastReq.Context()))
  85. require.Equal(t, "https://image-upstream.example/v1/images/generations", upstream.lastReq.URL.String())
  86. require.Equal(t, "Bearer test-api-key", upstream.lastReq.Header.Get("Authorization"))
  87. require.Contains(t, rec.Body.String(), "data:image/png;base64,aGVsbG8=")
  88. require.Contains(t, rec.Body.String(), "\"success\":true")
  89. }
  90. func TestAccountTestService_OpenAIImageOAuthSurfacesSSEError(t *testing.T) {
  91. gin.SetMode(gin.TestMode)
  92. rec := httptest.NewRecorder()
  93. c, _ := gin.CreateTestContext(rec)
  94. c.Request = httptest.NewRequest(http.MethodPost, "/api/v1/admin/accounts/1/test", nil)
  95. svc := &AccountTestService{httpUpstream: &httpUpstreamRecorder{resp: &http.Response{
  96. StatusCode: http.StatusOK, Header: http.Header{"Content-Type": []string{"text/event-stream"}},
  97. Body: io.NopCloser(strings.NewReader("data: {\"type\":\"error\",\"error\":{\"type\":\"invalid_request_error\",\"message\":\"The selected image model is unavailable\"}}\n\n")),
  98. }}}
  99. account := &Account{ID: 1, Platform: PlatformOpenAI, Type: AccountTypeOAuth, Credentials: map[string]any{"access_token": "test"}}
  100. err := svc.testOpenAIImageOAuth(c, context.Background(), account, "gpt-image-2.5-flare", "draw a cup")
  101. require.ErrorContains(t, err, "The selected image model is unavailable")
  102. require.NotContains(t, rec.Body.String(), "No images returned")
  103. }