Files
fscan/webscan/lib/eval_test.go
T
ZacharyZcR 1595c92aed
测试构建 / 代码检查 (push) Has been cancelled
测试构建 / 单元测试和构建 (push) Has been cancelled
测试构建 / 构建验证 (push) Has been cancelled
feat: 统一服务缓存 + 指纹驱动插件匹配
将 webServiceCache 扩展为通用 serviceCache,所有指纹识别结果
统一缓存,插件匹配时端口不命中则回退到服务名称匹配。

删除多余的 service_cache.go,复用已有的 ServiceInfo 体系。
补充 nil 防御、Explicit 标记、大量单元/集成/回归测试。
2026-06-12 19:49:07 +08:00

1211 lines
29 KiB
Go

package lib
import (
"errors"
"fmt"
"io"
"net/http"
"net/url"
"strings"
"testing"
"github.com/google/cel-go/common/types"
)
type roundTripFunc func(*http.Request) (*http.Response, error)
func (f roundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) {
return f(req)
}
// =============================================================================
// eval_encoding.go 测试 - 编码解码函数
// =============================================================================
func TestBase64Encoding(t *testing.T) {
tests := []struct {
name string
input string
expected string
}{
{"简单字符串", "hello", "aGVsbG8="},
{"特殊字符", "test@123", "dGVzdEAxMjM="},
{"中文", "测试", "5rWL6K+V"},
{"空字符串", "", ""},
}
customLib := NewEnvOption()
env, err := NewEnv(&customLib)
if err != nil {
t.Fatalf("创建 CEL 环境失败: %v", err)
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
expr := `base64("` + tt.input + `")`
result, err := Evaluate(env, expr, map[string]interface{}{})
if err != nil {
t.Fatalf("表达式评估失败: %v", err)
}
str, ok := result.(types.String)
if !ok {
t.Fatalf("返回值类型错误,期望 String,实际 %T", result)
}
if string(str) != tt.expected {
t.Errorf("base64() = %q, want %q", str, tt.expected)
}
})
}
}
func TestBase64Decoding(t *testing.T) {
tests := []struct {
name string
input string
expected string
}{
{"简单字符串", "aGVsbG8=", "hello"},
{"特殊字符", "dGVzdEAxMjM=", "test@123"},
{"空字符串", "", ""},
}
customLib := NewEnvOption()
env, err := NewEnv(&customLib)
if err != nil {
t.Fatalf("创建 CEL 环境失败: %v", err)
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
expr := `base64Decode("` + tt.input + `")`
result, err := Evaluate(env, expr, map[string]interface{}{})
if err != nil {
t.Fatalf("表达式评估失败: %v", err)
}
str, ok := result.(types.String)
if !ok {
t.Fatalf("返回值类型错误,期望 String,实际 %T", result)
}
if string(str) != tt.expected {
t.Errorf("base64Decode() = %q, want %q", str, tt.expected)
}
})
}
}
func TestURLEncoding(t *testing.T) {
tests := []struct {
name string
input string
expected string
}{
{"普通字符串", "hello world", "hello+world"},
{"特殊字符", "[email protected]", "test%40example.com"},
{"路径", "/api/v1/users", "%2Fapi%2Fv1%2Fusers"},
{"空字符串", "", ""},
}
customLib := NewEnvOption()
env, err := NewEnv(&customLib)
if err != nil {
t.Fatalf("创建 CEL 环境失败: %v", err)
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
expr := `urlencode("` + tt.input + `")`
result, err := Evaluate(env, expr, map[string]interface{}{})
if err != nil {
t.Fatalf("表达式评估失败: %v", err)
}
str, ok := result.(types.String)
if !ok {
t.Fatalf("返回值类型错误,期望 String,实际 %T", result)
}
if string(str) != tt.expected {
t.Errorf("urlencode() = %q, want %q", str, tt.expected)
}
})
}
}
func TestURLDecoding(t *testing.T) {
tests := []struct {
name string
input string
expected string
}{
{"空格编码", "hello+world", "hello world"},
{"特殊字符", "test%40example.com", "[email protected]"},
{"路径", "%2Fapi%2Fv1%2Fusers", "/api/v1/users"},
{"空字符串", "", ""},
}
customLib := NewEnvOption()
env, err := NewEnv(&customLib)
if err != nil {
t.Fatalf("创建 CEL 环境失败: %v", err)
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
expr := `urldecode("` + tt.input + `")`
result, err := Evaluate(env, expr, map[string]interface{}{})
if err != nil {
t.Fatalf("表达式评估失败: %v", err)
}
str, ok := result.(types.String)
if !ok {
t.Fatalf("返回值类型错误,期望 String,实际 %T", result)
}
if string(str) != tt.expected {
t.Errorf("urldecode() = %q, want %q", str, tt.expected)
}
})
}
}
// =============================================================================
// eval_crypto.go 测试 - 加密函数
// =============================================================================
func TestMD5Function(t *testing.T) {
tests := []struct {
name string
input string
expected string
}{
{"简单字符串", "hello", "5d41402abc4b2a76b9719d911017c592"},
{"数字", "123456", "e10adc3949ba59abbe56e057f20f883e"},
{"空字符串", "", "d41d8cd98f00b204e9800998ecf8427e"},
{"特殊字符", "admin@123", "e6e061838856bf47e1de730719fb2609"},
}
customLib := NewEnvOption()
env, err := NewEnv(&customLib)
if err != nil {
t.Fatalf("创建 CEL 环境失败: %v", err)
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
expr := `md5("` + tt.input + `")`
result, err := Evaluate(env, expr, map[string]interface{}{})
if err != nil {
t.Fatalf("表达式评估失败: %v", err)
}
str, ok := result.(types.String)
if !ok {
t.Fatalf("返回值类型错误,期望 String,实际 %T", result)
}
if string(str) != tt.expected {
t.Errorf("md5() = %q, want %q", str, tt.expected)
}
})
}
}
// =============================================================================
// eval_random.go 测试 - 随机函数
// =============================================================================
func TestRandomInt(t *testing.T) {
customLib := NewEnvOption()
env, err := NewEnv(&customLib)
if err != nil {
t.Fatalf("创建 CEL 环境失败: %v", err)
}
tests := []struct {
name string
expr string
min int64
max int64
}{
{"范围1-10", "randomInt(1, 10)", 1, 10},
{"范围100-200", "randomInt(100, 200)", 100, 200},
{"范围0-1", "randomInt(0, 1)", 0, 1},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
for i := 0; i < 10; i++ { // 运行10次确保随机性
result, err := Evaluate(env, tt.expr, map[string]interface{}{})
if err != nil {
t.Fatalf("表达式评估失败: %v", err)
}
num, ok := result.(types.Int)
if !ok {
t.Fatalf("返回值类型错误,期望 Int,实际 %T", result)
}
if int64(num) < tt.min || int64(num) >= tt.max {
t.Errorf("randomInt() = %d, 超出范围 [%d, %d)", num, tt.min, tt.max)
}
}
})
}
}
func TestRandomLowercase(t *testing.T) {
customLib := NewEnvOption()
env, err := NewEnv(&customLib)
if err != nil {
t.Fatalf("创建 CEL 环境失败: %v", err)
}
tests := []struct {
name string
length int
}{
{"长度5", 5},
{"长度10", 10},
{"长度1", 1},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
expr := fmt.Sprintf("randomLowercase(%d)", tt.length)
result, err := Evaluate(env, expr, map[string]interface{}{})
if err != nil {
t.Fatalf("表达式评估失败: %v", err)
}
str, ok := result.(types.String)
if !ok {
t.Fatalf("返回值类型错误,期望 String,实际 %T", result)
}
if len(str) != tt.length {
t.Errorf("randomLowercase() 长度 = %d, want %d", len(str), tt.length)
}
// 验证所有字符都是小写字母
for _, c := range str {
if c < 'a' || c > 'z' {
t.Errorf("randomLowercase() 包含非小写字母字符: %c", c)
break
}
}
})
}
}
func TestRandomUppercase(t *testing.T) {
customLib := NewEnvOption()
env, err := NewEnv(&customLib)
if err != nil {
t.Fatalf("创建 CEL 环境失败: %v", err)
}
tests := []struct {
name string
length int
}{
{"长度5", 5},
{"长度8", 8},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
expr := fmt.Sprintf("randomUppercase(%d)", tt.length)
result, err := Evaluate(env, expr, map[string]interface{}{})
if err != nil {
t.Fatalf("表达式评估失败: %v", err)
}
str, ok := result.(types.String)
if !ok {
t.Fatalf("返回值类型错误,期望 String,实际 %T", result)
}
if len(str) != tt.length {
t.Errorf("randomUppercase() 长度 = %d, want %d", len(str), tt.length)
}
// 验证所有字符都是大写字母
for _, c := range str {
if c < 'A' || c > 'Z' {
t.Errorf("randomUppercase() 包含非大写字母字符: %c", c)
break
}
}
})
}
}
func TestRandomString(t *testing.T) {
customLib := NewEnvOption()
env, err := NewEnv(&customLib)
if err != nil {
t.Fatalf("创建 CEL 环境失败: %v", err)
}
tests := []struct {
name string
length int
}{
{"长度10", 10},
{"长度20", 20},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
expr := fmt.Sprintf("randomString(%d)", tt.length)
result, err := Evaluate(env, expr, map[string]interface{}{})
if err != nil {
t.Fatalf("表达式评估失败: %v", err)
}
str, ok := result.(types.String)
if !ok {
t.Fatalf("返回值类型错误,期望 String,实际 %T", result)
}
if len(str) != tt.length {
t.Errorf("randomString() 长度 = %d, want %d", len(str), tt.length)
}
// 验证所有字符都是字母或数字
for _, c := range str {
if (c < 'a' || c > 'z') && (c < 'A' || c > 'Z') && (c < '0' || c > '9') {
t.Errorf("randomString() 包含非字母数字字符: %c", c)
break
}
}
})
}
}
// =============================================================================
// eval_string.go 测试 - 字符串函数
// =============================================================================
func TestIContains(t *testing.T) {
customLib := NewEnvOption()
env, err := NewEnv(&customLib)
if err != nil {
t.Fatalf("创建 CEL 环境失败: %v", err)
}
tests := []struct {
name string
str string
substr string
expected bool
}{
{"大小写不敏感匹配", "Hello World", "WORLD", true},
{"小写匹配", "hello world", "world", true},
{"不匹配", "hello", "bye", false},
{"空子串", "hello", "", true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
expr := `"` + tt.str + `".icontains("` + tt.substr + `")`
result, err := Evaluate(env, expr, map[string]interface{}{})
if err != nil {
t.Fatalf("表达式评估失败: %v", err)
}
b, ok := result.(types.Bool)
if !ok {
t.Fatalf("返回值类型错误,期望 Bool,实际 %T", result)
}
if bool(b) != tt.expected {
t.Errorf("icontains() = %v, want %v", b, tt.expected)
}
})
}
}
func TestSubstr(t *testing.T) {
customLib := NewEnvOption()
env, err := NewEnv(&customLib)
if err != nil {
t.Fatalf("创建 CEL 环境失败: %v", err)
}
tests := []struct {
name string
str string
start int
length int
expected string
}{
{"正常提取", "hello world", 0, 5, "hello"},
{"中间提取", "hello world", 6, 5, "world"},
{"提取一个字符", "test", 1, 1, "e"},
{"中文字符", "你好世界", 0, 2, "你好"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
expr := fmt.Sprintf(`substr("%s", %d, %d)`, tt.str, tt.start, tt.length)
result, err := Evaluate(env, expr, map[string]interface{}{})
if err != nil {
t.Fatalf("表达式评估失败: %v", err)
}
str, ok := result.(types.String)
if !ok {
t.Fatalf("返回值类型错误,期望 String,实际 %T", result)
}
if string(str) != tt.expected {
t.Errorf("substr() = %q, want %q", str, tt.expected)
}
})
}
}
func TestIStartsWith(t *testing.T) {
customLib := NewEnvOption()
env, err := NewEnv(&customLib)
if err != nil {
t.Fatalf("创建 CEL 环境失败: %v", err)
}
tests := []struct {
name string
str string
prefix string
expected bool
}{
{"大小写不敏感匹配", "Hello World", "HELLO", true},
{"小写匹配", "hello world", "hello", true},
{"不匹配", "hello", "world", false},
{"空前缀", "hello", "", true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
expr := `"` + tt.str + `".istartsWith("` + tt.prefix + `")`
result, err := Evaluate(env, expr, map[string]interface{}{})
if err != nil {
t.Fatalf("表达式评估失败: %v", err)
}
b, ok := result.(types.Bool)
if !ok {
t.Fatalf("返回值类型错误,期望 Bool,实际 %T", result)
}
if bool(b) != tt.expected {
t.Errorf("istartsWith() = %v, want %v", b, tt.expected)
}
})
}
}
// =============================================================================
// 核心函数测试 - Evaluate
// =============================================================================
func TestEvaluate(t *testing.T) {
customLib := NewEnvOption()
env, err := NewEnv(&customLib)
if err != nil {
t.Fatalf("创建 CEL 环境失败: %v", err)
}
tests := []struct {
name string
expr string
params map[string]interface{}
expected interface{}
wantErr bool
}{
{
name: "空表达式返回true",
expr: "",
params: map[string]interface{}{},
expected: true,
wantErr: false,
},
{
name: "简单布尔表达式",
expr: "true && false",
params: map[string]interface{}{},
expected: false,
wantErr: false,
},
{
name: "数学计算",
expr: "1 + 2 * 3",
params: map[string]interface{}{},
expected: int64(7),
wantErr: false,
},
{
name: "字符串拼接",
expr: `"hello" + " " + "world"`,
params: map[string]interface{}{},
expected: "hello world",
wantErr: false,
},
{
name: "无效表达式",
expr: "invalid syntax +++",
params: map[string]interface{}{},
expected: nil,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := Evaluate(env, tt.expr, tt.params)
if (err != nil) != tt.wantErr {
t.Errorf("Evaluate() error = %v, wantErr %v", err, tt.wantErr)
return
}
if tt.wantErr {
return
}
// 类型转换和比较
switch expected := tt.expected.(type) {
case bool:
if b, ok := result.(types.Bool); ok {
if bool(b) != expected {
t.Errorf("Evaluate() = %v, want %v", b, expected)
}
} else {
t.Errorf("Evaluate() 返回类型错误,期望 Bool,实际 %T", result)
}
case int64:
if i, ok := result.(types.Int); ok {
if int64(i) != expected {
t.Errorf("Evaluate() = %v, want %v", i, expected)
}
} else {
t.Errorf("Evaluate() 返回类型错误,期望 Int,实际 %T", result)
}
case string:
if s, ok := result.(types.String); ok {
if string(s) != expected {
t.Errorf("Evaluate() = %v, want %v", s, expected)
}
} else {
t.Errorf("Evaluate() 返回类型错误,期望 String,实际 %T", result)
}
}
})
}
}
// =============================================================================
// 辅助函数测试
// =============================================================================
func TestURLTypeToString(t *testing.T) {
tests := []struct {
name string
url *UrlType
expected string
}{
{
name: "完整URL",
url: &UrlType{
Scheme: "https",
Host: "example.com:443",
Path: "/api/v1/users",
Query: "page=1&size=10",
Fragment: "section1",
},
expected: "https://example.com:443/api/v1/users?page=1&size=10#section1",
},
{
name: "无端口号",
url: &UrlType{
Scheme: "http",
Host: "example.com",
Path: "/test",
},
expected: "http://example.com/test",
},
{
name: "IPv6 host",
url: &UrlType{
Scheme: "http",
Host: "2001:db8::1",
Path: "/test",
},
expected: "http://[2001:db8::1]/test",
},
{
name: "仅路径",
url: &UrlType{
Path: "/api/test",
},
expected: "/api/test",
},
{
name: "带查询参数无路径",
url: &UrlType{
Scheme: "https",
Host: "example.com",
Query: "q=test",
},
expected: "https://example.com?q=test",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := URLTypeToString(tt.url)
if result != tt.expected {
t.Errorf("URLTypeToString() = %q, want %q", result, tt.expected)
}
})
}
}
func TestRandomStrHelpers(t *testing.T) {
t.Run("randomLowercase生成小写字母", func(t *testing.T) {
for i := 0; i < 10; i++ {
result := randomLowercase(10)
if len(result) != 10 {
t.Errorf("randomLowercase(10) 长度 = %d, want 10", len(result))
}
for _, c := range result {
if c < 'a' || c > 'z' {
t.Errorf("randomLowercase() 包含非小写字母: %c", c)
break
}
}
}
})
t.Run("randomUppercase生成大写字母", func(t *testing.T) {
for i := 0; i < 10; i++ {
result := randomUppercase(10)
if len(result) != 10 {
t.Errorf("randomUppercase(10) 长度 = %d, want 10", len(result))
}
for _, c := range result {
if c < 'A' || c > 'Z' {
t.Errorf("randomUppercase() 包含非大写字母: %c", c)
break
}
}
}
})
t.Run("randomString生成字母数字", func(t *testing.T) {
for i := 0; i < 10; i++ {
result := randomString(15)
if len(result) != 15 {
t.Errorf("randomString(15) 长度 = %d, want 15", len(result))
}
for _, c := range result {
if (c < 'a' || c > 'z') && (c < 'A' || c > 'Z') && (c < '0' || c > '9') {
t.Errorf("randomString() 包含非字母数字字符: %c", c)
break
}
}
}
})
}
func TestCompileAndProgramOptions(t *testing.T) {
customLib := NewEnvOption()
t.Run("CompileOptions返回非空", func(t *testing.T) {
opts := customLib.CompileOptions()
if len(opts) == 0 {
t.Error("CompileOptions() 返回空切片")
}
})
t.Run("ProgramOptions返回空切片", func(t *testing.T) {
// ProgramOptions() 返回空切片是预期行为
// 函数实现通过 GetBaseProgramOptions() 在 Evaluate() 时注入
// 这避免了多次创建环境时重复注册函数导致的冲突
opts := customLib.ProgramOptions()
if len(opts) != 0 {
t.Error("ProgramOptions() 应返回空切片以避免重复注册函数")
}
})
t.Run("GetBaseProgramOptions返回非空", func(t *testing.T) {
// 函数实现通过 GetBaseProgramOptions() 提供
opts := GetBaseProgramOptions()
if len(opts) == 0 {
t.Error("GetBaseProgramOptions() 返回空切片")
}
})
}
func TestUpdateCompileOptions(t *testing.T) {
customLib := NewEnvOption()
tests := []struct {
name string
args StrMap
checkVar string
}{
{
name: "添加randomInt变量",
args: StrMap{
{Key: "myrand", Value: "randomInt(1, 100)"},
},
checkVar: "myrand",
},
{
name: "添加字符串变量",
args: StrMap{
{Key: "mystr", Value: "somevalue"},
},
checkVar: "mystr",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
initialLen := len(customLib.envOptions)
customLib.UpdateCompileOptions(tt.args)
if len(customLib.envOptions) <= initialLen {
t.Error("UpdateCompileOptions() 未添加新的环境选项")
}
})
}
}
func TestBcontainsBytes(t *testing.T) {
customLib := NewEnvOption()
env, err := NewEnv(&customLib)
if err != nil {
t.Fatalf("创建 CEL 环境失败: %v", err)
}
tests := []struct {
name string
haystack string
needle string
expected bool
}{
{"包含", "hello world", "world", true},
{"不包含", "hello", "world", false},
{"空needle", "hello", "", true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
expr := `b"` + tt.haystack + `".bcontains(b"` + tt.needle + `")`
result, err := Evaluate(env, expr, map[string]interface{}{})
if err != nil {
t.Fatalf("表达式评估失败: %v", err)
}
b, ok := result.(types.Bool)
if !ok {
t.Fatalf("返回值类型错误,期望 Bool,实际 %T", result)
}
if bool(b) != tt.expected {
t.Errorf("bcontains() = %v, want %v", b, tt.expected)
}
})
}
}
func TestBmatches(t *testing.T) {
customLib := NewEnvOption()
env, err := NewEnv(&customLib)
if err != nil {
t.Fatalf("创建 CEL 环境失败: %v", err)
}
tests := []struct {
name string
pattern string
text string
expected bool
}{
{"匹配数字", `\d+`, "abc123", true},
{"匹配邮箱", `\w+@\w+\.\w+`, "[email protected]", true},
{"不匹配", `\d+`, "abcdef", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// 需要转义反斜杠
pattern := strings.ReplaceAll(tt.pattern, `\`, `\\`)
expr := `"` + pattern + `".bmatches(b"` + tt.text + `")`
result, err := Evaluate(env, expr, map[string]interface{}{})
if err != nil {
t.Fatalf("表达式评估失败: %v", err)
}
b, ok := result.(types.Bool)
if !ok {
t.Fatalf("返回值类型错误,期望 Bool,实际 %T", result)
}
if bool(b) != tt.expected {
t.Errorf("bmatches() = %v, want %v", b, tt.expected)
}
})
}
}
// =============================================================================
// HTTP 解析函数测试
// =============================================================================
func TestParseURL(t *testing.T) {
tests := []struct {
name string
input string
expected *UrlType
}{
{
name: "完整URL",
input: "https://example.com:443/api/v1/users?page=1&size=10#section1",
expected: &UrlType{
Scheme: "https",
Domain: "example.com",
Host: "example.com:443",
Port: "443",
Path: "/api/v1/users",
Query: "page=1&size=10",
Fragment: "section1",
},
},
{
name: "HTTP URL",
input: "http://example.com/test",
expected: &UrlType{
Scheme: "http",
Domain: "example.com",
Host: "example.com",
Port: "",
Path: "/test",
Query: "",
},
},
{
name: "仅域名",
input: "https://example.com",
expected: &UrlType{
Scheme: "https",
Domain: "example.com",
Host: "example.com",
Port: "",
Path: "",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
u, err := url.Parse(tt.input)
if err != nil {
t.Fatalf("URL 解析失败: %v", err)
}
result := ParseURL(u)
if result.Scheme != tt.expected.Scheme {
t.Errorf("Scheme = %q, want %q", result.Scheme, tt.expected.Scheme)
}
if result.Domain != tt.expected.Domain {
t.Errorf("Domain = %q, want %q", result.Domain, tt.expected.Domain)
}
if result.Host != tt.expected.Host {
t.Errorf("Host = %q, want %q", result.Host, tt.expected.Host)
}
if result.Port != tt.expected.Port {
t.Errorf("Port = %q, want %q", result.Port, tt.expected.Port)
}
if result.Path != tt.expected.Path {
t.Errorf("Path = %q, want %q", result.Path, tt.expected.Path)
}
if result.Query != tt.expected.Query {
t.Errorf("Query = %q, want %q", result.Query, tt.expected.Query)
}
})
}
}
func TestParseRequest(t *testing.T) {
tests := []struct {
name string
method string
url string
headers map[string]string
body string
wantMethod string
wantPath string
wantHeaders int
}{
{
name: "GET请求",
method: "GET",
url: "http://example.com/api/test",
headers: map[string]string{
"User-Agent": "test-agent",
"Accept": "application/json",
},
body: "",
wantMethod: "GET",
wantPath: "/api/test",
wantHeaders: 2,
},
{
name: "POST请求带Body",
method: "POST",
url: "http://example.com/api/login",
headers: map[string]string{
"Content-Type": "application/json",
},
body: `{"username":"admin","password":"123456"}`,
wantMethod: "POST",
wantPath: "/api/login",
wantHeaders: 1,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
u, _ := url.Parse(tt.url)
var bodyReader io.Reader
if tt.body != "" {
bodyReader = strings.NewReader(tt.body)
}
httpReq, err := http.NewRequest(tt.method, u.String(), bodyReader)
if err != nil {
t.Fatalf("创建 HTTP 请求失败: %v", err)
}
for k, v := range tt.headers {
httpReq.Header.Set(k, v)
}
req, err := ParseRequest(httpReq)
if err != nil {
t.Fatalf("ParseRequest() error = %v", err)
}
if req.Method != tt.wantMethod {
t.Errorf("Method = %q, want %q", req.Method, tt.wantMethod)
}
if req.URL.Path != tt.wantPath {
t.Errorf("Path = %q, want %q", req.URL.Path, tt.wantPath)
}
if len(req.Headers) < tt.wantHeaders {
t.Errorf("Headers count = %d, want at least %d", len(req.Headers), tt.wantHeaders)
}
if tt.body != "" && string(req.Body) != tt.body {
t.Errorf("Body = %q, want %q", req.Body, tt.body)
}
})
}
}
func TestGetRespBody(t *testing.T) {
tests := []struct {
name string
body string
compress bool
wantContent string
wantErr bool
}{
{
name: "普通响应体",
body: "Hello, World!",
compress: false,
wantContent: "Hello, World!",
wantErr: false,
},
{
name: "空响应体",
body: "",
compress: false,
wantContent: "",
wantErr: false,
},
{
name: "JSON响应",
body: `{"status":"success","data":{"id":1}}`,
compress: false,
wantContent: `{"status":"success","data":{"id":1}}`,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// 创建模拟响应
resp := &http.Response{
Header: make(http.Header),
}
var bodyReader io.ReadCloser
if tt.compress {
resp.Header.Set("Content-Encoding", "gzip")
// 这里需要 gzip 压缩,暂时跳过
t.Skip("gzip 测试需要额外实现")
} else {
bodyReader = io.NopCloser(strings.NewReader(tt.body))
}
resp.Body = bodyReader
result, err := getRespBody(resp)
if (err != nil) != tt.wantErr {
t.Errorf("getRespBody() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !tt.wantErr && string(result) != tt.wantContent {
t.Errorf("getRespBody() = %q, want %q", result, tt.wantContent)
}
})
}
}
func TestDoRequestBuffersUnknownLengthBody(t *testing.T) {
previous := ClientNoRedirect
defer func() { ClientNoRedirect = previous }()
var gotContentLength string
var gotBody string
ClientNoRedirect = &http.Client{Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
gotContentLength = req.Header.Get("Content-Length")
body, err := io.ReadAll(req.Body)
if err != nil {
return nil, err
}
gotBody = string(body)
return &http.Response{
StatusCode: http.StatusOK,
Header: make(http.Header),
Body: io.NopCloser(strings.NewReader("ok")),
Request: req,
}, nil
})}
req, err := http.NewRequest(http.MethodPost, "http://example.com", io.NopCloser(strings.NewReader("abc")))
if err != nil {
t.Fatalf("NewRequest error = %v", err)
}
req.ContentLength = -1
if _, err := DoRequest(req, false, nil); err != nil {
t.Fatalf("DoRequest error = %v", err)
}
if gotContentLength != "3" {
t.Fatalf("Content-Length = %q, want 3", gotContentLength)
}
if gotBody != "abc" {
t.Fatalf("body = %q, want abc", gotBody)
}
}
func TestDoRequestReplaysBodyForGMTLSFallback(t *testing.T) {
previousNR, previousGM := ClientNoRedirect, ClientNoRedirectGM
defer func() {
ClientNoRedirect = previousNR
ClientNoRedirectGM = previousGM
}()
ClientNoRedirect = &http.Client{Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
_, _ = io.ReadAll(req.Body)
return nil, errors.New("standard tls failed")
})}
var gotBody string
ClientNoRedirectGM = &http.Client{Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
body, err := io.ReadAll(req.Body)
if err != nil {
return nil, err
}
gotBody = string(body)
return &http.Response{
StatusCode: http.StatusOK,
Header: make(http.Header),
Body: io.NopCloser(strings.NewReader("ok")),
Request: req,
}, nil
})}
req, err := http.NewRequest(http.MethodPost, "https://example.com", strings.NewReader("payload"))
if err != nil {
t.Fatalf("NewRequest error = %v", err)
}
if _, err := DoRequest(req, false, nil); err != nil {
t.Fatalf("DoRequest error = %v", err)
}
if gotBody != "payload" {
t.Fatalf("fallback body = %q, want payload", gotBody)
}
}
func TestRandomStr(t *testing.T) {
tests := []struct {
name string
charset string
length int
checkRange func(c rune) bool
}{
{
name: "数字字符集",
charset: "0123456789",
length: 10,
checkRange: func(c rune) bool {
return c >= '0' && c <= '9'
},
},
{
name: "小写字母字符集",
charset: "abcdefghijklmnopqrstuvwxyz",
length: 15,
checkRange: func(c rune) bool {
return c >= 'a' && c <= 'z'
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
for i := 0; i < 5; i++ { // 运行多次确保稳定性
result := RandomStr(randSource, tt.charset, tt.length)
if len(result) != tt.length {
t.Errorf("RandomStr() 长度 = %d, want %d", len(result), tt.length)
}
for _, c := range result {
if !tt.checkRange(c) {
t.Errorf("RandomStr() 包含无效字符: %c", c)
break
}
}
}
})
}
}