mirror of
https://github.com/shadow1ng/fscan.git
synced 2026-09-22 03:10:42 +08:00
将 webServiceCache 扩展为通用 serviceCache,所有指纹识别结果 统一缓存,插件匹配时端口不命中则回退到服务名称匹配。 删除多余的 service_cache.go,复用已有的 ServiceInfo 体系。 补充 nil 防御、Explicit 标记、大量单元/集成/回归测试。
1211 lines
29 KiB
Go
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
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|