lolly/internal/server/vhost_test.go
xfy 7a96db9f05 test(server): 重构并扩展服务器测试
- 拆分 server_extended_test.go 到独立测试文件
- 添加 pprof、purge、vhost、internal 测试

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-21 08:12:33 +08:00

533 lines
14 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.

// Package server 提供虚拟主机管理器的测试。
package server
import (
"testing"
"github.com/valyala/fasthttp"
)
// mockHandler 创建一个记录调用的 mock handler
func mockHandler(name string, called *bool) fasthttp.RequestHandler {
return func(ctx *fasthttp.RequestCtx) {
*called = true
_, _ = ctx.WriteString(name)
}
}
// TestVHostManager_Handler 测试虚拟主机选择器功能。
func TestVHostManager_Handler(t *testing.T) {
t.Run("匹配已知主机", func(t *testing.T) {
manager := NewVHostManager()
hostCalled := false
_ = manager.AddHost("example.com", mockHandler("example", &hostCalled))
handler := manager.Handler()
ctx := &fasthttp.RequestCtx{}
ctx.Request.SetHost("example.com")
handler(ctx)
if !hostCalled {
t.Error("期望 example.com handler 被调用,但未被调用")
}
if string(ctx.Response.Body()) != "example" {
t.Errorf("响应体 = %q, want %q", string(ctx.Response.Body()), "example")
}
})
t.Run("匹配带端口的主机", func(t *testing.T) {
manager := NewVHostManager()
hostCalled := false
_ = manager.AddHost("example.com", mockHandler("example", &hostCalled))
handler := manager.Handler()
ctx := &fasthttp.RequestCtx{}
ctx.Request.SetHost("example.com:8080")
handler(ctx)
if !hostCalled {
t.Error("期望 example.com handler 被调用(端口应被忽略),但未被调用")
}
if string(ctx.Response.Body()) != "example" {
t.Errorf("响应体 = %q, want %q", string(ctx.Response.Body()), "example")
}
})
t.Run("无匹配使用默认主机", func(t *testing.T) {
manager := NewVHostManager()
exampleCalled := false
defaultCalled := false
_ = manager.AddHost("example.com", mockHandler("example", &exampleCalled))
manager.SetDefault(mockHandler("default", &defaultCalled))
handler := manager.Handler()
ctx := &fasthttp.RequestCtx{}
ctx.Request.SetHost("unknown.com")
handler(ctx)
if exampleCalled {
t.Error("不期望 example.com handler 被调用")
}
if !defaultCalled {
t.Error("期望默认 handler 被调用,但未被调用")
}
if string(ctx.Response.Body()) != "default" {
t.Errorf("响应体 = %q, want %q", string(ctx.Response.Body()), "default")
}
})
t.Run("无匹配无默认返回404", func(t *testing.T) {
manager := NewVHostManager()
exampleCalled := false
_ = manager.AddHost("example.com", mockHandler("example", &exampleCalled))
handler := manager.Handler()
ctx := &fasthttp.RequestCtx{}
ctx.Request.SetHost("unknown.com")
handler(ctx)
if exampleCalled {
t.Error("不期望 example.com handler 被调用")
}
if ctx.Response.StatusCode() != fasthttp.StatusNotFound {
t.Errorf("状态码 = %d, want %d", ctx.Response.StatusCode(), fasthttp.StatusNotFound)
}
})
t.Run("IPv6地址Host", func(t *testing.T) {
manager := NewVHostManager()
ipv6Called := false
_ = manager.AddHost("[::1]", mockHandler("ipv6", &ipv6Called))
handler := manager.Handler()
ctx := &fasthttp.RequestCtx{}
ctx.Request.SetHost("[::1]:8080")
handler(ctx)
if !ipv6Called {
t.Error("期望 [::1] handler 被调用,但未被调用")
}
if string(ctx.Response.Body()) != "ipv6" {
t.Errorf("响应体 = %q, want %q", string(ctx.Response.Body()), "ipv6")
}
})
t.Run("空Host使用默认", func(t *testing.T) {
manager := NewVHostManager()
defaultCalled := false
manager.SetDefault(mockHandler("default", &defaultCalled))
handler := manager.Handler()
ctx := &fasthttp.RequestCtx{}
ctx.Request.SetHost("")
handler(ctx)
if !defaultCalled {
t.Error("期望默认 handler 被调用,但未被调用")
}
if string(ctx.Response.Body()) != "default" {
t.Errorf("响应体 = %q, want %q", string(ctx.Response.Body()), "default")
}
})
t.Run("空Host无默认返回404", func(t *testing.T) {
manager := NewVHostManager()
handler := manager.Handler()
ctx := &fasthttp.RequestCtx{}
ctx.Request.SetHost("")
handler(ctx)
if ctx.Response.StatusCode() != fasthttp.StatusNotFound {
t.Errorf("状态码 = %d, want %d", ctx.Response.StatusCode(), fasthttp.StatusNotFound)
}
})
}
// TestVHostManager_AddHost 测试添加虚拟主机功能。
func TestVHostManager_AddHost(t *testing.T) {
t.Run("添加单个主机", func(t *testing.T) {
manager := NewVHostManager()
called := false
_ = manager.AddHost("test.com", mockHandler("test", &called))
handler := manager.Handler()
ctx := &fasthttp.RequestCtx{}
ctx.Request.SetHost("test.com")
handler(ctx)
if !called {
t.Error("期望添加的主机 handler 被调用")
}
})
t.Run("添加多个主机", func(t *testing.T) {
manager := NewVHostManager()
host1Called := false
host2Called := false
_ = manager.AddHost("host1.com", mockHandler("host1", &host1Called))
_ = manager.AddHost("host2.com", mockHandler("host2", &host2Called))
handler := manager.Handler()
// 测试 host1
ctx1 := &fasthttp.RequestCtx{}
ctx1.Request.SetHost("host1.com")
handler(ctx1)
if !host1Called {
t.Error("期望 host1 handler 被调用")
}
// 测试 host2
ctx2 := &fasthttp.RequestCtx{}
ctx2.Request.SetHost("host2.com")
handler(ctx2)
if !host2Called {
t.Error("期望 host2 handler 被调用")
}
})
t.Run("覆盖已存在的主机", func(t *testing.T) {
manager := NewVHostManager()
firstCalled := false
secondCalled := false
_ = manager.AddHost("test.com", mockHandler("first", &firstCalled))
_ = manager.AddHost("test.com", mockHandler("second", &secondCalled))
handler := manager.Handler()
ctx := &fasthttp.RequestCtx{}
ctx.Request.SetHost("test.com")
handler(ctx)
if firstCalled {
t.Error("不期望第一个 handler 被调用(应被覆盖)")
}
if !secondCalled {
t.Error("期望第二个 handler 被调用")
}
})
}
// TestVHostManager_SetDefault 测试设置默认主机功能。
func TestVHostManager_SetDefault(t *testing.T) {
t.Run("设置默认主机", func(t *testing.T) {
manager := NewVHostManager()
defaultCalled := false
manager.SetDefault(mockHandler("default", &defaultCalled))
handler := manager.Handler()
ctx := &fasthttp.RequestCtx{}
ctx.Request.SetHost("nonexistent.com")
handler(ctx)
if !defaultCalled {
t.Error("期望默认 handler 被调用")
}
})
t.Run("覆盖默认主机", func(t *testing.T) {
manager := NewVHostManager()
firstCalled := false
secondCalled := false
manager.SetDefault(mockHandler("first", &firstCalled))
manager.SetDefault(mockHandler("second", &secondCalled))
handler := manager.Handler()
ctx := &fasthttp.RequestCtx{}
ctx.Request.SetHost("unknown.com")
handler(ctx)
if firstCalled {
t.Error("不期望第一个默认 handler 被调用(应被覆盖)")
}
if !secondCalled {
t.Error("期望第二个默认 handler 被调用")
}
})
}
// TestVHostManager_WildcardPrefix 测试前缀通配符 *.example.com。
func TestVHostManager_WildcardPrefix(t *testing.T) {
tests := []struct {
name string
pattern string
host string
shouldMatch bool
}{
{"exact subdomain", "*.example.com", "www.example.com", true},
{"nested subdomain", "*.example.com", "api.www.example.com", true},
{"no subdomain", "*.example.com", "example.com", false},
{"different domain", "*.example.com", "www.other.com", false},
{"longest match", "*.b.example.com", "a.b.example.com", true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
manager := NewVHostManager()
called := false
_ = manager.AddHost(tt.pattern, mockHandler("wildcard", &called))
handler := manager.Handler()
ctx := &fasthttp.RequestCtx{}
ctx.Request.SetHost(tt.host)
handler(ctx)
if called != tt.shouldMatch {
t.Errorf("expected match %v, got %v", tt.shouldMatch, called)
}
})
}
}
// TestVHostManager_WildcardSuffix 测试后缀通配符 example.*。
func TestVHostManager_WildcardSuffix(t *testing.T) {
tests := []struct {
name string
pattern string
host string
shouldMatch bool
}{
{"match com", "example.*", "example.com", true},
{"match net", "example.*", "example.net", true},
{"match org", "example.*", "example.org", true},
{"no match subdomain", "example.*", "www.example.com", false},
{"no match different prefix", "example.*", "other.com", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
manager := NewVHostManager()
called := false
_ = manager.AddHost(tt.pattern, mockHandler("suffix", &called))
handler := manager.Handler()
ctx := &fasthttp.RequestCtx{}
ctx.Request.SetHost(tt.host)
handler(ctx)
if called != tt.shouldMatch {
t.Errorf("expected match %v, got %v", tt.shouldMatch, called)
}
})
}
}
// TestVHostManager_Regex 测试正则匹配。
func TestVHostManager_Regex(t *testing.T) {
tests := []struct {
name string
pattern string
host string
shouldMatch bool
wantErr bool
}{
{"match digits", "~^api[0-9]+\\.example\\.com$", "api1.example.com", true, false},
{"match digits 2", "~^api[0-9]+\\.example\\.com$", "api99.example.com", true, false},
{"no match letters", "~^api[0-9]+\\.example\\.com$", "apiX.example.com", false, false},
{"invalid regex", "~[invalid", "", false, true},
{"match any subdomain", "~.*\\.example\\.com", "www.example.com", true, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
manager := NewVHostManager()
called := false
err := manager.AddHost(tt.pattern, mockHandler("regex", &called))
if tt.wantErr {
if err == nil {
t.Error("expected error for invalid regex")
}
return
}
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
handler := manager.Handler()
ctx := &fasthttp.RequestCtx{}
ctx.Request.SetHost(tt.host)
handler(ctx)
if called != tt.shouldMatch {
t.Errorf("expected match %v, got %v", tt.shouldMatch, called)
}
})
}
}
// TestVHostManager_MatchPriority 测试匹配优先级。
func TestVHostManager_MatchPriority(t *testing.T) {
t.Run("exact over wildcard", func(t *testing.T) {
manager := NewVHostManager()
exactCalled := false
wildcardCalled := false
_ = manager.AddHost("www.example.com", mockHandler("exact", &exactCalled))
_ = manager.AddHost("*.example.com", mockHandler("wildcard", &wildcardCalled))
handler := manager.Handler()
ctx := &fasthttp.RequestCtx{}
ctx.Request.SetHost("www.example.com")
handler(ctx)
if !exactCalled {
t.Error("expected exact match to be called")
}
if wildcardCalled {
t.Error("expected wildcard to NOT be called when exact match exists")
}
})
t.Run("longest wildcard prefix", func(t *testing.T) {
manager := NewVHostManager()
shortCalled := false
longCalled := false
_ = manager.AddHost("*.example.com", mockHandler("short", &shortCalled))
_ = manager.AddHost("*.b.example.com", mockHandler("long", &longCalled))
handler := manager.Handler()
ctx := &fasthttp.RequestCtx{}
ctx.Request.SetHost("a.b.example.com")
handler(ctx)
if shortCalled {
t.Error("expected short wildcard to NOT be called")
}
if !longCalled {
t.Error("expected longest wildcard match to be called")
}
})
}
// TestVHostManager_FindHost 测试 FindHost 方法。
func TestVHostManager_FindHost(t *testing.T) {
manager := NewVHostManager()
_ = manager.AddHost("exact.com", mockHandler("exact", new(bool)))
_ = manager.AddHost("*.wildcard.com", mockHandler("wildcard", new(bool)))
_ = manager.AddHost("suffix.*", mockHandler("suffix", new(bool)))
_ = manager.AddHost("~^regex.*", mockHandler("regex", new(bool)))
manager.SetDefault(mockHandler("default", new(bool)))
tests := []struct {
host string
wantName string
}{
{"exact.com", "exact.com"},
{"www.wildcard.com", "*.wildcard.com"},
{"suffix.net", "suffix.*"},
{"regex123", "~^regex.*"},
{"unknown.com", "default"},
}
for _, tt := range tests {
t.Run(tt.host, func(t *testing.T) {
vhost := manager.FindHost(tt.host)
if vhost == nil {
t.Fatal("expected non-nil vhost")
}
if vhost.name != tt.wantName {
t.Errorf("expected name %q, got %q", tt.wantName, vhost.name)
}
})
}
}
// TestVHostManager_FindHost_NilDefault 测试无默认主机时返回 nil。
func TestVHostManager_FindHost_NilDefault(t *testing.T) {
manager := NewVHostManager()
_ = manager.AddHost("example.com", mockHandler("example", new(bool)))
vhost := manager.FindHost("unknown.com")
if vhost != nil {
t.Error("expected nil when no default host and no match")
}
}
// TestVHostManager_AddHost_InvalidRegex 测试无效正则表达式。
func TestVHostManager_AddHost_InvalidRegex(t *testing.T) {
manager := NewVHostManager()
err := manager.AddHost("~[invalid(regex", mockHandler("test", new(bool)))
if err == nil {
t.Error("expected error for invalid regex pattern")
}
}
// TestVHostManager_PortStripping 测试端口剥离逻辑。
func TestVHostManager_PortStripping(t *testing.T) {
tests := []struct {
name string
input string
expected string
}{
{"无端口", "example.com", "example.com"},
{"标准HTTP端口", "example.com:80", "example.com"},
{"标准HTTPS端口", "example.com:443", "example.com"},
{"自定义端口", "example.com:8080", "example.com"},
{"IPv6 localhost带端口", "[localhost]:8080", "[localhost]"},
{"IPv6 loopback带端口", "[::1]:8080", "[::1]"},
{"IPv6完整地址带端口", "[2001:db8::1]:443", "[2001:db8::1]"},
{"IPv6无端口", "[::1]", "[::1]"},
{"IPv6完整地址无端口", "[2001:db8::1]", "[2001:db8::1]"},
{"空字符串", "", ""},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
manager := NewVHostManager()
called := false
_ = manager.AddHost(tt.expected, mockHandler("matched", &called))
handler := manager.Handler()
ctx := &fasthttp.RequestCtx{}
ctx.Request.SetHost(tt.input)
handler(ctx)
if !called {
t.Errorf("Host %q 期望匹配 %q但未匹配", tt.input, tt.expected)
}
})
}
// IPv6 数字地址测试
t.Run("IPv6数字地址", func(t *testing.T) {
manager := NewVHostManager()
ipv6Called := false
manager.AddHost("[::1]", mockHandler("ipv6", &ipv6Called))
handler := manager.Handler()
ctx := &fasthttp.RequestCtx{}
ctx.Request.SetHost("[::1]:8080")
handler(ctx)
if !ipv6Called {
t.Error("期望 [::1] handler 被调用,但未被调用")
}
if string(ctx.Response.Body()) != "ipv6" {
t.Errorf("响应体 = %q, want %q", string(ctx.Response.Body()), "ipv6")
}
})
}