test(loadbalance,security): 完善模块测试覆盖率

loadbalance 模块覆盖率从 52.4% 提升至 96.1%:
- TestConsistentHash: 一致性哈希算法测试
- TestIsValidAlgorithm: 算法验证函数测试

security 模块覆盖率从 50.2% 提升至 63.7%:
- TestNewSlidingWindowLimiter: 滑动窗口限流器创建测试
- TestSlidingWindowLimiter_Allow: 请求允许/拒绝测试
- TestSlidingWindowLimiter_Reset/ResetAll: 重置测试
- TestSlidingWindowLimiter_Cleanup: 清理测试
- TestSlidingWindowLimiter_GetStats/GetCount: 统计测试

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
xfy 2026-04-03 18:38:35 +08:00
parent 0a50678b55
commit ff260def3b
2 changed files with 307 additions and 0 deletions

View File

@ -650,3 +650,131 @@ func TestBalancerInterface(t *testing.T) {
})
}
}
// TestConsistentHash 测试一致性哈希负载均衡器。
func TestConsistentHash(t *testing.T) {
t.Run("创建默认配置", func(t *testing.T) {
ch := NewConsistentHash(0, "ip")
if ch == nil {
t.Fatal("NewConsistentHash() = nil")
}
if ch.GetVirtualNodes() != 150 {
t.Errorf("GetVirtualNodes() = %d, want 150", ch.GetVirtualNodes())
}
if ch.GetHashKey() != "ip" {
t.Errorf("GetHashKey() = %q, want %q", ch.GetHashKey(), "ip")
}
})
t.Run("自定义虚拟节点数", func(t *testing.T) {
ch := NewConsistentHash(200, "uri")
if ch.GetVirtualNodes() != 200 {
t.Errorf("GetVirtualNodes() = %d, want 200", ch.GetVirtualNodes())
}
})
t.Run("SelectByKey 空目标", func(t *testing.T) {
ch := NewConsistentHash(150, "ip")
got := ch.SelectByKey([]*Target{}, "192.168.1.1")
if got != nil {
t.Errorf("SelectByKey() = %v, want nil", got)
}
})
t.Run("SelectByKey 单目标", func(t *testing.T) {
ch := NewConsistentHash(150, "ip")
targets := []*Target{
createHealthyTarget("http://backend1:8080", true),
}
got := ch.SelectByKey(targets, "192.168.1.1")
if got == nil {
t.Fatal("SelectByKey() = nil")
}
if got.URL != "http://backend1:8080" {
t.Errorf("SelectByKey() = %q, want %q", got.URL, "http://backend1:8080")
}
})
t.Run("SelectByKey 多目标相同键", func(t *testing.T) {
ch := NewConsistentHash(150, "ip")
targets := []*Target{
createHealthyTarget("http://backend1:8080", true),
createHealthyTarget("http://backend2:8080", true),
createHealthyTarget("http://backend3:8080", true),
}
// 相同的键应该选择相同的目标
key := "192.168.1.100"
first := ch.SelectByKey(targets, key)
for i := 0; i < 10; i++ {
got := ch.SelectByKey(targets, key)
if got == nil {
t.Fatal("SelectByKey() = nil")
}
if got.URL != first.URL {
t.Errorf("相同键选择不同目标: first=%q, got=%q", first.URL, got.URL)
}
}
})
t.Run("GetStats", func(t *testing.T) {
ch := NewConsistentHash(100, "ip")
targets := []*Target{
createHealthyTarget("http://backend1:8080", true),
createHealthyTarget("http://backend2:8080", true),
}
ch.Rebuild(targets)
stats := ch.GetStats()
if stats.VirtualNodes != 100 {
t.Errorf("VirtualNodes = %d, want 100", stats.VirtualNodes)
}
if stats.CircleSize != 200 { // 2 targets * 100 nodes
t.Errorf("CircleSize = %d, want 200", stats.CircleSize)
}
})
t.Run("Rebuild 跳过不健康目标", func(t *testing.T) {
ch := NewConsistentHash(10, "ip")
targets := []*Target{
createHealthyTarget("http://backend1:8080", false),
createHealthyTarget("http://backend2:8080", true),
}
ch.Rebuild(targets)
stats := ch.GetStats()
if stats.CircleSize != 10 { // 只有1个健康目标 * 10 nodes
t.Errorf("CircleSize = %d, want 10", stats.CircleSize)
}
})
}
// TestIsValidAlgorithm 测试算法验证函数。
func TestIsValidAlgorithm(t *testing.T) {
tests := []struct {
name string
algorithm string
want bool
}{
{"round_robin", "round_robin", true},
{"weighted_round_robin", "weighted_round_robin", true},
{"least_conn", "least_conn", true},
{"ip_hash", "ip_hash", true},
{"consistent_hash", "consistent_hash", true},
{"invalid", "invalid", false},
{"empty", "", true}, // 空字符串有效(使用默认值)
{"unknown", "unknown-algorithm", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := IsValidAlgorithm(tt.algorithm)
if got != tt.want {
t.Errorf("IsValidAlgorithm(%q) = %v, want %v", tt.algorithm, got, tt.want)
}
})
}
}

