From ddd63a8402a03e946dc2316fe757cd27c96d8643 Mon Sep 17 00:00:00 2001 From: alfadb Date: Tue, 7 Jul 2026 10:06:56 +0800 Subject: [PATCH] test: cover endpoint wildcard edge cases --- backend/internal/handler/endpoint_test.go | 41 +++++++++++++++++++ .../internal/handler/stream_error_event.go | 19 +++++---- 2 files changed, 53 insertions(+), 7 deletions(-) diff --git a/backend/internal/handler/endpoint_test.go b/backend/internal/handler/endpoint_test.go index 5756c27339..96ed1292b3 100644 --- a/backend/internal/handler/endpoint_test.go +++ b/backend/internal/handler/endpoint_test.go @@ -232,6 +232,24 @@ func TestInboundEndpointMiddleware_WildcardRoutes(t *testing.T) { requestPath: "/backend-api/codex/responses/compact", want: EndpointResponsesCompact, }, + { + name: "v1 responses wildcard route, non-compact subpath request", + routePath: "/v1/responses/*subpath", + requestPath: "/v1/responses/foo", + want: EndpointResponses, + }, + { + name: "bare responses wildcard route, non-compact subpath request", + routePath: "/responses/*subpath", + requestPath: "/responses/foo", + want: EndpointResponses, + }, + { + name: "codex direct wildcard route, non-compact subpath request", + routePath: "/backend-api/codex/responses/*subpath", + requestPath: "/backend-api/codex/responses/foo", + want: EndpointResponses, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -254,6 +272,29 @@ func TestInboundEndpointMiddleware_WildcardRoutes(t *testing.T) { } } +// TestInboundEndpointMiddleware_GeminiWildcardRoute verifies that a Gemini +// wildcard route (e.g. "/v1beta/models/*modelAction", used to capture the +// ":generateContent"-style action suffix embedded in the path) is normalized +// to EndpointGeminiModels via InboundEndpointMiddleware, using the same real +// Gin routing path as TestInboundEndpointMiddleware_WildcardRoutes above. +func TestInboundEndpointMiddleware_GeminiWildcardRoute(t *testing.T) { + router := gin.New() + router.Use(InboundEndpointMiddleware()) + + var captured string + router.POST("/v1beta/models/*modelAction", func(c *gin.Context) { + captured = GetInboundEndpoint(c) + c.Status(http.StatusOK) + }) + + req := httptest.NewRequest(http.MethodPost, "/v1beta/models/gemini-2.5-pro:generateContent", nil) + rec := httptest.NewRecorder() + router.ServeHTTP(rec, req) + + require.Equal(t, http.StatusOK, rec.Code) + require.Equal(t, EndpointGeminiModels, captured) +} + // TestGetInboundEndpoint_FallbackWildcardRouteWithoutMiddleware verifies // that when InboundEndpointMiddleware did NOT run (so no value is stored // in gin.Context), the GetInboundEndpoint fallback path still prefers diff --git a/backend/internal/handler/stream_error_event.go b/backend/internal/handler/stream_error_event.go index f3a33a8c89..2af098dbed 100644 --- a/backend/internal/handler/stream_error_event.go +++ b/backend/internal/handler/stream_error_event.go @@ -85,22 +85,27 @@ func writeResponsesFailedSSE(c *gin.Context, errType, message string) bool { return true } -// inboundIsResponses 判断当前请求是否落在任何 /responses 路由上。 +// inboundIsResponses 判断当前请求是否落在任意 Responses 路由上 +// (不区分 root 还是 compact 变体)。 // // 不能直接用 GetInboundEndpoint(c) == EndpointResponses 比较,因为 -// NormalizeInboundEndpoint 只识别包含 "/v1/responses" 子串的路径; -// 项目里实际注册了多组路由(gateway_v1、top-level bare、codex direct), -// 其中 r.POST("/responses", ...) 和 codexDirect.POST("/responses", ...) -// 的 c.FullPath() 不含 "/v1/" 前缀,会被归一化为原始路径, -// 导致协议合规终止事件没法发出去。 +// GetInboundEndpoint/NormalizeInboundEndpoint 会把 compact 变体归一化为 +// 单独的 EndpointResponsesCompact(而不是 EndpointResponses), +// 而本函数在这里只关心“是不是 Responses 家族的请求”, +// 不需要区分 root/compact,所以不能用那个等值比较。 // -// 这里用 FullPath 的后缀判断,覆盖所有变体: +// 这里改用 FullPath 的后缀/子串判断,一次性覆盖 root 和 compact 的所有变体: // - /v1/responses // - /v1/responses/compact // - /responses // - /responses/compact // - /backend-api/codex/responses // - /backend-api/codex/responses/compact +// +// 对于通配路由(如 "/v1/responses/*action")注册的 FullPath 本身就带有 +// "/responses/" 子串(例如 "/v1/responses/*action"),所以下面的 +// strings.Contains(p, "/responses/") 分支同样能覆盖这些通配路由, +// 不需要额外处理通配符本身。 func inboundIsResponses(c *gin.Context) bool { if c == nil { return false