package routes import ( "net/http" "net/http/httptest" "strings" "testing" "github.com/Wei-Shaw/sub2api/internal/config" "github.com/Wei-Shaw/sub2api/internal/handler" servermiddleware "github.com/Wei-Shaw/sub2api/internal/server/middleware" "github.com/Wei-Shaw/sub2api/internal/service" "github.com/gin-gonic/gin" "github.com/stretchr/testify/require" ) func newGatewayRoutesTestRouter(platform ...string) *gin.Engine { gin.SetMode(gin.TestMode) router := gin.New() groupPlatform := service.PlatformOpenAI if len(platform) > 0 && platform[0] != "" { groupPlatform = platform[0] } RegisterGatewayRoutes( router, &handler.Handlers{ Gateway: &handler.GatewayHandler{}, OpenAIGateway: &handler.OpenAIGatewayHandler{}, }, servermiddleware.APIKeyAuthMiddleware(func(c *gin.Context) { groupID := int64(1) c.Set(string(servermiddleware.ContextKeyAPIKey), &service.APIKey{ GroupID: &groupID, Group: &service.Group{Platform: groupPlatform}, }) c.Next() }), nil, nil, nil, nil, &config.Config{}, ) return router } func TestGatewayRoutesOpenAIResponsesCompactPathIsRegistered(t *testing.T) { router := newGatewayRoutesTestRouter() for _, path := range []string{ "/v1/responses/compact", "/responses/compact", "/backend-api/codex/responses", "/backend-api/codex/responses/compact", } { req := httptest.NewRequest(http.MethodPost, path, strings.NewReader(`{"model":"gpt-5"}`)) req.Header.Set("Content-Type", "application/json") w := httptest.NewRecorder() router.ServeHTTP(w, req) require.NotEqual(t, http.StatusNotFound, w.Code, "path=%s should hit OpenAI responses handler", path) } } func TestGatewayRoutesOpenAIAlphaSearchPathsAreRegistered(t *testing.T) { router := newGatewayRoutesTestRouter() registered := make(map[string]bool) for _, route := range router.Routes() { if route.Method == http.MethodPost { registered[route.Path] = true } } for _, path := range []string{ "/v1/alpha/search", "/alpha/search", "/backend-api/codex/alpha/search", } { require.True(t, registered[path], "POST %s should be registered", path) } } func TestGatewayRoutesAlphaSearchRejectsNonOpenAIGroup(t *testing.T) { router := newGatewayRoutesTestRouter(service.PlatformGrok) req := httptest.NewRequest(http.MethodPost, "/v1/alpha/search", strings.NewReader(`{"model":"gpt-5.6-sol"}`)) req.Header.Set("Content-Type", "application/json") w := httptest.NewRecorder() router.ServeHTTP(w, req) require.Equal(t, http.StatusNotFound, w.Code) require.Contains(t, w.Body.String(), "only available for OpenAI groups") } func TestGatewayRoutesOpenAIImagesPathsAreRegistered(t *testing.T) { router := newGatewayRoutesTestRouter() for _, path := range []string{ "/v1/images/generations", "/v1/images/edits", "/images/generations", "/images/edits", } { req := httptest.NewRequest(http.MethodPost, path, strings.NewReader(`{"model":"gpt-image-2","prompt":"draw a cat"}`)) req.Header.Set("Content-Type", "application/json") w := httptest.NewRecorder() router.ServeHTTP(w, req) require.NotEqual(t, http.StatusNotFound, w.Code, "path=%s should hit OpenAI images handler", path) } } func TestGatewayRoutesGrokImagesAndVideosPathsAreRegistered(t *testing.T) { router := newGatewayRoutesTestRouter(service.PlatformGrok) for _, path := range []string{ "/v1/images/generations", "/v1/images/edits", "/images/generations", "/images/edits", "/v1/videos/generations", "/videos/generations", "/v1/videos/edits", "/videos/edits", "/v1/videos/extensions", "/videos/extensions", } { req := httptest.NewRequest(http.MethodPost, path, strings.NewReader(`{"model":"grok-imagine","prompt":"draw a cat"}`)) req.Header.Set("Content-Type", "application/json") w := httptest.NewRecorder() router.ServeHTTP(w, req) require.NotEqual(t, http.StatusNotFound, w.Code, "path=%s should hit Grok media handler", path) require.NotContains(t, w.Body.String(), "not supported for this platform") } for _, path := range []string{ "/v1/videos/request-123", "/videos/request-123", } { req := httptest.NewRequest(http.MethodGet, path, nil) w := httptest.NewRecorder() router.ServeHTTP(w, req) require.NotEqual(t, http.StatusNotFound, w.Code, "path=%s should hit Grok video handler", path) require.NotContains(t, w.Body.String(), "not supported for this platform") } } func TestGatewayRoutesNonGrokVideosAreRejectedAtPlatformGate(t *testing.T) { router := newGatewayRoutesTestRouter(service.PlatformOpenAI) for _, tc := range []struct { method string path string body string }{ {http.MethodPost, "/v1/videos/generations", `{"model":"grok-imagine-video-1.5","prompt":"waves"}`}, {http.MethodPost, "/videos/generations", `{"model":"grok-imagine-video-1.5","prompt":"waves"}`}, {http.MethodPost, "/v1/videos/edits", `{"model":"grok-imagine-video","prompt":"waves","video":{"url":"https://example.com/in.mp4"}}`}, {http.MethodPost, "/videos/edits", `{"model":"grok-imagine-video","prompt":"waves","video":{"url":"https://example.com/in.mp4"}}`}, {http.MethodPost, "/v1/videos/extensions", `{"model":"grok-imagine-video","prompt":"waves","video":{"url":"https://example.com/in.mp4"}}`}, {http.MethodPost, "/videos/extensions", `{"model":"grok-imagine-video","prompt":"waves","video":{"url":"https://example.com/in.mp4"}}`}, {http.MethodGet, "/v1/videos/request-123", ""}, {http.MethodGet, "/videos/request-123", ""}, } { req := httptest.NewRequest(tc.method, tc.path, strings.NewReader(tc.body)) req.Header.Set("Content-Type", "application/json") w := httptest.NewRecorder() router.ServeHTTP(w, req) require.Equal(t, http.StatusNotFound, w.Code, "method=%s path=%s", tc.method, tc.path) require.Contains(t, w.Body.String(), "Videos API is not supported for this platform") } } func TestGatewayRoutesGrokAllowsCLICompatibilityEntrypoints(t *testing.T) { router := newGatewayRoutesTestRouter(service.PlatformGrok) for _, tc := range []struct { method string path string }{ {http.MethodPost, "/v1/messages"}, {http.MethodPost, "/v1/chat/completions"}, {http.MethodPost, "/chat/completions"}, {http.MethodGet, "/v1/responses"}, {http.MethodGet, "/responses"}, {http.MethodGet, "/backend-api/codex/responses"}, } { req := httptest.NewRequest(tc.method, tc.path, strings.NewReader(`{"model":"grok"}`)) req.Header.Set("Content-Type", "application/json") w := httptest.NewRecorder() router.ServeHTTP(w, req) require.NotEqual(t, http.StatusNotFound, w.Code, "method=%s path=%s", tc.method, tc.path) require.NotContains(t, w.Body.String(), "not supported for Grok groups") } req := httptest.NewRequest(http.MethodPost, "/v1/messages/count_tokens", strings.NewReader(`{"model":"grok","messages":[{"role":"user","content":"hi"}]}`)) req.Header.Set("Content-Type", "application/json") w := httptest.NewRecorder() router.ServeHTTP(w, req) require.Equal(t, http.StatusNotFound, w.Code) require.Contains(t, w.Body.String(), "Token counting is not supported for this platform") for _, path := range []string{ "/v1/responses", "/responses", "/backend-api/codex/responses", } { req := httptest.NewRequest(http.MethodPost, path, strings.NewReader(`{"model":"grok","input":"hi"}`)) req.Header.Set("Content-Type", "application/json") w := httptest.NewRecorder() router.ServeHTTP(w, req) require.NotEqual(t, http.StatusNotFound, w.Code, "path=%s should still reach Responses handler", path) } } func TestGatewayRoutesOpenAICountTokensPathIsRegistered(t *testing.T) { router := newGatewayRoutesTestRouter(service.PlatformOpenAI) req := httptest.NewRequest(http.MethodPost, "/v1/messages/count_tokens", strings.NewReader(`{"model":"claude-sonnet-4-5","messages":[{"role":"user","content":"hi"}]}`)) req.Header.Set("Content-Type", "application/json") w := httptest.NewRecorder() router.ServeHTTP(w, req) require.NotEqual(t, http.StatusNotFound, w.Code) }