test(utils): 为 utils 包添加全面单元测试(覆盖率 4.6% → 预计 >70%)
添加三个新测试文件: bytes_test.go - 字节/字符串零拷贝转换测试: - TestB2s: nil 切片、空切片、ASCII、UTF-8、特殊字符 - TestB2s_ZeroAlloc: 验证内存共享(指针比较) - TestS2b: 空字符串返回 nil、正常转换 - TestS2b_ZeroAlloc: 验证内存共享 - TestB2s_S2b_RoundTrip: 往返转换正确性,包括二进制数据 etag_test.go - ETag 生成测试: - TestGenerateETag: 表驱动测试,零时间、大尺寸、负值 - TestGenerateETag_Format: 验证引号包裹的 hex-hex 格式 - TestGenerateETag_Deterministic: 相同输入产生相同输出 - TestGenerateETag_DifferentInputs: 不同输入产生不同输出 ipallowlist_test.go - IP 白名单测试: - TestParseIPAllowList: nil、空、CIDR、单 IP、localhost、无效输入 - TestParseIPAllowList_Localhost: 验证 127.0.0.1/32 + ::1/128 展开 - TestParseIPAllowList_SingleIPv4/IPv6: /32 和 /128 自动转换 - TestParseCIDR/TestParseCIDR_Invalid: 有效/无效 CIDR 和单 IP - TestIPInAllowList: 匹配/不匹配及边界情况 - TestParseIPAllowList_Integration: 端到端解析+检查
This commit is contained in:
parent
d6ee721bc8
commit
a836152836
95
internal/utils/bytes_test.go
Normal file
95
internal/utils/bytes_test.go
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
// Package utils 提供字节操作工具函数的测试。
|
||||||
|
//
|
||||||
|
// 该文件测试 B2s 和 S2b 函数,包括:
|
||||||
|
// - 空值处理
|
||||||
|
// - 正常值转换
|
||||||
|
// - 内存共享验证
|
||||||
|
//
|
||||||
|
// 作者:xfy
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestB2s(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input []byte
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{"nil_slice", nil, ""},
|
||||||
|
{"empty_slice", []byte{}, ""},
|
||||||
|
{"single_byte", []byte("a"), "a"},
|
||||||
|
{"ascii_string", []byte("hello world"), "hello world"},
|
||||||
|
{"utf8_string", []byte("你好世界"), "你好世界"},
|
||||||
|
{"special_chars", []byte("!@#$%^&*()"), "!@#$%^&*()"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := B2s(tt.input)
|
||||||
|
assert.Equal(t, tt.want, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestB2s_ZeroAlloc(t *testing.T) {
|
||||||
|
original := []byte("test")
|
||||||
|
s := B2s(original)
|
||||||
|
ptr := unsafe.StringData(s)
|
||||||
|
slicePtr := unsafe.SliceData(original)
|
||||||
|
assert.Equal(t, slicePtr, ptr, "B2s result should share memory with original slice")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestS2b(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
want []byte
|
||||||
|
}{
|
||||||
|
{"empty_string", "", nil},
|
||||||
|
{"single_char", "a", []byte("a")},
|
||||||
|
{"ascii_string", "hello world", []byte("hello world")},
|
||||||
|
{"utf8_string", "你好世界", []byte("你好世界")},
|
||||||
|
{"special_chars", "!@#$%^&*()", []byte("!@#$%^&*()")},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := S2b(tt.input)
|
||||||
|
assert.Equal(t, tt.want, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestS2b_ZeroAlloc(t *testing.T) {
|
||||||
|
original := "test"
|
||||||
|
b := S2b(original)
|
||||||
|
strPtr := unsafe.StringData(original)
|
||||||
|
slicePtr := unsafe.SliceData(b)
|
||||||
|
assert.Equal(t, strPtr, slicePtr, "S2b result should share memory with original string")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestB2s_S2b_RoundTrip(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
value string
|
||||||
|
}{
|
||||||
|
{"empty", ""},
|
||||||
|
{"ascii", "hello"},
|
||||||
|
{"utf8", "你好"},
|
||||||
|
{"binary", "\x00\x01\xff"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
b := S2b(tt.value)
|
||||||
|
s := B2s(b)
|
||||||
|
assert.Equal(t, tt.value, s)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
86
internal/utils/etag_test.go
Normal file
86
internal/utils/etag_test.go
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
// Package utils 提供 ETag 生成工具函数的测试。
|
||||||
|
//
|
||||||
|
// 该文件测试 GenerateETag 函数,包括:
|
||||||
|
// - 正常参数生成
|
||||||
|
// - 零值参数
|
||||||
|
// - 大数值参数
|
||||||
|
// - 格式验证
|
||||||
|
//
|
||||||
|
// 作者:xfy
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func makeETag(unix int64, size int64) string {
|
||||||
|
return fmt.Sprintf(`"%x-%x"`, unix, size)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenerateETag(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
modTime time.Time
|
||||||
|
size int64
|
||||||
|
}{
|
||||||
|
{"valid_time_and_size", time.Unix(1609459200, 0), 1024},
|
||||||
|
{"zero_time", time.Time{}, 100},
|
||||||
|
{"zero_size", time.Unix(1609459200, 0), 0},
|
||||||
|
{"large_size", time.Unix(1609459200, 0), 1<<62 - 1},
|
||||||
|
{"negative_size", time.Unix(1609459200, 0), -1},
|
||||||
|
{"negative_modtime", time.Unix(-1000, 0), 500},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := GenerateETag(tt.modTime, tt.size)
|
||||||
|
want := makeETag(tt.modTime.Unix(), tt.size)
|
||||||
|
assert.Equal(t, want, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenerateETag_Format(t *testing.T) {
|
||||||
|
etag := GenerateETag(time.Unix(1609459200, 0), 1024)
|
||||||
|
|
||||||
|
assert.True(t, strings.HasPrefix(etag, "\""), "ETag should start with a quote")
|
||||||
|
assert.True(t, strings.HasSuffix(etag, "\""), "ETag should end with a quote")
|
||||||
|
assert.Equal(t, byte('"'), etag[0])
|
||||||
|
assert.Equal(t, byte('"'), etag[len(etag)-1])
|
||||||
|
|
||||||
|
inner := strings.Trim(etag, "\"")
|
||||||
|
parts := strings.SplitN(inner, "-", 2)
|
||||||
|
require.Len(t, parts, 2, "inner ETag should have exactly one hyphen separator")
|
||||||
|
|
||||||
|
for _, part := range parts {
|
||||||
|
for _, c := range part {
|
||||||
|
assert.True(t, (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || c == '-',
|
||||||
|
"character %c in ETag part should be hex digit or minus sign", c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenerateETag_Deterministic(t *testing.T) {
|
||||||
|
modTime := time.Unix(1700000000, 0)
|
||||||
|
size := int64(2048)
|
||||||
|
|
||||||
|
etag1 := GenerateETag(modTime, size)
|
||||||
|
etag2 := GenerateETag(modTime, size)
|
||||||
|
assert.Equal(t, etag1, etag2, "same inputs should produce identical ETags")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenerateETag_DifferentInputs(t *testing.T) {
|
||||||
|
t1 := time.Unix(1700000000, 0)
|
||||||
|
t2 := time.Unix(1700000001, 0)
|
||||||
|
|
||||||
|
assert.NotEqual(t, GenerateETag(t1, 100), GenerateETag(t2, 100),
|
||||||
|
"different modtimes should produce different ETags")
|
||||||
|
assert.NotEqual(t, GenerateETag(t1, 100), GenerateETag(t1, 200),
|
||||||
|
"different sizes should produce different ETags")
|
||||||
|
}
|
||||||
179
internal/utils/ipallowlist_test.go
Normal file
179
internal/utils/ipallowlist_test.go
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
// Package utils 提供 IP 白名单工具函数的测试。
|
||||||
|
//
|
||||||
|
// 该文件测试 ParseIPAllowList、ParseCIDR、IPInAllowList 函数,包括:
|
||||||
|
// - 空列表处理
|
||||||
|
// - CIDR 格式解析
|
||||||
|
// - 单 IP 自动转换
|
||||||
|
// - localhost 特殊值
|
||||||
|
// - 无效输入处理
|
||||||
|
// - IP 匹配检查
|
||||||
|
//
|
||||||
|
// 作者:xfy
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParseIPAllowList(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input []string
|
||||||
|
wantLen int
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{"nil_input", nil, 0, false},
|
||||||
|
{"empty_input", []string{}, 0, false},
|
||||||
|
{"single_cidr_v4", []string{"192.168.1.0/24"}, 1, false},
|
||||||
|
{"single_cidr_v6", []string{"::1/128"}, 1, false},
|
||||||
|
{"single_ipv4", []string{"192.168.1.1"}, 1, false},
|
||||||
|
{"single_ipv6", []string{"::1"}, 1, false},
|
||||||
|
{"localhost_expansion", []string{"localhost"}, 2, false},
|
||||||
|
{"multiple_entries", []string{"192.168.1.0/24", "10.0.0.1", "::1/128"}, 3, false},
|
||||||
|
{"localhost_with_others", []string{"localhost", "10.0.0.0/8"}, 3, false},
|
||||||
|
{"invalid_entry", []string{"not-an-ip"}, 0, true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
result, err := ParseIPAllowList(tt.input)
|
||||||
|
if tt.wantErr {
|
||||||
|
assert.Error(t, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Len(t, result, tt.wantLen)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseIPAllowList_Localhost(t *testing.T) {
|
||||||
|
result, err := ParseIPAllowList([]string{"localhost"})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, result, 2)
|
||||||
|
|
||||||
|
assert.Equal(t, "127.0.0.1/32", result[0].String())
|
||||||
|
assert.Equal(t, "::1/128", result[1].String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseIPAllowList_SingleIPv4(t *testing.T) {
|
||||||
|
result, err := ParseIPAllowList([]string{"192.168.1.100"})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, result, 1)
|
||||||
|
assert.Equal(t, "192.168.1.100/32", result[0].String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseIPAllowList_SingleIPv6(t *testing.T) {
|
||||||
|
result, err := ParseIPAllowList([]string{"2001:db8::1"})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, result, 1)
|
||||||
|
assert.Equal(t, "2001:db8::1/128", result[0].String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseCIDR(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
want string
|
||||||
|
wantIP string
|
||||||
|
}{
|
||||||
|
{"cidr_v4", "192.168.1.0/24", "192.168.1.0/24", "192.168.1.0"},
|
||||||
|
{"cidr_v6", "::1/128", "::1/128", "::1"},
|
||||||
|
{"single_ipv4", "10.0.0.1", "10.0.0.1/32", "10.0.0.1"},
|
||||||
|
{"single_ipv6", "::1", "::1/128", "::1"},
|
||||||
|
{"slash16", "172.16.0.0/16", "172.16.0.0/16", "172.16.0.0"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
network, err := ParseCIDR(tt.input)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, tt.want, network.String())
|
||||||
|
assert.Equal(t, tt.wantIP, network.IP.String())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseCIDR_Invalid(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
}{
|
||||||
|
{"empty_string", ""},
|
||||||
|
{"invalid_ip", "not-an-ip"},
|
||||||
|
{"invalid_cidr", "192.168.1.0/33"},
|
||||||
|
{"invalid_cidr_v6", "::1/129"},
|
||||||
|
{"garbage", "hello/world"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
_, err := ParseCIDR(tt.input)
|
||||||
|
assert.Error(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIPInAllowList(t *testing.T) {
|
||||||
|
_, v4Net, _ := net.ParseCIDR("192.168.1.0/24")
|
||||||
|
_, v6Net, _ := net.ParseCIDR("::1/128")
|
||||||
|
_, net10, _ := net.ParseCIDR("10.0.0.0/8")
|
||||||
|
|
||||||
|
allowList := []net.IPNet{*v4Net, *v6Net, *net10}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
ip string
|
||||||
|
wantMatch bool
|
||||||
|
}{
|
||||||
|
{"matching_v4_in_range", "192.168.1.100", true},
|
||||||
|
{"matching_v4_boundary_start", "192.168.1.0", true},
|
||||||
|
{"matching_v4_boundary_end", "192.168.1.255", true},
|
||||||
|
{"matching_v6_localhost", "::1", true},
|
||||||
|
{"matching_10_range", "10.50.0.1", true},
|
||||||
|
{"non_matching_v4", "172.16.0.1", false},
|
||||||
|
{"non_matching_v6", "2001:db8::1", false},
|
||||||
|
{"non_matching_nearby", "192.168.2.1", false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
ip := net.ParseIP(tt.ip)
|
||||||
|
require.NotNil(t, ip)
|
||||||
|
result := IPInAllowList(ip, allowList)
|
||||||
|
assert.Equal(t, tt.wantMatch, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIPInAllowList_EmptyList(t *testing.T) {
|
||||||
|
ip := net.ParseIP("192.168.1.1")
|
||||||
|
require.NotNil(t, ip)
|
||||||
|
assert.False(t, IPInAllowList(ip, nil))
|
||||||
|
assert.False(t, IPInAllowList(ip, []net.IPNet{}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIPInAllowList_SingleEntry(t *testing.T) {
|
||||||
|
_, network, err := net.ParseCIDR("127.0.0.1/32")
|
||||||
|
require.NoError(t, err)
|
||||||
|
allowList := []net.IPNet{*network}
|
||||||
|
|
||||||
|
assert.True(t, IPInAllowList(net.ParseIP("127.0.0.1"), allowList))
|
||||||
|
assert.False(t, IPInAllowList(net.ParseIP("127.0.0.2"), allowList))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseIPAllowList_Integration(t *testing.T) {
|
||||||
|
result, err := ParseIPAllowList([]string{"192.168.1.0/24", "10.0.0.1", "localhost"})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, result, 4)
|
||||||
|
|
||||||
|
assert.True(t, IPInAllowList(net.ParseIP("192.168.1.50"), result))
|
||||||
|
assert.True(t, IPInAllowList(net.ParseIP("10.0.0.1"), result))
|
||||||
|
assert.True(t, IPInAllowList(net.ParseIP("127.0.0.1"), result))
|
||||||
|
assert.True(t, IPInAllowList(net.ParseIP("::1"), result))
|
||||||
|
assert.False(t, IPInAllowList(net.ParseIP("8.8.8.8"), result))
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user