| package middleware |
|
|
| import ( |
| "net/http" |
| "net/http/httptest" |
| "strings" |
| "testing" |
|
|
| "github.com/gin-gonic/gin" |
| "github.com/router-for-me/CLIProxyAPI/v6/internal/cache" |
| "github.com/router-for-me/CLIProxyAPI/v6/internal/config" |
| ) |
|
|
| func testResponseCacheConfig() config.ResponseCacheConfig { |
| return config.ResponseCacheConfig{ |
| Enabled: true, |
| TTLSeconds: 300, |
| MaxMemoryEntries: 100, |
| MaxResponseSizeKB: 512, |
| ExcludeStreaming: true, |
| RedisKeyPrefix: "test", |
| } |
| } |
|
|
| func setupRouterWithCache(t *testing.T) *gin.Engine { |
| t.Helper() |
| gin.SetMode(gin.TestMode) |
| r := gin.New() |
| cfg := testResponseCacheConfig() |
| rc := cache.NewResponseCache(cfg, nil) |
| r.Use(ResponseCacheMiddleware(rc, cfg)) |
| return r |
| } |
|
|
| func TestResponseCacheMiddleware_CachesAndHitsNonStreaming(t *testing.T) { |
| r := setupRouterWithCache(t) |
| calls := 0 |
| r.POST("/v1/chat/completions", func(c *gin.Context) { |
| calls++ |
| c.JSON(http.StatusOK, gin.H{"ok": true, "n": calls}) |
| }) |
|
|
| body := `{"model":"gpt-5","messages":[{"role":"user","content":"hi"}]}` |
|
|
| req1 := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(body)) |
| req1.Header.Set("Content-Type", "application/json") |
| w1 := httptest.NewRecorder() |
| r.ServeHTTP(w1, req1) |
| if w1.Code != http.StatusOK { |
| t.Fatalf("first request status: got %d", w1.Code) |
| } |
| if got := w1.Header().Get("X-Cache"); got != "" { |
| t.Fatalf("first request should not be cache hit, got X-Cache=%q", got) |
| } |
|
|
| req2 := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(body)) |
| req2.Header.Set("Content-Type", "application/json") |
| w2 := httptest.NewRecorder() |
| r.ServeHTTP(w2, req2) |
| if w2.Code != http.StatusOK { |
| t.Fatalf("second request status: got %d", w2.Code) |
| } |
| if got := w2.Header().Get("X-Cache"); got != "HIT" { |
| t.Fatalf("second request should be cache hit, got X-Cache=%q", got) |
| } |
| if calls != 1 { |
| t.Fatalf("handler should be called once, got %d", calls) |
| } |
| } |
|
|
| func TestResponseCacheMiddleware_DoesNotCacheStreaming(t *testing.T) { |
| r := setupRouterWithCache(t) |
| calls := 0 |
| r.POST("/v1/chat/completions", func(c *gin.Context) { |
| calls++ |
| c.JSON(http.StatusOK, gin.H{"ok": true, "n": calls}) |
| }) |
|
|
| body := `{"model":"gpt-5","stream":true,"messages":[{"role":"user","content":"hi"}]}` |
|
|
| req1 := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(body)) |
| req1.Header.Set("Content-Type", "application/json") |
| w1 := httptest.NewRecorder() |
| r.ServeHTTP(w1, req1) |
|
|
| req2 := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(body)) |
| req2.Header.Set("Content-Type", "application/json") |
| w2 := httptest.NewRecorder() |
| r.ServeHTTP(w2, req2) |
|
|
| if calls != 2 { |
| t.Fatalf("streaming requests should not be cached; handler calls=%d", calls) |
| } |
| if got := w2.Header().Get("X-Cache"); got != "" { |
| t.Fatalf("streaming request should not have X-Cache hit, got %q", got) |
| } |
| } |
|
|
| func TestResponseCacheMiddleware_SkipsNonJSONContentType(t *testing.T) { |
| r := setupRouterWithCache(t) |
| calls := 0 |
| r.POST("/v1/chat/completions", func(c *gin.Context) { |
| calls++ |
| c.JSON(http.StatusOK, gin.H{"ok": true, "n": calls}) |
| }) |
|
|
| body := "model=gpt-5&q=hello" |
| req1 := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(body)) |
| req1.Header.Set("Content-Type", "application/x-www-form-urlencoded") |
| w1 := httptest.NewRecorder() |
| r.ServeHTTP(w1, req1) |
|
|
| req2 := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(body)) |
| req2.Header.Set("Content-Type", "application/x-www-form-urlencoded") |
| w2 := httptest.NewRecorder() |
| r.ServeHTTP(w2, req2) |
|
|
| if calls != 2 { |
| t.Fatalf("non-json requests should bypass cache; handler calls=%d", calls) |
| } |
| if got := w2.Header().Get("X-Cache"); got != "" { |
| t.Fatalf("non-json request should not have X-Cache hit, got %q", got) |
| } |
| } |
|
|