lolly/internal/proxy/health_match_test.go
xfy 92b7040a5f feat(proxy,config): 增强健康检查和缓存配置
健康检查增强:
- 添加 HealthMatch 接口支持自定义健康判断逻辑
- 支持状态码范围、响应体正则、响应头匹配
- 集成 SlowStartManager 实现慢启动

配置增强:
- HealthCheckConfig 新增 Match 和 SlowStart 字段
- ProxyBufferingConfig 支持 Buffers 配置字符串格式
- 新增 ProxyCachePathConfig 磁盘缓存路径配置
- 添加 StaleIfError/StaleIfTimeout 缓存配置

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 13:15:18 +08:00

283 lines
5.3 KiB
Go

package proxy
import (
"testing"
)
func TestDefaultHealthMatch(t *testing.T) {
m := DefaultHealthMatch()
tests := []struct {
status int
want bool
}{
{200, true},
{201, true},
{299, true},
{300, false},
{400, false},
{500, false},
{199, false},
}
for _, tt := range tests {
t.Run("", func(t *testing.T) {
got := m.Match(tt.status, nil, nil)
if got != tt.want {
t.Errorf("Match(%d) = %v, want %v", tt.status, got, tt.want)
}
})
}
}
func TestCustomHealthMatch_StatusRange(t *testing.T) {
cfg := &HealthMatchConfig{
Status: []string{"200-299", "301", "302"},
}
m := NewHealthMatch(cfg)
tests := []struct {
status int
want bool
}{
{200, true},
{250, true},
{299, true},
{301, true},
{302, true},
{300, false}, // 不在范围内
{303, false},
{400, false},
}
for _, tt := range tests {
t.Run("", func(t *testing.T) {
got := m.Match(tt.status, nil, nil)
if got != tt.want {
t.Errorf("Match(%d) = %v, want %v", tt.status, got, tt.want)
}
})
}
}
func TestCustomHealthMatch_BodyRegex(t *testing.T) {
cfg := &HealthMatchConfig{
Status: []string{"200"},
Body: `"status":"ok"`,
}
m := NewHealthMatch(cfg)
tests := []struct {
name string
status int
body string
want bool
}{
{
name: "matching body",
status: 200,
body: `{"status":"ok","data":{}}`,
want: true,
},
{
name: "non-matching body",
status: 200,
body: `{"status":"error"}`,
want: false,
},
{
name: "wrong status",
status: 500,
body: `{"status":"ok"}`,
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := m.Match(tt.status, []byte(tt.body), nil)
if got != tt.want {
t.Errorf("Match() = %v, want %v", got, tt.want)
}
})
}
}
func TestCustomHealthMatch_Headers(t *testing.T) {
cfg := &HealthMatchConfig{
Status: []string{"200"},
Headers: map[string]string{
"X-Health": "ok",
},
}
m := NewHealthMatch(cfg)
tests := []struct {
name string
status int
headers map[string]string
want bool
}{
{
name: "matching header",
status: 200,
headers: map[string]string{
"x-health": "ok",
},
want: true,
},
{
name: "missing header",
status: 200,
headers: map[string]string{
"content-type": "application/json",
},
want: false,
},
{
name: "wrong value",
status: 200,
headers: map[string]string{
"x-health": "error",
},
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := m.Match(tt.status, nil, tt.headers)
if got != tt.want {
t.Errorf("Match() = %v, want %v", got, tt.want)
}
})
}
}
func TestNewHealthMatch_NilConfig(t *testing.T) {
m := NewHealthMatch(nil)
// 应该返回默认匹配器
if !m.Match(200, nil, nil) {
t.Error("nil config should return default matcher")
}
if m.Match(300, nil, nil) {
t.Error("default matcher should not match 300")
}
}
func TestNewHealthMatch_EmptyStatus(t *testing.T) {
cfg := &HealthMatchConfig{
Status: []string{}, // 空
}
m := NewHealthMatch(cfg)
// 应该使用默认 2xx 范围
if !m.Match(200, nil, nil) {
t.Error("empty status should default to 2xx")
}
if m.Match(300, nil, nil) {
t.Error("empty status should default to 2xx, not match 300")
}
}
func TestParseStatusRange(t *testing.T) {
tests := []struct {
input string
min int
max int
wantErr bool
}{
{"200", 200, 200, false},
{"200-299", 200, 299, false},
{" 200-299 ", 200, 299, false},
{"200 - 299", 200, 299, false},
{"abc", 0, 0, true},
{"200-abc", 0, 0, true},
{"200-300-400", 0, 0, true},
}
for _, tt := range tests {
t.Run(tt.input, func(t *testing.T) {
r, err := parseStatusRange(tt.input)
if tt.wantErr {
if err == nil {
t.Error("expected error, got nil")
}
return
}
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if r.min != tt.min || r.max != tt.max {
t.Errorf("range = {%d, %d}, want {%d, %d}", r.min, r.max, tt.min, tt.max)
}
})
}
}
func TestCustomHealthMatch_Combined(t *testing.T) {
cfg := &HealthMatchConfig{
Status: []string{"200-299"},
Body: `"healthy":true`,
Headers: map[string]string{
"Content-Type": "application/json",
},
}
m := NewHealthMatch(cfg)
tests := []struct {
name string
status int
body string
headers map[string]string
want bool
}{
{
name: "all match",
status: 200,
body: `{"healthy":true,"status":"ok"}`,
headers: map[string]string{
"content-type": "application/json",
},
want: true,
},
{
name: "status mismatch",
status: 400,
body: `{"healthy":true}`,
headers: map[string]string{
"content-type": "application/json",
},
want: false,
},
{
name: "body mismatch",
status: 200,
body: `{"healthy":false}`,
headers: map[string]string{
"content-type": "application/json",
},
want: false,
},
{
name: "header mismatch",
status: 200,
body: `{"healthy":true}`,
headers: map[string]string{
"content-type": "text/plain",
},
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := m.Match(tt.status, []byte(tt.body), tt.headers)
if got != tt.want {
t.Errorf("Match() = %v, want %v", got, tt.want)
}
})
}
}