openai_responses_input_tokens_test.go 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  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 TestForwardResponsesInputTokensCustomRelayUsesLocalEstimate(t *testing.T) {
  15. gin.SetMode(gin.TestMode)
  16. recorder := httptest.NewRecorder()
  17. c, _ := gin.CreateTestContext(recorder)
  18. c.Request = httptest.NewRequest(http.MethodPost, "/v1/responses/input_tokens", nil)
  19. upstream := &httpUpstreamRecorder{}
  20. svc := &OpenAIGatewayService{
  21. cfg: &config.Config{Security: config.SecurityConfig{URLAllowlist: config.URLAllowlistConfig{Enabled: false}}},
  22. httpUpstream: upstream,
  23. }
  24. account := &Account{
  25. ID: 159,
  26. Platform: PlatformOpenAI,
  27. Type: AccountTypeAPIKey,
  28. Concurrency: 1,
  29. Credentials: map[string]any{
  30. "api_key": "relay-key",
  31. "base_url": "https://relay.example/v1",
  32. },
  33. }
  34. body := []byte(`{"model":"gpt-5.4","instructions":"Be concise.","input":"hello world","tools":[{"type":"function","name":"lookup","description":"Look up a value","parameters":{"type":"object"}}]}`)
  35. err := svc.ForwardResponsesInputTokens(context.Background(), c, account, body)
  36. require.NoError(t, err)
  37. require.Equal(t, http.StatusOK, recorder.Code)
  38. require.Equal(t, "response.input_tokens", gjson.Get(recorder.Body.String(), "object").String())
  39. require.Positive(t, gjson.Get(recorder.Body.String(), "input_tokens").Int())
  40. require.Nil(t, upstream.lastReq, "custom relay must not receive /v1/responses/input_tokens")
  41. }
  42. func TestForwardResponsesInputTokensGrokOAuthUsesLocalEstimate(t *testing.T) {
  43. gin.SetMode(gin.TestMode)
  44. recorder := httptest.NewRecorder()
  45. c, _ := gin.CreateTestContext(recorder)
  46. c.Request = httptest.NewRequest(http.MethodPost, "/v1/responses/input_tokens", nil)
  47. upstream := &httpUpstreamRecorder{}
  48. svc := &OpenAIGatewayService{httpUpstream: upstream}
  49. account := &Account{ID: 160, Platform: PlatformGrok, Type: AccountTypeOAuth}
  50. body := []byte(`{"model":"grok-4.1","input":"hello world"}`)
  51. err := svc.ForwardResponsesInputTokens(context.Background(), c, account, body)
  52. require.NoError(t, err)
  53. require.Equal(t, http.StatusOK, recorder.Code)
  54. require.Equal(t, "response.input_tokens", gjson.Get(recorder.Body.String(), "object").String())
  55. require.Positive(t, gjson.Get(recorder.Body.String(), "input_tokens").Int())
  56. require.Nil(t, upstream.lastReq)
  57. }
  58. func TestForwardResponsesInputTokensUpstream404FallsBackLocally(t *testing.T) {
  59. gin.SetMode(gin.TestMode)
  60. recorder := httptest.NewRecorder()
  61. c, _ := gin.CreateTestContext(recorder)
  62. c.Request = httptest.NewRequest(http.MethodPost, "/v1/responses/input_tokens", nil)
  63. upstream := &httpUpstreamRecorder{resp: &http.Response{
  64. StatusCode: http.StatusNotFound,
  65. Header: make(http.Header),
  66. Body: io.NopCloser(strings.NewReader(`{"error":{"type":"invalid_request_error","message":"Invalid URL (POST /v1/responses/input_tokens)"}}`)),
  67. }}
  68. svc := &OpenAIGatewayService{
  69. cfg: &config.Config{Security: config.SecurityConfig{URLAllowlist: config.URLAllowlistConfig{Enabled: false}}},
  70. httpUpstream: upstream,
  71. }
  72. account := &Account{
  73. ID: 171,
  74. Platform: PlatformOpenAI,
  75. Type: AccountTypeAPIKey,
  76. Concurrency: 1,
  77. Credentials: map[string]any{
  78. "api_key": "official-key",
  79. "base_url": "https://api.openai.com/v1",
  80. },
  81. }
  82. body := []byte(`{"model":"gpt-5.4","instructions":"Be concise.","input":"hello world"}`)
  83. err := svc.ForwardResponsesInputTokens(context.Background(), c, account, body)
  84. require.NoError(t, err)
  85. require.Equal(t, http.StatusOK, recorder.Code)
  86. require.Equal(t, "response.input_tokens", gjson.Get(recorder.Body.String(), "object").String())
  87. require.Positive(t, gjson.Get(recorder.Body.String(), "input_tokens").Int())
  88. require.NotNil(t, upstream.lastReq)
  89. }