gateway_models_pinned_test.go 3.8 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
  1. package routes
  2. import (
  3. "context"
  4. "encoding/json"
  5. "io"
  6. "net/http"
  7. "net/http/httptest"
  8. "strings"
  9. "sync/atomic"
  10. "testing"
  11. "github.com/Wei-Shaw/sub2api/internal/config"
  12. "github.com/Wei-Shaw/sub2api/internal/handler"
  13. servermiddleware "github.com/Wei-Shaw/sub2api/internal/server/middleware"
  14. "github.com/Wei-Shaw/sub2api/internal/service"
  15. "github.com/gin-gonic/gin"
  16. "github.com/stretchr/testify/require"
  17. )
  18. type pinnedModelsRoutesRepository struct {
  19. service.AccountRepository
  20. account service.Account
  21. }
  22. func (r *pinnedModelsRoutesRepository) ListByGroup(context.Context, int64) ([]service.Account, error) {
  23. return []service.Account{r.account}, nil
  24. }
  25. type pinnedModelsRoutesUpstream struct {
  26. service.HTTPUpstream
  27. ordinaryCalls atomic.Int32
  28. codexCalls atomic.Int32
  29. }
  30. func (u *pinnedModelsRoutesUpstream) Do(req *http.Request, _ string, _ int64, _ int) (*http.Response, error) {
  31. body := `{"data":[{"id":"ordinary-upstream-model"}]}`
  32. if req.URL.Query().Has("client_version") {
  33. u.codexCalls.Add(1)
  34. body = `{"models":[{"slug":"gpt-5.5"}]}`
  35. } else {
  36. u.ordinaryCalls.Add(1)
  37. }
  38. return &http.Response{StatusCode: 200, Header: make(http.Header), Body: io.NopCloser(strings.NewReader(body))}, nil
  39. }
  40. func TestGatewayRoutesPinnedModelsDispatchesOrdinaryAndCodexRequests(t *testing.T) {
  41. gin.SetMode(gin.TestMode)
  42. repo := &pinnedModelsRoutesRepository{account: service.Account{
  43. ID: 7, Platform: service.PlatformOpenAI, Type: service.AccountTypeAPIKey,
  44. Status: service.StatusActive, Schedulable: true,
  45. Credentials: map[string]any{"api_key": "test-models-key", "base_url": "https://models.example/v1"},
  46. }}
  47. upstream := &pinnedModelsRoutesUpstream{}
  48. cfg := &config.Config{RunMode: config.RunModeSimple}
  49. s := service.NewOpenAIGatewayService(repo, nil, nil, nil, nil, nil, nil, cfg,
  50. nil, nil, nil, nil, nil, upstream, nil, nil, nil, nil, nil, nil, nil, nil)
  51. h := &handler.Handlers{
  52. Gateway: handler.NewGatewayHandler(nil, s, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, cfg, nil),
  53. OpenAIGateway: handler.NewOpenAIGatewayHandler(s, nil, nil, nil, nil, nil, nil, nil, cfg),
  54. AsyncImage: handler.NewAsyncImageHandler(nil, nil),
  55. }
  56. group := &service.Group{ID: 1, Platform: service.PlatformOpenAI,
  57. CodexModelsManifestConfig: service.GroupCodexModelsManifestConfig{Enabled: true, AccountIDs: []int64{7}}}
  58. router := gin.New()
  59. RegisterGatewayRoutes(router, h, servermiddleware.APIKeyAuthMiddleware(func(c *gin.Context) {
  60. c.Set(string(servermiddleware.ContextKeyAPIKey), &service.APIKey{GroupID: &group.ID, Group: group})
  61. c.Next()
  62. }), nil, nil, nil, nil, nil, cfg)
  63. for _, path := range []string{"/v1/models", "/models", "/v1/models?client_version=", "/models?client_version="} {
  64. w := httptest.NewRecorder()
  65. router.ServeHTTP(w, httptest.NewRequest(http.MethodGet, path, nil))
  66. require.Equal(t, http.StatusOK, w.Code, "%s: %s", path, w.Body.String())
  67. var response struct {
  68. Object string `json:"object"`
  69. Data []struct {
  70. ID string `json:"id"`
  71. } `json:"data"`
  72. }
  73. require.NoError(t, json.Unmarshal(w.Body.Bytes(), &response))
  74. require.Equal(t, "list", response.Object)
  75. require.Len(t, response.Data, 1)
  76. require.Equal(t, "ordinary-upstream-model", response.Data[0].ID)
  77. }
  78. for _, path := range []string{"/v1/models?client_version=" + service.CodexCanonicalClientVersion(), "/models?client_version=" + service.CodexCanonicalClientVersion(), "/backend-api/codex/models"} {
  79. w := httptest.NewRecorder()
  80. router.ServeHTTP(w, httptest.NewRequest(http.MethodGet, path, nil))
  81. require.Equal(t, http.StatusOK, w.Code, "%s: %s", path, w.Body.String())
  82. require.Contains(t, w.Body.String(), `"slug":"gpt-5.5"`)
  83. require.NotContains(t, w.Body.String(), `"data"`)
  84. }
  85. require.EqualValues(t, 1, upstream.ordinaryCalls.Load())
  86. require.EqualValues(t, 1, upstream.codexCalls.Load())
  87. }