veza/veza-backend-api/internal/middleware/cache_headers_test.go
senke ade46fc70f feat(v0.12.4): Redis response cache and CDN cache headers middleware
- ResponseCache: Redis-backed HTTP response caching for public GET endpoints
  with configurable TTLs per endpoint prefix (tracks 15m, search 5m, etc.)
- CacheHeaders: CDN-optimized Cache-Control headers per asset type
  (static 1yr immutable, audio 7d, HLS 60s, images 30d, API no-cache)
- Integrated both middlewares into the router middleware stack
- Unit tests for cache key generation, header rules, and config

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 09:57:06 +01:00

107 lines
2.4 KiB
Go

package middleware
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/gin-gonic/gin"
)
func TestCacheHeaders(t *testing.T) {
gin.SetMode(gin.TestMode)
cfg := DefaultCacheHeadersConfig()
tests := []struct {
name string
path string
expectedHeader string
}{
{
name: "static assets get immutable cache",
path: "/static/js/main.abc123.js",
expectedHeader: "public, max-age=31536000, immutable",
},
{
name: "audio files get 7 day cache",
path: "/audio/track-123/file.mp3",
expectedHeader: "public, max-age=604800",
},
{
name: "HLS segments get short cache",
path: "/hls/track-123/segment.ts",
expectedHeader: "public, max-age=60",
},
{
name: "images get 30 day cache",
path: "/images/covers/album.webp",
expectedHeader: "public, max-age=2592000",
},
{
name: "API responses no-cache",
path: "/api/v1/tracks",
expectedHeader: "no-cache, no-store, must-revalidate",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
router := gin.New()
router.Use(CacheHeaders(cfg))
router.GET(tt.path, func(c *gin.Context) {
c.String(200, "ok")
})
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", tt.path, nil)
router.ServeHTTP(w, req)
got := w.Header().Get("Cache-Control")
if got != tt.expectedHeader {
t.Errorf("path %s: expected Cache-Control=%q, got %q", tt.path, tt.expectedHeader, got)
}
})
}
}
func TestDefaultCacheHeadersConfig(t *testing.T) {
cfg := DefaultCacheHeadersConfig()
if len(cfg.Rules) == 0 {
t.Error("expected non-empty default rules")
}
// Verify critical rules exist
hasStatic := false
hasAudio := false
hasAPI := false
for _, rule := range cfg.Rules {
switch rule.PathPrefix {
case "/static/":
hasStatic = true
if !rule.Immutable {
t.Error("static assets should be immutable")
}
case "/audio/":
hasAudio = true
if rule.MaxAge != 604800 {
t.Errorf("audio max-age should be 604800, got %d", rule.MaxAge)
}
case "/api/":
hasAPI = true
if rule.Directive != "no-cache" {
t.Errorf("API directive should be no-cache, got %s", rule.Directive)
}
}
}
if !hasStatic {
t.Error("missing static rule")
}
if !hasAudio {
t.Error("missing audio rule")
}
if !hasAPI {
t.Error("missing API rule")
}
}