lolly/internal/e2e/rewrite_e2e_test.go
xfy 5574339d28 test: 完善测试覆盖率和 E2E 测试场景
Phase 1: 单元测试补充
- 新增 config/loader_test.go,覆盖配置加载、include 合并、循环检测
- 补充 cache/cache_test.go,测试 RefreshCachedAt、DeleteByPatternWithMethod
- 补充 handler/static_test.go,测试 SetExpires、setCacheHeaders、parseExpires

Phase 2: E2E 测试扩展
- 新增 ratelimit_e2e_test.go,测试请求限流功能
- 新增 compression_e2e_test.go,测试 Gzip 压缩功能
- 新增 access_e2e_test.go,测试 IP 访问控制
- 新增 rewrite_e2e_test.go,测试 URL 重写和重定向

覆盖率提升: 82.3% -> 83.1%
E2E 测试用例: ~84 -> ~104 (+20)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-27 17:06:55 +08:00

297 lines
8.5 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.

//go:build e2e
// rewrite_e2e_test.go - URL 重写 E2E 测试L3 层,需要 Docker
//
// 测试 lolly URL 重写功能,包括:
// - URL 重写
// - 正则表达式重写
// - 重定向 (301/302)
// - 内部重定向
//
// 作者xfy
package e2e
import (
"context"
"fmt"
"net/http"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"rua.plus/lolly/internal/e2e/testutil"
)
// TestE2ERewriteBasic 测试基本 URL 重写。
func TestE2ERewriteBasic(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second)
defer cancel()
if !testutil.LollyImageAvailable(ctx) {
t.Skip("lolly:latest image not available, run 'make docker-build' first")
}
// 配置 URL 重写:/old/* -> /new/*
config := `
servers:
- listen: ":8080"
static:
- path: "/"
root: "/var/www/html"
rewrite:
- pattern: "^/old/(.*)$"
replacement: "/new/$1"
flag: "last"
`
// 启动 lolly
lolly, err := testutil.StartLollyContainer(ctx, "", testutil.WithConfigYAML(config))
require.NoError(t, err, "Failed to start lolly container")
defer lolly.Terminate(ctx)
err = lolly.WaitForHealthy(ctx, 30*time.Second)
require.NoError(t, err, "Lolly not healthy")
client := &http.Client{Timeout: 10 * time.Second, CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse // 不自动跟随重定向
}}
// 请求 /old/test 应该被重写到 /new/test
resp, err := client.Get(lolly.HTTPBaseURL() + "/old/test")
require.NoError(t, err)
defer resp.Body.Close()
t.Logf("Status: %d", resp.StatusCode)
// 重写后可能返回 404文件不存在但不应该返回重定向
assert.NotEqual(t, http.StatusMovedPermanently, resp.StatusCode)
assert.NotEqual(t, http.StatusFound, resp.StatusCode)
}
// TestE2ERewriteRedirect 测试重写为重定向。
func TestE2ERewriteRedirect(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second)
defer cancel()
if !testutil.LollyImageAvailable(ctx) {
t.Skip("lolly:latest image not available, run 'make docker-build' first")
}
// 配置重写为 302 重定向
config := `
servers:
- listen: ":8080"
static:
- path: "/"
root: "/var/www/html"
rewrite:
- pattern: "^/old/(.*)$"
replacement: "/new/$1"
flag: "redirect"
`
// 启动 lolly
lolly, err := testutil.StartLollyContainer(ctx, "", testutil.WithConfigYAML(config))
require.NoError(t, err, "Failed to start lolly container")
defer lolly.Terminate(ctx)
err = lolly.WaitForHealthy(ctx, 30*time.Second)
require.NoError(t, err, "Lolly not healthy")
client := &http.Client{Timeout: 10 * time.Second, CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
}}
// 请求 /old/test 应该返回 302 重定向
resp, err := client.Get(lolly.HTTPBaseURL() + "/old/test")
require.NoError(t, err)
defer resp.Body.Close()
assert.Equal(t, http.StatusFound, resp.StatusCode, "Should return 302 redirect")
location := resp.Header.Get("Location")
assert.Contains(t, location, "/new/test", "Location should contain /new/test")
t.Logf("Location: %s", location)
}
// TestE2ERewritePermanent 测试永久重定向。
func TestE2ERewritePermanent(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second)
defer cancel()
if !testutil.LollyImageAvailable(ctx) {
t.Skip("lolly:latest image not available, run 'make docker-build' first")
}
// 配置永久重定向
config := `
servers:
- listen: ":8080"
static:
- path: "/"
root: "/var/www/html"
rewrite:
- pattern: "^/old/(.*)$"
replacement: "/new/$1"
flag: "permanent"
`
// 启动 lolly
lolly, err := testutil.StartLollyContainer(ctx, "", testutil.WithConfigYAML(config))
require.NoError(t, err, "Failed to start lolly container")
defer lolly.Terminate(ctx)
err = lolly.WaitForHealthy(ctx, 30*time.Second)
require.NoError(t, err, "Lolly not healthy")
client := &http.Client{Timeout: 10 * time.Second, CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
}}
// 请求 /old/test 应该返回 301 永久重定向
resp, err := client.Get(lolly.HTTPBaseURL() + "/old/test")
require.NoError(t, err)
defer resp.Body.Close()
assert.Equal(t, http.StatusMovedPermanently, resp.StatusCode, "Should return 301 permanent redirect")
}
// TestE2ERewriteRegexCapture 测试正则表达式捕获组。
func TestE2ERewriteRegexCapture(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second)
defer cancel()
if !testutil.LollyImageAvailable(ctx) {
t.Skip("lolly:latest image not available, run 'make docker-build' first")
}
// 配置复杂的正则重写
config := `
servers:
- listen: ":8080"
static:
- path: "/"
root: "/var/www/html"
rewrite:
- pattern: "^/user/([0-9]+)/post/([0-9]+)$"
replacement: "/posts/$1-$2"
flag: "redirect"
`
// 启动 lolly
lolly, err := testutil.StartLollyContainer(ctx, "", testutil.WithConfigYAML(config))
require.NoError(t, err, "Failed to start lolly container")
defer lolly.Terminate(ctx)
err = lolly.WaitForHealthy(ctx, 30*time.Second)
require.NoError(t, err, "Lolly not healthy")
client := &http.Client{Timeout: 10 * time.Second, CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
}}
// 请求 /user/123/post/456 应该重定向到 /posts/123-456
resp, err := client.Get(lolly.HTTPBaseURL() + "/user/123/post/456")
require.NoError(t, err)
defer resp.Body.Close()
assert.Equal(t, http.StatusFound, resp.StatusCode)
location := resp.Header.Get("Location")
assert.Contains(t, location, "/posts/123-456", "Location should contain /posts/123-456")
t.Logf("Location: %s", location)
}
// TestE2ERewriteProxy 测试代理模式下的重写。
func TestE2ERewriteProxy(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second)
defer cancel()
if !testutil.LollyImageAvailable(ctx) {
t.Skip("lolly:latest image not available, run 'make docker-build' first")
}
// 启动模拟后端
backend, backendAddr, err := testutil.StartMockBackend(ctx)
require.NoError(t, err, "Failed to start mock backend")
defer backend.Terminate(ctx)
// 配置代理 + 重写
config := fmt.Sprintf(`
servers:
- listen: ":8080"
proxy:
- path: "/api"
targets:
- url: "http://%s"
rewrite:
- pattern: "^/api/v1/(.*)$"
replacement: "/api/v2/$1"
flag: "last"
`, backendAddr)
// 启动 lolly
lolly, err := testutil.StartLollyContainer(ctx, "", testutil.WithConfigYAML(config))
require.NoError(t, err, "Failed to start lolly container")
defer lolly.Terminate(ctx)
err = lolly.WaitForHealthy(ctx, 30*time.Second)
require.NoError(t, err, "Lolly not healthy")
client := &http.Client{Timeout: 10 * time.Second}
// 请求 /api/v1/users 应该被重写到 /api/v2/users
resp, err := client.Get(lolly.HTTPBaseURL() + "/api/v1/users")
require.NoError(t, err)
defer resp.Body.Close()
t.Logf("Status: %d", resp.StatusCode)
// 代理请求应该成功
assert.NotEqual(t, http.StatusForbidden, resp.StatusCode)
}
// TestE2ERewriteNoMatch 测试不匹配时不重写。
func TestE2ERewriteNoMatch(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second)
defer cancel()
if !testutil.LollyImageAvailable(ctx) {
t.Skip("lolly:latest image not available, run 'make docker-build' first")
}
// 配置重写规则
config := `
servers:
- listen: ":8080"
static:
- path: "/"
root: "/var/www/html"
rewrite:
- pattern: "^/old/(.*)$"
replacement: "/new/$1"
flag: "redirect"
`
// 启动 lolly
lolly, err := testutil.StartLollyContainer(ctx, "", testutil.WithConfigYAML(config))
require.NoError(t, err, "Failed to start lolly container")
defer lolly.Terminate(ctx)
err = lolly.WaitForHealthy(ctx, 30*time.Second)
require.NoError(t, err, "Lolly not healthy")
client := &http.Client{Timeout: 10 * time.Second, CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
}}
// 请求 /other/test 不匹配规则,不应该重定向
resp, err := client.Get(lolly.HTTPBaseURL() + "/other/test")
require.NoError(t, err)
defer resp.Body.Close()
// 不应该返回重定向
assert.NotEqual(t, http.StatusFound, resp.StatusCode)
assert.NotEqual(t, http.StatusMovedPermanently, resp.StatusCode)
}