openai_service_tier_validation_test.go 3.8 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
  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. // 非法 service_tier 必须在两个 OpenAI 端点(/v1/responses、/v1/chat/completions)
  14. // 上以 OpenAI 兼容错误结构返回 HTTP 400。这些用例在 handler 的 service_tier
  15. // 校验处短路,不会进入账号选择/重试。
  16. //
  17. // 合法值(fast/priority/flex/auto/default/scale/ultrafast)与省略/null 的接受语义由
  18. // service 层纯校验函数 TestValidateOpenAIServiceTierField 覆盖,避免 handler
  19. // 测试走入真实账号选择/重试路径。
  20. func newServiceTierHandlerTest(t *testing.T) *OpenAIGatewayHandler {
  21. t.Helper()
  22. return &OpenAIGatewayHandler{
  23. gatewayService: &service.OpenAIGatewayService{},
  24. billingCacheService: service.NewBillingCacheService(nil, nil, nil, nil, nil, nil, &config.Config{RunMode: config.RunModeSimple}, nil),
  25. apiKeyService: &service.APIKeyService{},
  26. concurrencyHelper: &ConcurrencyHelper{concurrencyService: service.NewConcurrencyService(
  27. &helperConcurrencyCacheStub{userSeq: []bool{true}},
  28. )},
  29. cfg: &config.Config{},
  30. imageLimiter: &imageConcurrencyLimiter{},
  31. }
  32. }
  33. func runOpenAIHandlerServiceTierTest(t *testing.T, path, body string, handler func(h *OpenAIGatewayHandler, c *gin.Context)) *httptest.ResponseRecorder {
  34. t.Helper()
  35. gin.SetMode(gin.TestMode)
  36. rec := httptest.NewRecorder()
  37. c, _ := gin.CreateTestContext(rec)
  38. c.Request = httptest.NewRequest(http.MethodPost, path, strings.NewReader(body))
  39. c.Request.Header.Set("Content-Type", "application/json")
  40. groupID := int64(6401)
  41. userID := int64(6402)
  42. c.Set(string(middleware2.ContextKeyAPIKey), &service.APIKey{
  43. ID: 6403,
  44. GroupID: &groupID,
  45. Group: &service.Group{
  46. ID: groupID,
  47. Platform: service.PlatformOpenAI,
  48. },
  49. User: &service.User{ID: userID, Status: service.StatusActive},
  50. })
  51. c.Set(string(middleware2.ContextKeyUser), middleware2.AuthSubject{UserID: userID, Concurrency: 1})
  52. handler(newServiceTierHandlerTest(t), c)
  53. return rec
  54. }
  55. func TestOpenAIGatewayHandlerResponses_InvalidServiceTierRejected400(t *testing.T) {
  56. for _, body := range []string{
  57. `{"model":"gpt-5.5","input":"hi","service_tier":"turbo"}`,
  58. `{"model":"gpt-5.5","input":"hi","service_tier":"SPEED"}`,
  59. `{"model":"gpt-5.5","input":"hi","service_tier":""}`,
  60. `{"model":"gpt-5.5","input":"hi","service_tier":123}`,
  61. `{"model":"gpt-5.5","input":"hi","service_tier":{}}`,
  62. } {
  63. rec := runOpenAIHandlerServiceTierTest(t, "/v1/responses", body, func(h *OpenAIGatewayHandler, c *gin.Context) {
  64. h.Responses(c)
  65. })
  66. require.Equal(t, http.StatusBadRequest, rec.Code, "body=%s", body)
  67. require.Contains(t, rec.Body.String(), "invalid_request_error", "body=%s", body)
  68. require.Contains(t, rec.Body.String(), "invalid service_tier", "body=%s", body)
  69. }
  70. }
  71. func TestOpenAIGatewayHandlerChatCompletions_InvalidServiceTierRejected400(t *testing.T) {
  72. for _, body := range []string{
  73. `{"model":"gpt-5.5","messages":[{"role":"user","content":"hi"}],"service_tier":"turbo"}`,
  74. `{"model":"gpt-5.5","messages":[{"role":"user","content":"hi"}],"service_tier":"ultra"}`,
  75. `{"model":"gpt-5.5","messages":[{"role":"user","content":"hi"}],"service_tier":""}`,
  76. `{"model":"gpt-5.5","messages":[{"role":"user","content":"hi"}],"service_tier":["priority"]}`,
  77. } {
  78. rec := runOpenAIHandlerServiceTierTest(t, "/v1/chat/completions", body, func(h *OpenAIGatewayHandler, c *gin.Context) {
  79. h.ChatCompletions(c)
  80. })
  81. require.Equal(t, http.StatusBadRequest, rec.Code, "body=%s", body)
  82. require.Contains(t, rec.Body.String(), "invalid_request_error", "body=%s", body)
  83. require.Contains(t, rec.Body.String(), "invalid service_tier", "body=%s", body)
  84. }
  85. }