lolly/internal/middleware/compression/gzip_static_test.go
xfy 7f4c8f547c refactor(compression): 移除 TryServeFile 和 DefaultExtensions 公开函数
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-10 13:22:35 +08:00

494 lines
12 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Package compression 提供 gzip_static 预压缩文件功能的测试。
//
// 该文件测试 gzip_static 模块的各项功能,包括:
// - Brotli 和 Gzip 文件优先级
// - Gzip 回退机制
// - Accept-Encoding 头解析
// - 扩展名检查
// - 路径遍历防护
// - Vary 头设置
//
// 作者xfy
package compression
import (
"os"
"path/filepath"
"testing"
"github.com/valyala/fasthttp"
)
// TestGzipStaticServeFile_BrotliPriority 测试 .br 文件优先于 .gz 文件
func TestGzipStaticServeFile_BrotliPriority(t *testing.T) {
// 创建临时目录
tmpDir := t.TempDir()
// 创建 .br 和 .gz 文件
brFile := filepath.Join(tmpDir, "test.js.br")
gzFile := filepath.Join(tmpDir, "test.js.gz")
if err := os.WriteFile(brFile, []byte("br content"), 0644); err != nil {
t.Fatalf("创建 .br 文件失败: %v", err)
}
if err := os.WriteFile(gzFile, []byte("gz content"), 0644); err != nil {
t.Fatalf("创建 .gz 文件失败: %v", err)
}
g := NewGzipStatic(true, tmpDir, nil)
tests := []struct {
name string
acceptEncoding string
wantServed bool
wantEncoding string
}{
{
name: "同时支持 br 和 gzip优先返回 br",
acceptEncoding: "br, gzip",
wantServed: true,
wantEncoding: "br",
},
{
name: "只支持 br",
acceptEncoding: "br",
wantServed: true,
wantEncoding: "br",
},
{
name: "只支持 gzip",
acceptEncoding: "gzip",
wantServed: true,
wantEncoding: "gzip",
},
{
name: "支持 deflate 和 gzip返回 gzip",
acceptEncoding: "deflate, gzip",
wantServed: true,
wantEncoding: "gzip",
},
{
name: "不支持任何编码",
acceptEncoding: "",
wantServed: false,
wantEncoding: "",
},
{
name: "支持 br大小写不敏感",
acceptEncoding: "BR",
wantServed: true,
wantEncoding: "br",
},
{
name: "支持 gzip大小写不敏感",
acceptEncoding: "GZIP",
wantServed: true,
wantEncoding: "gzip",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx := &fasthttp.RequestCtx{}
ctx.Request.Header.Set("Accept-Encoding", tt.acceptEncoding)
served := g.ServeFile(ctx, "test.js")
if served != tt.wantServed {
t.Errorf("ServeFile() = %v, want %v", served, tt.wantServed)
}
if tt.wantServed {
encoding := ctx.Response.Header.Peek("Content-Encoding")
if string(encoding) != tt.wantEncoding {
t.Errorf("Content-Encoding = %q, want %q", string(encoding), tt.wantEncoding)
}
}
})
}
}
// TestGzipStaticServeFile_GzipFallback 测试仅 .gz 存在时的回退
func TestGzipStaticServeFile_GzipFallback(t *testing.T) {
// 创建临时目录
tmpDir := t.TempDir()
// 只创建 .gz 文件
gzFile := filepath.Join(tmpDir, "test.css.gz")
if err := os.WriteFile(gzFile, []byte("gz content"), 0644); err != nil {
t.Fatalf("创建 .gz 文件失败: %v", err)
}
g := NewGzipStatic(true, tmpDir, nil)
tests := []struct {
name string
acceptEncoding string
wantServed bool
wantEncoding string
}{
{
name: "支持 br 但没有 .br 文件,回退到 gzip",
acceptEncoding: "br, gzip",
wantServed: true,
wantEncoding: "gzip",
},
{
name: "只支持 gzip",
acceptEncoding: "gzip",
wantServed: true,
wantEncoding: "gzip",
},
{
name: "只支持 br没有 .br 文件,不服务",
acceptEncoding: "br",
wantServed: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx := &fasthttp.RequestCtx{}
ctx.Request.Header.Set("Accept-Encoding", tt.acceptEncoding)
served := g.ServeFile(ctx, "test.css")
if served != tt.wantServed {
t.Errorf("ServeFile() = %v, want %v", served, tt.wantServed)
}
if tt.wantServed {
encoding := ctx.Response.Header.Peek("Content-Encoding")
if string(encoding) != tt.wantEncoding {
t.Errorf("Content-Encoding = %q, want %q", string(encoding), tt.wantEncoding)
}
}
})
}
}
// TestGzipStaticServeFile_AcceptEncodingParsing 测试 Accept-Encoding 头解析
func TestGzipStaticServeFile_AcceptEncodingParsing(t *testing.T) {
tmpDir := t.TempDir()
// 创建测试文件
brFile := filepath.Join(tmpDir, "test.html.br")
if err := os.WriteFile(brFile, []byte("br content"), 0644); err != nil {
t.Fatalf("创建 .br 文件失败: %v", err)
}
g := NewGzipStatic(true, tmpDir, nil)
tests := []struct {
name string
acceptEncoding string
wantSupported bool
}{
{
name: "包含 br",
acceptEncoding: "gzip, deflate, br",
wantSupported: true,
},
{
name: "br 在中间",
acceptEncoding: "gzip, br, deflate",
wantSupported: true,
},
{
name: "br 在最后",
acceptEncoding: "gzip, deflate,br",
wantSupported: true,
},
{
name: "包含空格",
acceptEncoding: "gzip, br , deflate",
wantSupported: true,
},
{
name: "q-value 支持",
acceptEncoding: "br;q=0.9, gzip;q=0.8",
wantSupported: true,
},
{
name: "没有 br",
acceptEncoding: "gzip, deflate",
wantSupported: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx := &fasthttp.RequestCtx{}
ctx.Request.Header.Set("Accept-Encoding", tt.acceptEncoding)
served := g.ServeFile(ctx, "test.html")
if served != tt.wantSupported {
t.Errorf("ServeFile() = %v, want %v", served, tt.wantSupported)
}
})
}
}
// TestGzipStaticServeFile_Disabled 测试禁用时不服务
func TestGzipStaticServeFile_Disabled(t *testing.T) {
tmpDir := t.TempDir()
// 创建测试文件
brFile := filepath.Join(tmpDir, "test.js.br")
if err := os.WriteFile(brFile, []byte("br content"), 0644); err != nil {
t.Fatalf("创建 .br 文件失败: %v", err)
}
// 禁用的 GzipStatic
g := NewGzipStatic(false, tmpDir, nil)
ctx := &fasthttp.RequestCtx{}
ctx.Request.Header.Set("Accept-Encoding", "br")
served := g.ServeFile(ctx, "test.js")
if served {
t.Error("禁用时不应服务文件")
}
}
// TestGzipStaticServeFile_InvalidExtension 测试无效扩展名
func TestGzipStaticServeFile_InvalidExtension(t *testing.T) {
tmpDir := t.TempDir()
// 创建测试文件
brFile := filepath.Join(tmpDir, "test.exe.br")
if err := os.WriteFile(brFile, []byte("br content"), 0644); err != nil {
t.Fatalf("创建 .br 文件失败: %v", err)
}
g := NewGzipStatic(true, tmpDir, nil)
ctx := &fasthttp.RequestCtx{}
ctx.Request.Header.Set("Accept-Encoding", "br")
served := g.ServeFile(ctx, "test.exe")
if served {
t.Error("无效扩展名不应服务文件")
}
}
// TestGzipStaticServeFile_PathTraversal 测试路径遍历防护
func TestGzipStaticServeFile_PathTraversal(t *testing.T) {
tmpDir := t.TempDir()
// 创建测试文件
brFile := filepath.Join(tmpDir, "test.js.br")
if err := os.WriteFile(brFile, []byte("br content"), 0644); err != nil {
t.Fatalf("创建 .br 文件失败: %v", err)
}
g := NewGzipStatic(true, tmpDir, nil)
ctx := &fasthttp.RequestCtx{}
ctx.Request.Header.Set("Accept-Encoding", "br")
// 尝试路径遍历
served := g.ServeFile(ctx, "../test.js")
if served {
t.Error("路径遍历应被阻止")
}
}
// TestGzipStaticServeFile_VaryHeader 测试 Vary 头设置
func TestGzipStaticServeFile_VaryHeader(t *testing.T) {
tmpDir := t.TempDir()
// 创建测试文件
brFile := filepath.Join(tmpDir, "test.js.br")
if err := os.WriteFile(brFile, []byte("br content"), 0644); err != nil {
t.Fatalf("创建 .br 文件失败: %v", err)
}
g := NewGzipStatic(true, tmpDir, nil)
ctx := &fasthttp.RequestCtx{}
ctx.Request.Header.Set("Accept-Encoding", "br")
g.ServeFile(ctx, "test.js")
vary := ctx.Response.Header.Peek("Vary")
if string(vary) != "Accept-Encoding" {
t.Errorf("Vary 头 = %q, want %q", string(vary), "Accept-Encoding")
}
}
// TestNewGzipStatic_DefaultExtensions 测试默认扩展名
func TestNewGzipStatic_DefaultExtensions(t *testing.T) {
g := NewGzipStatic(true, "/tmp", nil)
expected := []string{".html", ".css", ".js", ".json", ".xml", ".svg", ".txt"}
got := g.Extensions()
if len(got) != len(expected) {
t.Errorf("扩展名数量 = %d, want %d", len(got), len(expected))
}
for i, ext := range expected {
if got[i] != ext {
t.Errorf("扩展名[%d] = %q, want %q", i, got[i], ext)
}
}
}
// TestNewGzipStatic_CustomExtensions 测试自定义扩展名
func TestNewGzipStatic_CustomExtensions(t *testing.T) {
custom := []string{".custom", ".ext"}
g := NewGzipStatic(true, "/tmp", custom)
got := g.Extensions()
if len(got) != 2 || got[0] != ".custom" || got[1] != ".ext" {
t.Errorf("自定义扩展名 = %v, want %v", got, custom)
}
}
// TestGzipStatic_Enabled 测试 Enabled 方法
func TestGzipStatic_Enabled(t *testing.T) {
g1 := NewGzipStatic(true, "/tmp", nil)
if !g1.Enabled() {
t.Error("Enabled() = false, want true")
}
g2 := NewGzipStatic(false, "/tmp", nil)
if g2.Enabled() {
t.Error("Enabled() = true, want false")
}
}
// TestDefaultExtensions 测试默认扩展名(通过 NewGzipStatic 间接测试)
func TestDefaultExtensions(t *testing.T) {
expected := []string{".html", ".css", ".js", ".json", ".xml", ".svg", ".txt"}
g := NewGzipStatic(true, "/tmp", nil)
got := g.Extensions()
if len(got) != len(expected) {
t.Errorf("默认扩展名数量 = %d, want %d", len(got), len(expected))
}
for i, ext := range expected {
if got[i] != ext {
t.Errorf("默认扩展名[%d] = %q, want %q", i, got[i], ext)
}
}
}
// TestSupportsEncoding 测试 supportsEncoding 函数
func TestSupportsEncoding(t *testing.T) {
tests := []struct {
name string
acceptEncoding string
wantEncoding string
wantSupported bool
}{
{
name: "支持 br",
acceptEncoding: "br",
wantEncoding: ".br",
wantSupported: true,
},
{
name: "支持 gzip",
acceptEncoding: "gzip",
wantEncoding: ".gz",
wantSupported: true,
},
{
name: "不支持",
acceptEncoding: "deflate",
wantEncoding: ".br",
wantSupported: false,
},
{
name: "空 Accept-Encoding",
acceptEncoding: "",
wantEncoding: ".br",
wantSupported: false,
},
{
name: "br 在中间",
acceptEncoding: "gzip, br, deflate",
wantEncoding: ".br",
wantSupported: true,
},
{
name: "gzip 在中间",
acceptEncoding: "br, gzip, deflate",
wantEncoding: ".gz",
wantSupported: true,
},
{
name: "大小写不敏感",
acceptEncoding: "BR, GZIP",
wantEncoding: ".br",
wantSupported: true,
},
{
name: "未知扩展名",
acceptEncoding: "br, gzip",
wantEncoding: ".unknown",
wantSupported: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
supported := supportsEncoding([]byte(tt.acceptEncoding), tt.wantEncoding)
if supported != tt.wantSupported {
t.Errorf("supportsEncoding(%q, %q) = %v, want %v",
tt.acceptEncoding, tt.wantEncoding, supported, tt.wantSupported)
}
})
}
}
// TestTryServeFile 测试预压缩文件服务(通过 ServeFile 间接测试)
func TestTryServeFile(t *testing.T) {
tmpDir := t.TempDir()
// 创建测试文件
brFile := filepath.Join(tmpDir, "test.js.br")
if err := os.WriteFile(brFile, []byte("br content"), 0644); err != nil {
t.Fatalf("创建 .br 文件失败: %v", err)
}
ctx := &fasthttp.RequestCtx{}
ctx.Request.Header.Set("Accept-Encoding", "br")
g := NewGzipStatic(true, tmpDir, nil)
served := g.ServeFile(ctx, "test.js")
if !served {
t.Error("ServeFile() = false, want true")
}
encoding := ctx.Response.Header.Peek("Content-Encoding")
if string(encoding) != "br" {
t.Errorf("Content-Encoding = %q, want %q", string(encoding), "br")
}
}
// TestGzipStatic_PrecompressedExtensions 测试预压缩扩展名优先级
func TestGzipStatic_PrecompressedExtensions(t *testing.T) {
g := NewGzipStatic(true, "/tmp", nil)
// 验证默认预压缩扩展名顺序
expected := []string{".br", ".gz"}
if len(g.precompressedExtensions) != len(expected) {
t.Errorf("预压缩扩展名数量 = %d, want %d", len(g.precompressedExtensions), len(expected))
}
for i, ext := range expected {
if g.precompressedExtensions[i] != ext {
t.Errorf("预压缩扩展名[%d] = %q, want %q", i, g.precompressedExtensions[i], ext)
}
}
}