健康检查增强: - 添加 HealthMatch 接口支持自定义健康判断逻辑 - 支持状态码范围、响应体正则、响应头匹配 - 集成 SlowStartManager 实现慢启动 配置增强: - HealthCheckConfig 新增 Match 和 SlowStart 字段 - ProxyBufferingConfig 支持 Buffers 配置字符串格式 - 新增 ProxyCachePathConfig 磁盘缓存路径配置 - 添加 StaleIfError/StaleIfTimeout 缓存配置 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
283 lines
5.3 KiB
Go
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)
|
|
}
|
|
})
|
|
}
|
|
}
|