openai_fast_policy_forwarding_test.go 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. package middleware
  2. import (
  3. "bytes"
  4. "context"
  5. "encoding/json"
  6. "io"
  7. "net/http"
  8. "net/http/httptest"
  9. "testing"
  10. "time"
  11. "github.com/Wei-Shaw/sub2api/internal/config"
  12. "github.com/Wei-Shaw/sub2api/internal/pkg/tlsfingerprint"
  13. "github.com/Wei-Shaw/sub2api/internal/service"
  14. "github.com/gin-gonic/gin"
  15. "github.com/stretchr/testify/require"
  16. "github.com/tidwall/gjson"
  17. )
  18. func TestAPIKeyAuthForwardsUserScopedOpenAIFastPolicyToUpstream(t *testing.T) {
  19. gin.SetMode(gin.TestMode)
  20. upstreamBodies := make(chan []byte, 2)
  21. upstreamServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  22. body, err := io.ReadAll(r.Body)
  23. if err != nil {
  24. http.Error(w, "read request body", http.StatusInternalServerError)
  25. return
  26. }
  27. upstreamBodies <- body
  28. w.Header().Set("Content-Type", "application/json")
  29. _, _ = w.Write([]byte(`{"id":"resp_test","object":"response","model":"gpt-5","status":"completed","usage":{"input_tokens":1,"output_tokens":1,"total_tokens":2}}`))
  30. }))
  31. defer upstreamServer.Close()
  32. settings := &service.OpenAIFastPolicySettings{
  33. Rules: []service.OpenAIFastPolicyRule{
  34. {
  35. ServiceTier: service.OpenAIFastTierPriority,
  36. Action: service.BetaPolicyActionFilter,
  37. Scope: service.BetaPolicyScopeAll,
  38. },
  39. {
  40. ServiceTier: service.OpenAIFastTierPriority,
  41. Action: service.BetaPolicyActionPass,
  42. Scope: service.BetaPolicyScopeAll,
  43. UserIDs: []int64{42},
  44. },
  45. },
  46. }
  47. settingsJSON, err := json.Marshal(settings)
  48. require.NoError(t, err)
  49. cfg := &config.Config{RunMode: config.RunModeSimple}
  50. cfg.Security.URLAllowlist.Enabled = false
  51. cfg.Security.URLAllowlist.AllowInsecureHTTP = true
  52. settingService := service.NewSettingService(&openAIFastPolicyForwardingSettingRepo{
  53. value: string(settingsJSON),
  54. }, cfg)
  55. gatewayService := service.NewOpenAIGatewayService(
  56. nil, nil, nil, nil, nil, nil, nil, cfg,
  57. nil, nil, nil, nil, nil, &openAIFastPolicyForwardingHTTPUpstream{client: upstreamServer.Client()},
  58. nil, nil, nil, nil, nil, nil, settingService, nil,
  59. )
  60. groupID := int64(101)
  61. group := &service.Group{
  62. ID: groupID,
  63. Name: "openai",
  64. Status: service.StatusActive,
  65. Platform: service.PlatformOpenAI,
  66. Hydrated: true,
  67. }
  68. apiKeys := map[string]*service.APIKey{
  69. "key-user-42": newOpenAIFastPolicyForwardingAPIKey(1, "key-user-42", 42, groupID, group),
  70. "key-user-43": newOpenAIFastPolicyForwardingAPIKey(2, "key-user-43", 43, groupID, group),
  71. }
  72. apiKeyService := service.NewAPIKeyService(&openAIFastPolicyForwardingAPIKeyRepo{apiKeys: apiKeys}, nil, nil, nil, nil, nil, cfg)
  73. account := &service.Account{
  74. ID: 900,
  75. Name: "openai-upstream",
  76. Platform: service.PlatformOpenAI,
  77. Type: service.AccountTypeAPIKey,
  78. Status: service.StatusActive,
  79. Schedulable: true,
  80. Concurrency: 1,
  81. Credentials: map[string]any{
  82. "api_key": "sk-test",
  83. "base_url": upstreamServer.URL,
  84. },
  85. Extra: map[string]any{"use_responses_api": true},
  86. }
  87. router := gin.New()
  88. router.Use(gin.HandlerFunc(NewAPIKeyAuthMiddleware(apiKeyService, nil, cfg)))
  89. router.POST("/v1/responses", func(c *gin.Context) {
  90. body, readErr := io.ReadAll(c.Request.Body)
  91. if readErr != nil {
  92. c.Status(http.StatusBadRequest)
  93. return
  94. }
  95. service.SetOpenAIClientTransport(c, service.OpenAIClientTransportHTTP)
  96. if _, forwardErr := gatewayService.Forward(c.Request.Context(), c, account, body); forwardErr != nil {
  97. c.Status(http.StatusBadGateway)
  98. return
  99. }
  100. c.Status(http.StatusOK)
  101. })
  102. send := func(apiKey string) {
  103. request := httptest.NewRequest(
  104. http.MethodPost,
  105. "/v1/responses",
  106. bytes.NewBufferString(`{"model":"gpt-5","stream":false,"service_tier":"priority","input":"hi"}`),
  107. )
  108. request.Header.Set("Content-Type", "application/json")
  109. request.Header.Set("x-api-key", apiKey)
  110. response := httptest.NewRecorder()
  111. router.ServeHTTP(response, request)
  112. require.Equal(t, http.StatusOK, response.Code)
  113. }
  114. send("key-user-42")
  115. send("key-user-43")
  116. allowedUserBody := <-upstreamBodies
  117. otherUserBody := <-upstreamBodies
  118. require.Equal(t, service.OpenAIFastTierPriority, gjson.GetBytes(allowedUserBody, "service_tier").String())
  119. require.False(t, gjson.GetBytes(otherUserBody, "service_tier").Exists())
  120. }
  121. func newOpenAIFastPolicyForwardingAPIKey(id int64, key string, userID, groupID int64, group *service.Group) *service.APIKey {
  122. return &service.APIKey{
  123. ID: id,
  124. UserID: userID,
  125. Key: key,
  126. Status: service.StatusActive,
  127. GroupID: &groupID,
  128. User: &service.User{
  129. ID: userID,
  130. Role: service.RoleUser,
  131. Status: service.StatusActive,
  132. Balance: 10,
  133. Concurrency: 1,
  134. },
  135. Group: group,
  136. }
  137. }
  138. type openAIFastPolicyForwardingAPIKeyRepo struct {
  139. service.APIKeyRepository
  140. apiKeys map[string]*service.APIKey
  141. }
  142. func (r *openAIFastPolicyForwardingAPIKeyRepo) GetByKeyForAuth(_ context.Context, key string) (*service.APIKey, error) {
  143. apiKey, ok := r.apiKeys[key]
  144. if !ok {
  145. return nil, service.ErrAPIKeyNotFound
  146. }
  147. clone := *apiKey
  148. return &clone, nil
  149. }
  150. func (r *openAIFastPolicyForwardingAPIKeyRepo) UpdateLastUsed(context.Context, int64, time.Time) error {
  151. return nil
  152. }
  153. type openAIFastPolicyForwardingSettingRepo struct {
  154. service.SettingRepository
  155. value string
  156. }
  157. func (r *openAIFastPolicyForwardingSettingRepo) GetValue(context.Context, string) (string, error) {
  158. return r.value, nil
  159. }
  160. type openAIFastPolicyForwardingHTTPUpstream struct {
  161. client *http.Client
  162. }
  163. func (u *openAIFastPolicyForwardingHTTPUpstream) Do(req *http.Request, _ string, _ int64, _ int) (*http.Response, error) {
  164. return u.client.Do(req)
  165. }
  166. func (u *openAIFastPolicyForwardingHTTPUpstream) DoWithTLS(req *http.Request, proxyURL string, accountID int64, accountConcurrency int, _ *tlsfingerprint.Profile) (*http.Response, error) {
  167. return u.Do(req, proxyURL, accountID, accountConcurrency)
  168. }