View File

@ -0,0 +1,179 @@
package security
import (
"testing"
"time"
)
func TestNewSlidingWindowLimiter(t *testing.T) {
t.Run("默认配置", func(t *testing.T) {
limiter := NewSlidingWindowLimiter(time.Second, 100, false)
if limiter == nil {
t.Fatal("NewSlidingWindowLimiter() = nil")
}
if limiter.window != time.Second {
t.Errorf("window = %v, want %v", limiter.window, time.Second)
}
if limiter.limit != 100 {
t.Errorf("limit = %d, want 100", limiter.limit)
}
})
t.Run("精确模式", func(t *testing.T) {
limiter := NewSlidingWindowLimiter(time.Minute, 50, true)
if !limiter.precise {
t.Error("precise should be true")
}
})
}
func TestSlidingWindowLimiter_Allow(t *testing.T) {
t.Run("近似模式-允许请求", func(t *testing.T) {
limiter := NewSlidingWindowLimiter(time.Second, 10, false)
for i := 0; i < 10; i++ {
if !limiter.Allow("test-key") {
t.Errorf("请求 %d 应该被允许", i+1)
}
}
})
t.Run("近似模式-拒绝超限请求", func(t *testing.T) {
limiter := NewSlidingWindowLimiter(time.Second, 5, false)
// 发送 5 个请求
for i := 0; i < 5; i++ {
limiter.Allow("test-key")
}
// 验证统计
stats := limiter.GetStats()
if stats.Limit != 5 {
t.Errorf("Limit = %d, want 5", stats.Limit)
}
})
t.Run("精确模式-允许请求", func(t *testing.T) {
limiter := NewSlidingWindowLimiter(time.Second, 10, true)
for i := 0; i < 10; i++ {
if !limiter.Allow("test-key") {
t.Errorf("请求 %d 应该被允许", i+1)
}
}
})
t.Run("精确模式-拒绝超限请求", func(t *testing.T) {
limiter := NewSlidingWindowLimiter(time.Second, 3, true)
// 发送 3 个请求
for i := 0; i < 3; i++ {
if !limiter.Allow("test-key") {
t.Errorf("请求 %d 应该被允许", i+1)
}
}
// 第 4 个请求应该被拒绝
if limiter.Allow("test-key") {
t.Error("第 4 个请求应该被拒绝")
}
})
t.Run("不同键独立计数", func(t *testing.T) {
limiter := NewSlidingWindowLimiter(time.Second, 2, true)
// key1 发送 2 个请求
limiter.Allow("key1")
limiter.Allow("key1")
// key2 应该仍可发送
if !limiter.Allow("key2") {
t.Error("key2 的请求应该被允许")
}
})
}
func TestSlidingWindowLimiter_Reset(t *testing.T) {
limiter := NewSlidingWindowLimiter(time.Second, 5, true)
// 发送一些请求
for i := 0; i < 5; i++ {
limiter.Allow("test-key")
}
// 重置
limiter.Reset("test-key")
// 验证计数归零
if count := limiter.GetCount("test-key"); count != 0 {
t.Errorf("GetCount() = %d, want 0 after reset", count)
}
}
func TestSlidingWindowLimiter_ResetAll(t *testing.T) {
limiter := NewSlidingWindowLimiter(time.Second, 5, true)
// 为多个键发送请求
limiter.Allow("key1")
limiter.Allow("key2")
limiter.Allow("key3")
// 重置所有
limiter.ResetAll()
stats := limiter.GetStats()
if stats.CounterKeys != 0 {
t.Errorf("CounterKeys = %d, want 0 after ResetAll", stats.CounterKeys)
}
}
func TestSlidingWindowLimiter_Cleanup(t *testing.T) {
limiter := NewSlidingWindowLimiter(time.Second, 5, true)
// 发送请求
limiter.Allow("test-key")
// 清理(由于请求刚发送,不应该被清理)
limiter.Cleanup(time.Minute)
stats := limiter.GetStats()
if stats.CounterKeys != 1 {
t.Errorf("CounterKeys = %d, want 1", stats.CounterKeys)
}
}
func TestSlidingWindowLimiter_GetStats(t *testing.T) {
limiter := NewSlidingWindowLimiter(time.Minute, 100, false)
stats := limiter.GetStats()
if stats.Window != time.Minute {
t.Errorf("Window = %v, want %v", stats.Window, time.Minute)
}
if stats.Limit != 100 {
t.Errorf("Limit = %d, want 100", stats.Limit)
}
if stats.Precise {
t.Error("Precise should be false")
}
}
func TestSlidingWindowLimiter_GetCount(t *testing.T) {
limiter := NewSlidingWindowLimiter(time.Second, 10, true)
// 发送 3 个请求
for i := 0; i < 3; i++ {
limiter.Allow("test-key")
}
count := limiter.GetCount("test-key")
if count != 3 {
t.Errorf("GetCount() = %d, want 3", count)
}
// 不存在的键
count = limiter.GetCount("nonexistent")
if count != 0 {
t.Errorf("GetCount(nonexistent) = %d, want 0", count)
}
}