- 删除 loadbalance_e2e_test.go 中未使用的 encoding/json 导入 - 删除 ssl_e2e_test.go 中未使用的 net/http 导入 - 格式化 testutil/config.go 中的结构体字段对齐 - 为多个文件添加末尾换行符 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
905 lines
27 KiB
Go
905 lines
27 KiB
Go
//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) {
|
||
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")
|
||
}
|
||
|
||
// 启动后端
|
||
networkName, pool, err := testutil.SetupProxyTest(ctx, 1)
|
||
require.NoError(t, err, "Failed to start backend pool")
|
||
defer testutil.CleanupProxyTest(ctx, 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, 30*time.Second)
|
||
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) {
|
||
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")
|
||
}
|
||
|
||
// 启动后端
|
||
networkName, pool, err := testutil.SetupProxyTest(ctx, 1)
|
||
require.NoError(t, err, "Failed to start backend pool")
|
||
defer testutil.CleanupProxyTest(ctx, 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, 30*time.Second)
|
||
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) {
|
||
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")
|
||
}
|
||
|
||
// 启动后端
|
||
networkName, pool, err := testutil.SetupProxyTest(ctx, 1)
|
||
require.NoError(t, err, "Failed to start backend pool")
|
||
defer testutil.CleanupProxyTest(ctx, 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, 30*time.Second)
|
||
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) {
|
||
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")
|
||
}
|
||
|
||
// 启动后端
|
||
networkName, pool, err := testutil.SetupProxyTest(ctx, 1)
|
||
require.NoError(t, err, "Failed to start backend pool")
|
||
defer testutil.CleanupProxyTest(ctx, 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, 30*time.Second)
|
||
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) {
|
||
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")
|
||
}
|
||
|
||
// 启动后端
|
||
networkName, pool, err := testutil.SetupProxyTest(ctx, 1)
|
||
require.NoError(t, err, "Failed to start backend pool")
|
||
defer testutil.CleanupProxyTest(ctx, 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, 30*time.Second)
|
||
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) {
|
||
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")
|
||
}
|
||
|
||
// 启动后端
|
||
networkName, pool, err := testutil.SetupProxyTest(ctx, 1)
|
||
require.NoError(t, err, "Failed to start backend pool")
|
||
defer testutil.CleanupProxyTest(ctx, 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, 30*time.Second)
|
||
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) {
|
||
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")
|
||
}
|
||
|
||
// 启动后端
|
||
networkName, pool, err := testutil.SetupProxyTest(ctx, 1)
|
||
require.NoError(t, err, "Failed to start backend pool")
|
||
defer testutil.CleanupProxyTest(ctx, 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, 30*time.Second)
|
||
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) {
|
||
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")
|
||
}
|
||
|
||
// 启动后端
|
||
networkName, pool, err := testutil.SetupProxyTest(ctx, 1)
|
||
require.NoError(t, err, "Failed to start backend pool")
|
||
defer testutil.CleanupProxyTest(ctx, 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, 30*time.Second)
|
||
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) {
|
||
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")
|
||
}
|
||
|
||
// 启动后端
|
||
networkName, pool, err := testutil.SetupProxyTest(ctx, 1)
|
||
require.NoError(t, err, "Failed to start backend pool")
|
||
defer testutil.CleanupProxyTest(ctx, 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, 30*time.Second)
|
||
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) {
|
||
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")
|
||
}
|
||
|
||
// 启动后端
|
||
networkName, pool, err := testutil.SetupProxyTest(ctx, 1)
|
||
require.NoError(t, err, "Failed to start backend pool")
|
||
defer testutil.CleanupProxyTest(ctx, 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, 30*time.Second)
|
||
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) {
|
||
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")
|
||
}
|
||
|
||
// 启动后端
|
||
networkName, pool, err := testutil.SetupProxyTest(ctx, 2)
|
||
require.NoError(t, err, "Failed to start backend pool")
|
||
defer testutil.CleanupProxyTest(ctx, 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, 30*time.Second)
|
||
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) {
|
||
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")
|
||
}
|
||
|
||
// 启动后端
|
||
networkName, pool, err := testutil.SetupProxyTest(ctx, 1)
|
||
require.NoError(t, err, "Failed to start backend pool")
|
||
defer testutil.CleanupProxyTest(ctx, 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, 30*time.Second)
|
||
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) {
|
||
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")
|
||
}
|
||
|
||
// 启动后端
|
||
networkName, pool, err := testutil.SetupProxyTest(ctx, 1)
|
||
require.NoError(t, err, "Failed to start backend pool")
|
||
defer testutil.CleanupProxyTest(ctx, 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, 30*time.Second)
|
||
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) {
|
||
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")
|
||
}
|
||
|
||
// 启动后端
|
||
networkName, pool, err := testutil.SetupProxyTest(ctx, 1)
|
||
require.NoError(t, err, "Failed to start backend pool")
|
||
defer testutil.CleanupProxyTest(ctx, 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, 30*time.Second)
|
||
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) {
|
||
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")
|
||
}
|
||
|
||
// 启动后端
|
||
networkName, pool, err := testutil.SetupProxyTest(ctx, 1)
|
||
require.NoError(t, err, "Failed to start backend pool")
|
||
defer testutil.CleanupProxyTest(ctx, 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, 30*time.Second)
|
||
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) {
|
||
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")
|
||
}
|
||
|
||
// 启动后端
|
||
networkName, pool, err := testutil.SetupProxyTest(ctx, 2)
|
||
require.NoError(t, err, "Failed to start backend pool")
|
||
defer testutil.CleanupProxyTest(ctx, 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, 30*time.Second)
|
||
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")
|
||
}
|