lolly/internal/e2e/cache_e2e_test.go
xfy 2cb10eb749 perf(e2e): 并行化 E2E 测试,从 ~2h 降至 ~102s
- testutil: 用 sync.Once 缓存 LollyImageAvailable 结果
- testutil: 原子计数器替代时间戳避免容器名竞态
- testutil: SetupProxyTest 接受 suffix 参数生成独立 Docker 网络
- testutil: CleanupProxyTest 显式调用 network.Remove() 清理
- testutil: 移除死代码 SetupProxyTestEnv/ProxyTestEnv
- testutil: HealthCheckWaitTimeout 30s→15s, DefaultTestTimeout 180s→120s
- e2e: 所有 107 个测试函数添加 t.Parallel()
- e2e: 替换 65 处硬编码 30*time.Second 为常量
- make: test-all 三类测试并行运行,显式 PID wait 收集退出码
- make: test-e2e 添加 -parallel 4 flag

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-28 13:19:46 +08:00

921 lines
28 KiB
Go
Raw Permalink 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
// cache_e2e_test.go - 代理缓存 E2E 测试
//
// 测试 lolly 代理缓存功能:缓存命中、缓存过期、缓存锁等。
//
// 作者xfy
package e2e
import (
"context"
"fmt"
"io"
"net/http"
"strings"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"rua.plus/lolly/internal/e2e/testutil"
)
// TestE2EProxyCacheHit 测试缓存命中。
//
// 验证第二次请求返回缓存内容。
func TestE2EProxyCacheHit(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithTimeout(context.Background(), 180*time.Second)
defer cancel()
if !testutil.LollyImageAvailable(ctx) {
t.Skip("lolly:latest image not available, run 'make docker-build' first")
}
// 启动后端
netObj, networkName, pool, err := testutil.SetupProxyTest(ctx, 1, t.Name())
require.NoError(t, err, "Failed to start backend pool")
defer testutil.CleanupProxyTest(ctx, netObj, networkName, pool)
// 构建配置:启用缓存
cfg := testutil.NewConfigBuilder().
WithServer(":8080").
WithProxy("/", pool.InternalAddresses(),
testutil.WithProxyCache(5*time.Minute, false),
)
configYAML, err := cfg.Build()
require.NoError(t, err, "Failed to build config")
t.Logf("Config:\n%s", configYAML)
// 启动 lolly
lolly, err := testutil.StartLolly(ctx, testutil.WithConfigYAML(configYAML), testutil.WithNetwork(networkName))
require.NoError(t, err, "Failed to start lolly")
defer lolly.Terminate(ctx)
err = lolly.WaitForHealthy(ctx, testutil.HealthCheckWaitTimeout)
require.NoError(t, err, "Lolly not healthy")
client := &http.Client{Timeout: 10 * time.Second}
// 第一次请求(缓存 MISS
resp1, err := client.Get(lolly.HTTPBaseURL())
require.NoError(t, err, "First request failed")
defer resp1.Body.Close()
body1, err := io.ReadAll(resp1.Body)
require.NoError(t, err, "Failed to read first response")
// 检查缓存状态头部
cacheStatus1 := resp1.Header.Get("X-Cache-Status")
t.Logf("First request - X-Cache-Status: %s", cacheStatus1)
// 第二次请求(应该命中缓存)
resp2, err := client.Get(lolly.HTTPBaseURL())
require.NoError(t, err, "Second request failed")
defer resp2.Body.Close()
body2, err := io.ReadAll(resp2.Body)
require.NoError(t, err, "Failed to read second response")
cacheStatus2 := resp2.Header.Get("X-Cache-Status")
t.Logf("Second request - X-Cache-Status: %s", cacheStatus2)
// 验证响应内容相同
assert.Equal(t, string(body1), string(body2), "Cached response should match original")
// 如果有 Age 头部,表示来自缓存
age := resp2.Header.Get("Age")
if age != "" {
t.Logf("Response Age: %s", age)
}
}
// TestE2EProxyCacheExpire 测试缓存过期。
//
// 验证缓存过期后重新获取。
func TestE2EProxyCacheExpire(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithTimeout(context.Background(), 180*time.Second)
defer cancel()
if !testutil.LollyImageAvailable(ctx) {
t.Skip("lolly:latest image not available, run 'make docker-build' first")
}
// 启动后端
netObj, networkName, pool, err := testutil.SetupProxyTest(ctx, 1, t.Name())
require.NoError(t, err, "Failed to start backend pool")
defer testutil.CleanupProxyTest(ctx, netObj, networkName, pool)
// 构建配置:短缓存时间
cfg := testutil.NewConfigBuilder().
WithServer(":8080").
WithProxy("/", pool.InternalAddresses(),
testutil.WithProxyCache(2*time.Second, false),
)
configYAML, err := cfg.Build()
require.NoError(t, err, "Failed to build config")
// 启动 lolly
lolly, err := testutil.StartLolly(ctx, testutil.WithConfigYAML(configYAML), testutil.WithNetwork(networkName))
require.NoError(t, err, "Failed to start lolly")
defer lolly.Terminate(ctx)
err = lolly.WaitForHealthy(ctx, testutil.HealthCheckWaitTimeout)
require.NoError(t, err, "Lolly not healthy")
client := &http.Client{Timeout: 10 * time.Second}
// 第一次请求
resp1, err := client.Get(lolly.HTTPBaseURL())
require.NoError(t, err, "First request failed")
resp1.Body.Close()
t.Log("First request completed, waiting for cache to expire...")
// 等待缓存过期
time.Sleep(3 * time.Second)
// 第二次请求(缓存应该已过期)
resp2, err := client.Get(lolly.HTTPBaseURL())
require.NoError(t, err, "Second request failed")
defer resp2.Body.Close()
t.Log("Second request after cache expiry completed")
// 验证请求成功
assert.Equal(t, 200, resp2.StatusCode)
}
// TestE2EProxyCacheLock 测试缓存锁。
//
// 验证缓存锁防止缓存击穿。
func TestE2EProxyCacheLock(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithTimeout(context.Background(), 180*time.Second)
defer cancel()
if !testutil.LollyImageAvailable(ctx) {
t.Skip("lolly:latest image not available, run 'make docker-build' first")
}
// 启动后端
netObj, networkName, pool, err := testutil.SetupProxyTest(ctx, 1, t.Name())
require.NoError(t, err, "Failed to start backend pool")
defer testutil.CleanupProxyTest(ctx, netObj, networkName, pool)
// 构建配置:启用缓存锁
cfg := testutil.NewConfigBuilder().
WithServer(":8080").
WithProxy("/", pool.InternalAddresses(),
testutil.WithProxyCache(5*time.Minute, true),
)
configYAML, err := cfg.Build()
require.NoError(t, err, "Failed to build config")
// 启动 lolly
lolly, err := testutil.StartLolly(ctx, testutil.WithConfigYAML(configYAML), testutil.WithNetwork(networkName))
require.NoError(t, err, "Failed to start lolly")
defer lolly.Terminate(ctx)
err = lolly.WaitForHealthy(ctx, testutil.HealthCheckWaitTimeout)
require.NoError(t, err, "Lolly not healthy")
// 并发发送相同请求
failures := testutil.RunAndVerifyConcurrentRequests(t, testutil.ConcurrentRequestConfig{
URL: lolly.HTTPBaseURL(),
Count: 10,
Timeout: 30 * time.Second,
ExpectCode: 200,
})
assert.Empty(t, failures, "All concurrent requests should succeed with cache lock")
}
// TestE2EProxyCacheBypass 测试缓存绕过。
//
// 验证特定请求绕过缓存。
func TestE2EProxyCacheBypass(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithTimeout(context.Background(), 180*time.Second)
defer cancel()
if !testutil.LollyImageAvailable(ctx) {
t.Skip("lolly:latest image not available, run 'make docker-build' first")
}
// 启动后端
netObj, networkName, pool, err := testutil.SetupProxyTest(ctx, 1, t.Name())
require.NoError(t, err, "Failed to start backend pool")
defer testutil.CleanupProxyTest(ctx, netObj, networkName, pool)
// 构建配置
cfg := testutil.NewConfigBuilder().
WithServer(":8080").
WithProxy("/", pool.InternalAddresses(),
testutil.WithProxyCache(5*time.Minute, false),
)
configYAML, err := cfg.Build()
require.NoError(t, err, "Failed to build config")
// 启动 lolly
lolly, err := testutil.StartLolly(ctx, testutil.WithConfigYAML(configYAML), testutil.WithNetwork(networkName))
require.NoError(t, err, "Failed to start lolly")
defer lolly.Terminate(ctx)
err = lolly.WaitForHealthy(ctx, testutil.HealthCheckWaitTimeout)
require.NoError(t, err, "Lolly not healthy")
client := &http.Client{Timeout: 10 * time.Second}
// 正常请求
resp1, err := client.Get(lolly.HTTPBaseURL())
require.NoError(t, err, "Normal request failed")
resp1.Body.Close()
// 带 Cache-Control: no-cache 的请求
req, err := http.NewRequest("GET", lolly.HTTPBaseURL(), nil)
require.NoError(t, err, "Failed to create request")
req.Header.Set("Cache-Control", "no-cache")
resp2, err := client.Do(req)
require.NoError(t, err, "Bypass request failed")
defer resp2.Body.Close()
t.Log("Cache bypass test completed")
}
// TestE2EProxyCacheMethods 测试缓存 HTTP 方法。
//
// 验证只有 GET 和 HEAD 被缓存。
func TestE2EProxyCacheMethods(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithTimeout(context.Background(), 180*time.Second)
defer cancel()
if !testutil.LollyImageAvailable(ctx) {
t.Skip("lolly:latest image not available, run 'make docker-build' first")
}
// 启动后端
netObj, networkName, pool, err := testutil.SetupProxyTest(ctx, 1, t.Name())
require.NoError(t, err, "Failed to start backend pool")
defer testutil.CleanupProxyTest(ctx, netObj, networkName, pool)
// 构建配置
cfg := testutil.NewConfigBuilder().
WithServer(":8080").
WithProxy("/", pool.InternalAddresses(),
testutil.WithProxyCache(5*time.Minute, false),
)
configYAML, err := cfg.Build()
require.NoError(t, err, "Failed to build config")
// 启动 lolly
lolly, err := testutil.StartLolly(ctx, testutil.WithConfigYAML(configYAML), testutil.WithNetwork(networkName))
require.NoError(t, err, "Failed to start lolly")
defer lolly.Terminate(ctx)
err = lolly.WaitForHealthy(ctx, testutil.HealthCheckWaitTimeout)
require.NoError(t, err, "Lolly not healthy")
client := &http.Client{Timeout: 10 * time.Second}
// GET 请求
resp, err := client.Get(lolly.HTTPBaseURL())
require.NoError(t, err, "GET request failed")
resp.Body.Close()
// HEAD 请求
resp, err = client.Head(lolly.HTTPBaseURL())
require.NoError(t, err, "HEAD request failed")
resp.Body.Close()
// POST 请求(不应该被缓存)
resp, err = client.Post(lolly.HTTPBaseURL(), "text/plain", strings.NewReader("test"))
require.NoError(t, err, "POST request failed")
resp.Body.Close()
t.Log("Cache methods test completed")
}
// TestE2EProxyCacheHeaders 测试缓存相关头部。
//
// 验证缓存头部正确设置。
func TestE2EProxyCacheHeaders(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithTimeout(context.Background(), 180*time.Second)
defer cancel()
if !testutil.LollyImageAvailable(ctx) {
t.Skip("lolly:latest image not available, run 'make docker-build' first")
}
// 启动后端
netObj, networkName, pool, err := testutil.SetupProxyTest(ctx, 1, t.Name())
require.NoError(t, err, "Failed to start backend pool")
defer testutil.CleanupProxyTest(ctx, netObj, networkName, pool)
// 构建配置
cfg := testutil.NewConfigBuilder().
WithServer(":8080").
WithProxy("/", pool.InternalAddresses(),
testutil.WithProxyCache(5*time.Minute, false),
)
configYAML, err := cfg.Build()
require.NoError(t, err, "Failed to build config")
// 启动 lolly
lolly, err := testutil.StartLolly(ctx, testutil.WithConfigYAML(configYAML), testutil.WithNetwork(networkName))
require.NoError(t, err, "Failed to start lolly")
defer lolly.Terminate(ctx)
err = lolly.WaitForHealthy(ctx, testutil.HealthCheckWaitTimeout)
require.NoError(t, err, "Lolly not healthy")
client := &http.Client{Timeout: 10 * time.Second}
// 发送请求
resp, err := client.Get(lolly.HTTPBaseURL())
require.NoError(t, err, "Request failed")
defer resp.Body.Close()
// 检查响应头部
t.Logf("Response headers:")
for k, v := range resp.Header {
t.Logf(" %s: %v", k, v)
}
// 验证基本响应
assert.Equal(t, 200, resp.StatusCode)
}
// TestE2EProxyCacheStale 测试过期缓存使用。
//
// 验证后端不可用时返回过期缓存。
func TestE2EProxyCacheStale(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithTimeout(context.Background(), 180*time.Second)
defer cancel()
if !testutil.LollyImageAvailable(ctx) {
t.Skip("lolly:latest image not available, run 'make docker-build' first")
}
// 启动后端
netObj, networkName, pool, err := testutil.SetupProxyTest(ctx, 1, t.Name())
require.NoError(t, err, "Failed to start backend pool")
defer testutil.CleanupProxyTest(ctx, netObj, networkName, pool)
// 构建配置:启用 stale-while-revalidate
cfg := testutil.NewConfigBuilder().
WithServer(":8080").
WithProxy("/", pool.InternalAddresses(),
testutil.WithProxyCache(2*time.Second, false),
)
configYAML, err := cfg.Build()
require.NoError(t, err, "Failed to build config")
// 启动 lolly
lolly, err := testutil.StartLolly(ctx, testutil.WithConfigYAML(configYAML), testutil.WithNetwork(networkName))
require.NoError(t, err, "Failed to start lolly")
defer lolly.Terminate(ctx)
err = lolly.WaitForHealthy(ctx, testutil.HealthCheckWaitTimeout)
require.NoError(t, err, "Lolly not healthy")
client := &http.Client{Timeout: 10 * time.Second}
// 第一次请求建立缓存
resp, err := client.Get(lolly.HTTPBaseURL())
require.NoError(t, err, "First request failed")
resp.Body.Close()
t.Log("Cache established")
// 等待缓存过期
time.Sleep(3 * time.Second)
// 第二次请求
resp, err = client.Get(lolly.HTTPBaseURL())
require.NoError(t, err, "Second request failed")
resp.Body.Close()
t.Log("Stale cache test completed")
}
// TestE2EProxyCacheVary 测试 Vary 头部。
//
// 验证 Vary 头部影响缓存键。
func TestE2EProxyCacheVary(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithTimeout(context.Background(), 180*time.Second)
defer cancel()
if !testutil.LollyImageAvailable(ctx) {
t.Skip("lolly:latest image not available, run 'make docker-build' first")
}
// 启动后端
netObj, networkName, pool, err := testutil.SetupProxyTest(ctx, 1, t.Name())
require.NoError(t, err, "Failed to start backend pool")
defer testutil.CleanupProxyTest(ctx, netObj, networkName, pool)
// 构建配置
cfg := testutil.NewConfigBuilder().
WithServer(":8080").
WithProxy("/", pool.InternalAddresses(),
testutil.WithProxyCache(5*time.Minute, false),
)
configYAML, err := cfg.Build()
require.NoError(t, err, "Failed to build config")
// 启动 lolly
lolly, err := testutil.StartLolly(ctx, testutil.WithConfigYAML(configYAML), testutil.WithNetwork(networkName))
require.NoError(t, err, "Failed to start lolly")
defer lolly.Terminate(ctx)
err = lolly.WaitForHealthy(ctx, testutil.HealthCheckWaitTimeout)
require.NoError(t, err, "Lolly not healthy")
client := &http.Client{Timeout: 10 * time.Second}
// 发送带不同 Accept-Encoding 的请求
encodings := []string{"gzip", "identity", "br"}
for i, enc := range encodings {
req, err := http.NewRequest("GET", lolly.HTTPBaseURL(), nil)
require.NoError(t, err, "Failed to create request %d", i)
req.Header.Set("Accept-Encoding", enc)
resp, err := client.Do(req)
require.NoError(t, err, "Request %d failed", i)
resp.Body.Close()
t.Logf("Request with Accept-Encoding: %s - Status: %d", enc, resp.StatusCode)
}
}
// TestE2EProxyCacheRevalidate 测试缓存重新验证。
//
// 验证条件请求正确处理。
func TestE2EProxyCacheRevalidate(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithTimeout(context.Background(), 180*time.Second)
defer cancel()
if !testutil.LollyImageAvailable(ctx) {
t.Skip("lolly:latest image not available, run 'make docker-build' first")
}
// 启动后端
netObj, networkName, pool, err := testutil.SetupProxyTest(ctx, 1, t.Name())
require.NoError(t, err, "Failed to start backend pool")
defer testutil.CleanupProxyTest(ctx, netObj, networkName, pool)
// 构建配置
cfg := testutil.NewConfigBuilder().
WithServer(":8080").
WithProxy("/", pool.InternalAddresses(),
testutil.WithProxyCache(5*time.Minute, false),
)
configYAML, err := cfg.Build()
require.NoError(t, err, "Failed to build config")
// 启动 lolly
lolly, err := testutil.StartLolly(ctx, testutil.WithConfigYAML(configYAML), testutil.WithNetwork(networkName))
require.NoError(t, err, "Failed to start lolly")
defer lolly.Terminate(ctx)
err = lolly.WaitForHealthy(ctx, testutil.HealthCheckWaitTimeout)
require.NoError(t, err, "Lolly not healthy")
client := &http.Client{Timeout: 10 * time.Second}
// 第一次请求获取 ETag
resp1, err := client.Get(lolly.HTTPBaseURL())
require.NoError(t, err, "First request failed")
etag := resp1.Header.Get("ETag")
lastModified := resp1.Header.Get("Last-Modified")
resp1.Body.Close()
t.Logf("ETag: %s, Last-Modified: %s", etag, lastModified)
// 第二次请求带 If-None-Match
if etag != "" {
req, err := http.NewRequest("GET", lolly.HTTPBaseURL(), nil)
require.NoError(t, err, "Failed to create conditional request")
req.Header.Set("If-None-Match", etag)
resp2, err := client.Do(req)
require.NoError(t, err, "Conditional request failed")
resp2.Body.Close()
t.Logf("Conditional request status: %d", resp2.StatusCode)
}
}
// TestE2EProxyCacheConcurrent 测试并发缓存请求。
//
// 验证并发请求正确处理缓存。
func TestE2EProxyCacheConcurrent(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithTimeout(context.Background(), 180*time.Second)
defer cancel()
if !testutil.LollyImageAvailable(ctx) {
t.Skip("lolly:latest image not available, run 'make docker-build' first")
}
// 启动后端
netObj, networkName, pool, err := testutil.SetupProxyTest(ctx, 1, t.Name())
require.NoError(t, err, "Failed to start backend pool")
defer testutil.CleanupProxyTest(ctx, netObj, networkName, pool)
// 构建配置
cfg := testutil.NewConfigBuilder().
WithServer(":8080").
WithProxy("/", pool.InternalAddresses(),
testutil.WithProxyCache(5*time.Minute, true),
)
configYAML, err := cfg.Build()
require.NoError(t, err, "Failed to build config")
// 启动 lolly
lolly, err := testutil.StartLolly(ctx, testutil.WithConfigYAML(configYAML), testutil.WithNetwork(networkName))
require.NoError(t, err, "Failed to start lolly")
defer lolly.Terminate(ctx)
err = lolly.WaitForHealthy(ctx, testutil.HealthCheckWaitTimeout)
require.NoError(t, err, "Lolly not healthy")
// 并发请求
failures := testutil.RunAndVerifyConcurrentRequests(t, testutil.ConcurrentRequestConfig{
URL: lolly.HTTPBaseURL(),
Count: 20,
Timeout: 30 * time.Second,
ExpectCode: 200,
})
assert.Empty(t, failures, "All concurrent cache requests should succeed")
t.Log("Concurrent cache test completed")
}
// TestE2EProxyCacheMultiplePaths 测试多路径缓存。
//
// 验证不同路径独立缓存。
func TestE2EProxyCacheMultiplePaths(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithTimeout(context.Background(), 180*time.Second)
defer cancel()
if !testutil.LollyImageAvailable(ctx) {
t.Skip("lolly:latest image not available, run 'make docker-build' first")
}
// 启动后端
netObj, networkName, pool, err := testutil.SetupProxyTest(ctx, 2, t.Name())
require.NoError(t, err, "Failed to start backend pool")
defer testutil.CleanupProxyTest(ctx, netObj, networkName, pool)
// 构建配置:多路径代理
cfg := testutil.NewConfigBuilder().
WithServer(":8080").
WithProxy("/api/", []string{pool.InternalAddresses()[0]},
testutil.WithProxyCache(5*time.Minute, false),
).
WithProxy("/web/", []string{pool.InternalAddresses()[1]},
testutil.WithProxyCache(5*time.Minute, false),
)
configYAML, err := cfg.Build()
require.NoError(t, err, "Failed to build config")
// 启动 lolly
lolly, err := testutil.StartLolly(ctx, testutil.WithConfigYAML(configYAML), testutil.WithNetwork(networkName))
require.NoError(t, err, "Failed to start lolly")
defer lolly.Terminate(ctx)
err = lolly.WaitForHealthy(ctx, testutil.HealthCheckWaitTimeout)
require.NoError(t, err, "Lolly not healthy")
client := &http.Client{Timeout: 10 * time.Second}
// 测试 /api/ 路径
resp1, err := client.Get(lolly.HTTPBaseURL() + "/api/")
require.NoError(t, err, "API request failed")
resp1.Body.Close()
// 测试 /web/ 路径
resp2, err := client.Get(lolly.HTTPBaseURL() + "/web/")
require.NoError(t, err, "Web request failed")
resp2.Body.Close()
// 再次请求验证缓存
resp3, err := client.Get(lolly.HTTPBaseURL() + "/api/")
require.NoError(t, err, "Cached API request failed")
resp3.Body.Close()
t.Log("Multiple paths cache test completed")
}
// TestE2EProxyCacheWithHeaders 测试带头部的缓存请求。
//
// 验证请求头正确传递并影响缓存。
func TestE2EProxyCacheWithHeaders(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithTimeout(context.Background(), 180*time.Second)
defer cancel()
if !testutil.LollyImageAvailable(ctx) {
t.Skip("lolly:latest image not available, run 'make docker-build' first")
}
// 启动后端
netObj, networkName, pool, err := testutil.SetupProxyTest(ctx, 1, t.Name())
require.NoError(t, err, "Failed to start backend pool")
defer testutil.CleanupProxyTest(ctx, netObj, networkName, pool)
// 构建配置
cfg := testutil.NewConfigBuilder().
WithServer(":8080").
WithProxy("/", pool.InternalAddresses(),
testutil.WithProxyCache(5*time.Minute, false),
testutil.WithProxyHeaders(
map[string]string{
"X-Forwarded-For": "$remote_addr",
"X-Request-ID": "$request_id",
},
map[string]string{
"X-Cache-Status": "$upstream_cache_status",
},
),
)
configYAML, err := cfg.Build()
require.NoError(t, err, "Failed to build config")
// 启动 lolly
lolly, err := testutil.StartLolly(ctx, testutil.WithConfigYAML(configYAML), testutil.WithNetwork(networkName))
require.NoError(t, err, "Failed to start lolly")
defer lolly.Terminate(ctx)
err = lolly.WaitForHealthy(ctx, testutil.HealthCheckWaitTimeout)
require.NoError(t, err, "Lolly not healthy")
client := &http.Client{Timeout: 10 * time.Second}
// 发送请求
req, err := http.NewRequest("GET", lolly.HTTPBaseURL(), nil)
require.NoError(t, err, "Failed to create request")
req.Header.Set("X-Custom-Header", "test-value")
resp, err := client.Do(req)
require.NoError(t, err, "Request failed")
defer resp.Body.Close()
// 检查响应头
cacheStatus := resp.Header.Get("X-Cache-Status")
t.Logf("X-Cache-Status: %s", cacheStatus)
// 验证请求成功
assert.Equal(t, 200, resp.StatusCode)
}
// TestE2EProxyCacheSize 测试缓存大小限制。
//
// 验证大响应不被缓存或正确处理。
func TestE2EProxyCacheSize(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithTimeout(context.Background(), 180*time.Second)
defer cancel()
if !testutil.LollyImageAvailable(ctx) {
t.Skip("lolly:latest image not available, run 'make docker-build' first")
}
// 启动后端
netObj, networkName, pool, err := testutil.SetupProxyTest(ctx, 1, t.Name())
require.NoError(t, err, "Failed to start backend pool")
defer testutil.CleanupProxyTest(ctx, netObj, networkName, pool)
// 构建配置
cfg := testutil.NewConfigBuilder().
WithServer(":8080").
WithProxy("/", pool.InternalAddresses(),
testutil.WithProxyCache(5*time.Minute, false),
)
configYAML, err := cfg.Build()
require.NoError(t, err, "Failed to build config")
// 启动 lolly
lolly, err := testutil.StartLolly(ctx, testutil.WithConfigYAML(configYAML), testutil.WithNetwork(networkName))
require.NoError(t, err, "Failed to start lolly")
defer lolly.Terminate(ctx)
err = lolly.WaitForHealthy(ctx, testutil.HealthCheckWaitTimeout)
require.NoError(t, err, "Lolly not healthy")
client := &http.Client{Timeout: 30 * time.Second}
// 请求大文件
resp, err := client.Get(lolly.HTTPBaseURL())
require.NoError(t, err, "Request failed")
// 读取响应
body, err := io.ReadAll(resp.Body)
resp.Body.Close()
require.NoError(t, err, "Failed to read response")
t.Logf("Response size: %d bytes", len(body))
// 再次请求验证
resp2, err := client.Get(lolly.HTTPBaseURL())
require.NoError(t, err, "Second request failed")
resp2.Body.Close()
t.Log("Cache size test completed")
}
// TestE2EProxyCacheStatusCodes 测试不同状态码的缓存。
//
// 验证不同 HTTP 状态码的缓存行为。
func TestE2EProxyCacheStatusCodes(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithTimeout(context.Background(), 180*time.Second)
defer cancel()
if !testutil.LollyImageAvailable(ctx) {
t.Skip("lolly:latest image not available, run 'make docker-build' first")
}
// 启动后端
netObj, networkName, pool, err := testutil.SetupProxyTest(ctx, 1, t.Name())
require.NoError(t, err, "Failed to start backend pool")
defer testutil.CleanupProxyTest(ctx, netObj, networkName, pool)
// 构建配置
cfg := testutil.NewConfigBuilder().
WithServer(":8080").
WithProxy("/", pool.InternalAddresses(),
testutil.WithProxyCache(5*time.Minute, false),
)
configYAML, err := cfg.Build()
require.NoError(t, err, "Failed to build config")
// 启动 lolly
lolly, err := testutil.StartLolly(ctx, testutil.WithConfigYAML(configYAML), testutil.WithNetwork(networkName))
require.NoError(t, err, "Failed to start lolly")
defer lolly.Terminate(ctx)
err = lolly.WaitForHealthy(ctx, testutil.HealthCheckWaitTimeout)
require.NoError(t, err, "Lolly not healthy")
client := &http.Client{Timeout: 10 * time.Second}
// 测试正常请求200
resp, err := client.Get(lolly.HTTPBaseURL())
require.NoError(t, err, "Request failed")
resp.Body.Close()
assert.Equal(t, 200, resp.StatusCode)
// 测试不存在的路径404
resp, err = client.Get(lolly.HTTPBaseURL() + "/nonexistent")
require.NoError(t, err, "Request failed")
resp.Body.Close()
t.Logf("Non-existent path status: %d", resp.StatusCode)
// 测试带查询参数的请求
resp, err = client.Get(lolly.HTTPBaseURL() + "?query=test")
require.NoError(t, err, "Request failed")
resp.Body.Close()
t.Logf("Query parameter request status: %d", resp.StatusCode)
}
// TestE2EProxyCacheQueryParams 测试查询参数对缓存的影响。
//
// 验证不同查询参数产生不同的缓存条目。
func TestE2EProxyCacheQueryParams(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithTimeout(context.Background(), 180*time.Second)
defer cancel()
if !testutil.LollyImageAvailable(ctx) {
t.Skip("lolly:latest image not available, run 'make docker-build' first")
}
// 启动后端
netObj, networkName, pool, err := testutil.SetupProxyTest(ctx, 1, t.Name())
require.NoError(t, err, "Failed to start backend pool")
defer testutil.CleanupProxyTest(ctx, netObj, networkName, pool)
// 构建配置
cfg := testutil.NewConfigBuilder().
WithServer(":8080").
WithProxy("/", pool.InternalAddresses(),
testutil.WithProxyCache(5*time.Minute, false),
)
configYAML, err := cfg.Build()
require.NoError(t, err, "Failed to build config")
// 启动 lolly
lolly, err := testutil.StartLolly(ctx, testutil.WithConfigYAML(configYAML), testutil.WithNetwork(networkName))
require.NoError(t, err, "Failed to start lolly")
defer lolly.Terminate(ctx)
err = lolly.WaitForHealthy(ctx, testutil.HealthCheckWaitTimeout)
require.NoError(t, err, "Lolly not healthy")
client := &http.Client{Timeout: 10 * time.Second}
// 发送带不同查询参数的请求
queries := []string{"?a=1", "?b=2", "?c=3"}
for i, q := range queries {
resp, err := client.Get(lolly.HTTPBaseURL() + q)
require.NoError(t, err, "Request %d failed", i)
resp.Body.Close()
t.Logf("Query %s - Status: %d", q, resp.StatusCode)
}
// 再次请求相同查询参数
for i, q := range queries {
resp, err := client.Get(lolly.HTTPBaseURL() + q)
require.NoError(t, err, "Cached request %d failed", i)
resp.Body.Close()
}
t.Log("Query params cache test completed")
}
// TestE2EProxyCacheIntegration 综合缓存测试。
//
// 验证缓存功能与其他功能的集成。
func TestE2EProxyCacheIntegration(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithTimeout(context.Background(), 180*time.Second)
defer cancel()
if !testutil.LollyImageAvailable(ctx) {
t.Skip("lolly:latest image not available, run 'make docker-build' first")
}
// 启动后端
netObj, networkName, pool, err := testutil.SetupProxyTest(ctx, 2, t.Name())
require.NoError(t, err, "Failed to start backend pool")
defer testutil.CleanupProxyTest(ctx, netObj, networkName, pool)
// 构建配置:缓存 + 负载均衡
cfg := testutil.NewConfigBuilder().
WithServer(":8080").
WithProxy("/", pool.InternalAddresses(),
testutil.WithLoadBalance("round_robin"),
testutil.WithProxyCache(5*time.Minute, true),
testutil.WithProxyTimeout(5*time.Second, 30*time.Second, 30*time.Second),
)
configYAML, err := cfg.Build()
require.NoError(t, err, "Failed to build config")
// 启动 lolly
lolly, err := testutil.StartLolly(ctx, testutil.WithConfigYAML(configYAML), testutil.WithNetwork(networkName))
require.NoError(t, err, "Failed to start lolly")
defer lolly.Terminate(ctx)
err = lolly.WaitForHealthy(ctx, testutil.HealthCheckWaitTimeout)
require.NoError(t, err, "Lolly not healthy")
client := &http.Client{Timeout: 30 * time.Second}
// 发送多个请求
for i := 0; i < 10; i++ {
resp, err := client.Get(fmt.Sprintf("%s?request=%d", lolly.HTTPBaseURL(), i))
require.NoError(t, err, "Request %d failed", i)
io.Copy(io.Discard, resp.Body)
resp.Body.Close()
}
t.Log("Cache integration test completed")
}