mirror of
https://github.com/shadow1ng/fscan.git
synced 2026-09-22 03:10:42 +08:00
test: 补充单元测试覆盖率 29.9% → 36.6%
新建 18 个测试文件,追加 30 个已有测试文件,覆盖协议解析、 错误分类、CEL 表达式求值、YAML 反序列化、字节编码等纯函数。
This commit is contained in:
@@ -3,6 +3,7 @@ package common
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
fscanconfig "github.com/shadow1ng/fscan/common/config"
|
||||
)
|
||||
@@ -192,3 +193,258 @@ func TestNormalizeURLBracketsIPv6Literals(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestModuleTimeout 测试模块超时计算
|
||||
func TestModuleTimeout(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
timeout time.Duration
|
||||
want time.Duration
|
||||
}{
|
||||
{"超时大于下限", 10 * time.Second, 10 * time.Second},
|
||||
{"超时等于下限", 3 * time.Second, 3 * time.Second},
|
||||
{"超时小于下限", 1 * time.Second, 3 * time.Second},
|
||||
{"零超时", 0, 3 * time.Second},
|
||||
{"负超时", -1 * time.Second, 3 * time.Second},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
cfg := NewConfig()
|
||||
cfg.Timeout = tt.timeout
|
||||
got := cfg.ModuleTimeout()
|
||||
if got != tt.want {
|
||||
t.Errorf("ModuleTimeout() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestParseUserPassPairsExactMatch 测试精确单用户单密码路径
|
||||
func TestParseUserPassPairsExactMatch(t *testing.T) {
|
||||
fv := &FlagVars{
|
||||
Username: "admin",
|
||||
Password: "secret",
|
||||
}
|
||||
pairs, err := parseUserPassPairs(fv)
|
||||
if err != nil {
|
||||
t.Fatalf("parseUserPassPairs error = %v", err)
|
||||
}
|
||||
if len(pairs) != 1 {
|
||||
t.Fatalf("期望 1 个 pair, 实际 %d", len(pairs))
|
||||
}
|
||||
if pairs[0].Username != "admin" || pairs[0].Password != "secret" {
|
||||
t.Errorf("pair = %+v, want {admin secret}", pairs[0])
|
||||
}
|
||||
}
|
||||
|
||||
// TestParseUserPassPairsMultiUserSkips 测试多用户时不生成精确 pair
|
||||
func TestParseUserPassPairsMultiUserSkips(t *testing.T) {
|
||||
fv := &FlagVars{
|
||||
Username: "admin,root",
|
||||
Password: "pass",
|
||||
}
|
||||
pairs, err := parseUserPassPairs(fv)
|
||||
if err != nil {
|
||||
t.Fatalf("parseUserPassPairs error = %v", err)
|
||||
}
|
||||
if len(pairs) != 0 {
|
||||
t.Fatalf("多用户场景不应生成精确 pair, 实际 %d 个", len(pairs))
|
||||
}
|
||||
}
|
||||
|
||||
// TestParseURLsEmpty 测试空输入返回空列表
|
||||
func TestParseURLsEmpty(t *testing.T) {
|
||||
fv := &FlagVars{}
|
||||
urls, err := parseURLs(fv)
|
||||
if err != nil {
|
||||
t.Fatalf("parseURLs error = %v", err)
|
||||
}
|
||||
if len(urls) != 0 {
|
||||
t.Fatalf("空输入应返回空 url 列表, 实际 %v", urls)
|
||||
}
|
||||
}
|
||||
|
||||
// TestParseURLsCommaSeparated 测试逗号分隔多 URL
|
||||
func TestParseURLsCommaSeparated(t *testing.T) {
|
||||
fv := &FlagVars{
|
||||
TargetURL: "http://a.com,http://b.com,http://a.com", // 含重复
|
||||
}
|
||||
urls, err := parseURLs(fv)
|
||||
if err != nil {
|
||||
t.Fatalf("parseURLs error = %v", err)
|
||||
}
|
||||
if len(urls) != 2 {
|
||||
t.Fatalf("去重后应有 2 个 url, 实际 %d: %v", len(urls), urls)
|
||||
}
|
||||
}
|
||||
|
||||
// TestParseURLsMissingFile 测试缺失文件返回错误
|
||||
func TestParseURLsMissingFile(t *testing.T) {
|
||||
fv := &FlagVars{URLsFile: "nonexistent-urls.txt"}
|
||||
_, err := parseURLs(fv)
|
||||
if err == nil {
|
||||
t.Fatal("缺失文件应返回错误")
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// parseHashes
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// TestParseHashesEmpty 空输入返回空结果
|
||||
func TestParseHashesEmpty(t *testing.T) {
|
||||
fv := &FlagVars{}
|
||||
vals, bytes, err := parseHashes(fv)
|
||||
if err != nil {
|
||||
t.Fatalf("parseHashes error = %v", err)
|
||||
}
|
||||
if len(vals) != 0 || len(bytes) != 0 {
|
||||
t.Fatalf("空输入应返回空结果, vals=%v bytes=%v", vals, bytes)
|
||||
}
|
||||
}
|
||||
|
||||
// TestParseHashesValidNTLM 纯 32 字符 hex hash
|
||||
func TestParseHashesValidNTLM(t *testing.T) {
|
||||
hash := "aabbccddeeff00112233445566778899"
|
||||
fv := &FlagVars{HashValue: hash}
|
||||
vals, hashBytes, err := parseHashes(fv)
|
||||
if err != nil {
|
||||
t.Fatalf("parseHashes error = %v", err)
|
||||
}
|
||||
if len(vals) != 1 || vals[0] != hash {
|
||||
t.Fatalf("vals = %v, want [%s]", vals, hash)
|
||||
}
|
||||
if len(hashBytes) != 1 || len(hashBytes[0]) != 16 {
|
||||
t.Fatalf("hashBytes length wrong: %v", hashBytes)
|
||||
}
|
||||
}
|
||||
|
||||
// TestParseHashesLMNTFormat LM:NT 格式,提取 NT 部分
|
||||
func TestParseHashesLMNTFormat(t *testing.T) {
|
||||
lm := "aad3b435b51404eeaad3b435b51404ee"
|
||||
nt := "31d6cfe0d16ae931b73c59d7e0c089c0"
|
||||
fv := &FlagVars{HashValue: lm + ":" + nt}
|
||||
vals, _, err := parseHashes(fv)
|
||||
if err != nil {
|
||||
t.Fatalf("parseHashes error = %v", err)
|
||||
}
|
||||
if len(vals) != 1 || vals[0] != nt {
|
||||
t.Fatalf("vals = %v, want [%s]", vals, nt)
|
||||
}
|
||||
}
|
||||
|
||||
// TestParseHashesInvalidLength hash 长度不是 32 → error
|
||||
func TestParseHashesInvalidLength(t *testing.T) {
|
||||
fv := &FlagVars{HashValue: "tooshort"}
|
||||
_, _, err := parseHashes(fv)
|
||||
if err == nil {
|
||||
t.Fatal("hash 长度不足应返回错误")
|
||||
}
|
||||
}
|
||||
|
||||
// TestParseHashesInvalidHex 32 字符但含非 hex 字符 → error
|
||||
func TestParseHashesInvalidHex(t *testing.T) {
|
||||
fv := &FlagVars{HashValue: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz"}
|
||||
_, _, err := parseHashes(fv)
|
||||
if err == nil {
|
||||
t.Fatal("非 hex 字符应返回错误")
|
||||
}
|
||||
}
|
||||
|
||||
// TestParseHashesMissingFile hash 文件不存在 → error
|
||||
func TestParseHashesMissingFile(t *testing.T) {
|
||||
fv := &FlagVars{HashFile: "nonexistent-hashes.txt"}
|
||||
_, _, err := parseHashes(fv)
|
||||
if err == nil {
|
||||
t.Fatal("缺失 hash 文件应返回错误")
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// parseUsernames
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// TestParseUsernamesEmpty 空输入返回空结果
|
||||
func TestParseUsernamesEmpty(t *testing.T) {
|
||||
fv := &FlagVars{}
|
||||
got, err := parseUsernames(fv)
|
||||
if err != nil {
|
||||
t.Fatalf("parseUsernames error = %v", err)
|
||||
}
|
||||
if len(got) != 0 {
|
||||
t.Fatalf("空输入应返回空, got %v", got)
|
||||
}
|
||||
}
|
||||
|
||||
// TestParseUsernamesCommaSeparated 逗号分隔多用户
|
||||
func TestParseUsernamesCommaSeparated(t *testing.T) {
|
||||
fv := &FlagVars{Username: "admin, root, admin"} // 含重复和空格
|
||||
got, err := parseUsernames(fv)
|
||||
if err != nil {
|
||||
t.Fatalf("parseUsernames error = %v", err)
|
||||
}
|
||||
want := []string{"admin", "root"}
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Fatalf("got %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestParseUsernamesAddUsers AddUsers 追加去重
|
||||
func TestParseUsernamesAddUsers(t *testing.T) {
|
||||
fv := &FlagVars{
|
||||
Username: "admin",
|
||||
AddUsers: "root,admin", // admin 重复
|
||||
}
|
||||
got, err := parseUsernames(fv)
|
||||
if err != nil {
|
||||
t.Fatalf("parseUsernames error = %v", err)
|
||||
}
|
||||
want := []string{"admin", "root"}
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Fatalf("got %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestParseUsernamesMissingFile 缺失用户文件 → error
|
||||
func TestParseUsernamesMissingFile(t *testing.T) {
|
||||
fv := &FlagVars{UsersFile: "nonexistent-users.txt"}
|
||||
_, err := parseUsernames(fv)
|
||||
if err == nil {
|
||||
t.Fatal("缺失用户文件应返回错误")
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// cloneStringSlice
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// TestCloneStringSliceNil nil 输入返回 nil
|
||||
func TestCloneStringSliceNil(t *testing.T) {
|
||||
got := cloneStringSlice(nil)
|
||||
if got != nil {
|
||||
t.Fatalf("nil 输入应返回 nil, got %v", got)
|
||||
}
|
||||
}
|
||||
|
||||
// TestCloneStringSliceEmpty 空切片:append 无元素结果为 nil,len 为 0
|
||||
func TestCloneStringSliceEmpty(t *testing.T) {
|
||||
got := cloneStringSlice([]string{})
|
||||
if len(got) != 0 {
|
||||
t.Fatalf("got len %d, want 0", len(got))
|
||||
}
|
||||
}
|
||||
|
||||
// TestCloneStringSliceCopiesValues 正常切片:值正确且独立
|
||||
func TestCloneStringSliceCopiesValues(t *testing.T) {
|
||||
src := []string{"a", "b", "c"}
|
||||
got := cloneStringSlice(src)
|
||||
if !reflect.DeepEqual(got, src) {
|
||||
t.Fatalf("got %v, want %v", got, src)
|
||||
}
|
||||
// 修改 clone 不影响原始
|
||||
got[0] = "mutated"
|
||||
if src[0] != "a" {
|
||||
t.Fatal("cloneStringSlice 返回的切片与源共享底层数组")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user