test: 扩展parsers单元测试覆盖边缘情况

- 新增内网简写解析测试(192/172/10)
- 新增完整IP范围和无效CIDR测试
- 新增Windows行尾(CRLF)处理测试
- 新增凭据和哈希文件解析测试
- 新增端口解析边缘情况测试
- 测试覆盖率达到94.2%
This commit is contained in:
ZacharyZcR
2026-01-20 11:47:51 +08:00
parent e23d89dae2
commit e86dd1a231
+434
View File
@@ -843,3 +843,437 @@ func TestRemoveDuplicatePorts(t *testing.T) {
t.Logf("✓ removeDuplicatePorts: %d → %d", len(input), len(result))
}
// =============================================================================
// 边缘情况测试 - IP解析
// =============================================================================
// TestParseIP_InternalNetworkShortcuts 测试内网简写
func TestParseIP_InternalNetworkShortcuts(t *testing.T) {
tests := []struct {
name string
shortcut string
expectMin int // 最少应该有多少IP
sampleCheck string
}{
{
"192简写",
"192",
100, // 192.168.0.0/16 应该很多
"192.168.",
},
{
"172简写",
"172",
100, // 172.16.0.0/12 应该很多
"172.",
},
{
"10简写",
"10",
100, // 10.0.0.0/8 应该很多
"10.",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := ParseIP(tt.shortcut, "", "")
if err != nil {
t.Fatalf("ParseIP(%q) error = %v", tt.shortcut, err)
}
if len(result) < tt.expectMin {
t.Errorf("ParseIP(%q) 返回%d个IP,期望至少%d个",
tt.shortcut, len(result), tt.expectMin)
}
// 检查样本
hasMatch := false
for _, ip := range result[:min(10, len(result))] {
if len(ip) >= len(tt.sampleCheck) && ip[:len(tt.sampleCheck)] == tt.sampleCheck {
hasMatch = true
break
}
}
if !hasMatch {
t.Errorf("ParseIP(%q) 结果不包含预期前缀 %q", tt.shortcut, tt.sampleCheck)
}
t.Logf("✓ ParseIP(%q) → %d个IP(内网简写展开正确)", tt.shortcut, len(result))
})
}
}
// TestParseIP_FullIPRange 测试完整IP范围格式
func TestParseIP_FullIPRange(t *testing.T) {
tests := []struct {
name string
rangeStr string
expectCount int
expectFirst string
expectLast string
}{
{
"完整范围小",
"192.168.1.1-192.168.1.5",
5,
"192.168.1.1",
"192.168.1.5",
},
{
"跨子网范围",
"192.168.1.254-192.168.2.2",
5, // .254, .255, .0, .1, .2
"192.168.1.254",
"192.168.2.2",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := ParseIP(tt.rangeStr, "", "")
if err != nil {
t.Fatalf("ParseIP(%q) error = %v", tt.rangeStr, err)
}
if len(result) != tt.expectCount {
t.Errorf("ParseIP(%q) 返回%d个IP,期望%d个",
tt.rangeStr, len(result), tt.expectCount)
}
if len(result) > 0 && result[0] != tt.expectFirst {
t.Errorf("ParseIP(%q) 第一个IP = %q,期望 %q",
tt.rangeStr, result[0], tt.expectFirst)
}
if len(result) > 0 && result[len(result)-1] != tt.expectLast {
t.Errorf("ParseIP(%q) 最后一个IP = %q,期望 %q",
tt.rangeStr, result[len(result)-1], tt.expectLast)
}
t.Logf("✓ ParseIP(%q) → %v", tt.rangeStr, result)
})
}
}
// TestParseIP_InvalidCIDR 测试无效CIDR
func TestParseIP_InvalidCIDR(t *testing.T) {
tests := []struct {
name string
cidr string
expectErr bool
}{
{"无效掩码/33", "192.168.1.0/33", true},
{"无效掩码/0", "192.168.1.0/0", false}, // /0 技术上是有效的
{"格式错误", "192.168.1.0/abc", true},
{"缺少掩码", "192.168.1.0/", true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := ParseIP(tt.cidr, "", "")
if tt.expectErr && err == nil {
t.Errorf("ParseIP(%q) 应该返回错误", tt.cidr)
}
if !tt.expectErr && err != nil {
t.Errorf("ParseIP(%q) 不应该返回错误: %v", tt.cidr, err)
}
t.Logf("✓ ParseIP(%q) 错误处理正确", tt.cidr)
})
}
}
// TestParseIP_InvalidIPRange 测试无效IP范围
func TestParseIP_InvalidIPRange(t *testing.T) {
tests := []struct {
name string
rangeStr string
expectErr bool
}{
{"起始大于结束", "192.168.1.100-50", true},
{"无效起始IP", "999.999.999.999-192.168.1.100", true},
{"结束值超255", "192.168.1.1-256", true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := ParseIP(tt.rangeStr, "", "")
if tt.expectErr && err == nil {
t.Errorf("ParseIP(%q) 应该返回错误", tt.rangeStr)
}
t.Logf("✓ ParseIP(%q) 错误处理正确", tt.rangeStr)
})
}
}
// =============================================================================
// 边缘情况测试 - 文件读取
// =============================================================================
// TestReadLinesFromFile_WindowsLineEndings 测试Windows行尾
func TestReadLinesFromFile_WindowsLineEndings(t *testing.T) {
tmpDir := t.TempDir()
testFile := filepath.Join(tmpDir, "crlf.txt")
// Windows风格行尾: CRLF
content := "line1\r\nline2\r\nline3\r\n"
if err := os.WriteFile(testFile, []byte(content), 0600); err != nil {
t.Fatalf("创建测试文件失败: %v", err)
}
result, err := ReadLinesFromFile(testFile)
if err != nil {
t.Fatalf("ReadLinesFromFile error = %v", err)
}
// 应该有3行,且不包含\r
if len(result) != 3 {
t.Errorf("ReadLinesFromFile 返回%d行,期望3行", len(result))
}
for i, line := range result {
if len(line) > 0 && line[len(line)-1] == '\r' {
t.Errorf("第%d行包含\\r: %q", i+1, line)
}
}
t.Logf("✓ Windows行尾处理正确: %v", result)
}
// TestReadLinesFromFile_OnlyComments 测试只有注释的文件
func TestReadLinesFromFile_OnlyComments(t *testing.T) {
tmpDir := t.TempDir()
testFile := filepath.Join(tmpDir, "comments.txt")
content := `# comment 1
# comment 2
# comment 3
`
if err := os.WriteFile(testFile, []byte(content), 0600); err != nil {
t.Fatalf("创建测试文件失败: %v", err)
}
result, err := ReadLinesFromFile(testFile)
if err != nil {
t.Fatalf("ReadLinesFromFile error = %v", err)
}
if len(result) != 0 {
t.Errorf("只有注释的文件应该返回空列表,实际返回: %v", result)
}
t.Logf("✓ 只有注释的文件正确返回空列表")
}
// TestReadLinesFromFile_EmptyFile 测试空文件
func TestReadLinesFromFile_EmptyFile(t *testing.T) {
tmpDir := t.TempDir()
testFile := filepath.Join(tmpDir, "empty.txt")
if err := os.WriteFile(testFile, []byte(""), 0600); err != nil {
t.Fatalf("创建测试文件失败: %v", err)
}
result, err := ReadLinesFromFile(testFile)
if err != nil {
t.Fatalf("ReadLinesFromFile error = %v", err)
}
if len(result) != 0 {
t.Errorf("空文件应该返回空列表,实际返回: %v", result)
}
t.Logf("✓ 空文件正确返回空列表")
}
// TestReadLinesFromFile_MixedContent 测试混合内容
func TestReadLinesFromFile_MixedContent(t *testing.T) {
tmpDir := t.TempDir()
testFile := filepath.Join(tmpDir, "mixed.txt")
content := `# Header comment
192.168.1.1
# indented comment
192.168.1.2
192.168.1.3
# trailing comment
`
if err := os.WriteFile(testFile, []byte(content), 0600); err != nil {
t.Fatalf("创建测试文件失败: %v", err)
}
result, err := ReadLinesFromFile(testFile)
if err != nil {
t.Fatalf("ReadLinesFromFile error = %v", err)
}
expected := []string{"192.168.1.1", "192.168.1.2", "192.168.1.3"}
if !reflect.DeepEqual(result, expected) {
t.Errorf("ReadLinesFromFile = %v, want %v", result, expected)
}
t.Logf("✓ 混合内容处理正确: %v", result)
}
// =============================================================================
// 边缘情况测试 - 凭据解析
// =============================================================================
// TestParseUserPassFile 测试用户密码文件解析
func TestParseUserPassFile(t *testing.T) {
tmpDir := t.TempDir()
testFile := filepath.Join(tmpDir, "userpass.txt")
content := `admin:password123
root:toor
# comment line
user:pass:with:colons
:emptyuser
nopassword
test:
`
if err := os.WriteFile(testFile, []byte(content), 0600); err != nil {
t.Fatalf("创建测试文件失败: %v", err)
}
result, err := ParseUserPassFile(testFile)
if err != nil {
t.Fatalf("ParseUserPassFile error = %v", err)
}
// 验证解析结果
tests := []struct {
username string
password string
}{
{"admin", "password123"},
{"root", "toor"},
{"user", "pass:with:colons"}, // 密码可以包含冒号
{"test", ""}, // 空密码
}
if len(result) != len(tests) {
t.Errorf("ParseUserPassFile 返回%d对,期望%d对", len(result), len(tests))
}
for i, tt := range tests {
if i >= len(result) {
break
}
if result[i].Username != tt.username || result[i].Password != tt.password {
t.Errorf("第%d对: got (%q, %q), want (%q, %q)",
i, result[i].Username, result[i].Password, tt.username, tt.password)
}
}
t.Logf("✓ 用户密码文件解析正确: %d对", len(result))
}
// TestParseHashFile 测试哈希文件解析
func TestParseHashFile(t *testing.T) {
tmpDir := t.TempDir()
testFile := filepath.Join(tmpDir, "hashes.txt")
content := `# MD5 hashes
5d41402abc4b2a76b9719d911017c592
098f6bcd4621d373cade4e832627b4f6
# invalid hash (too short)
5d41402abc4b2a76
# invalid hash (non-hex)
zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
# valid hash
d41d8cd98f00b204e9800998ecf8427e
`
if err := os.WriteFile(testFile, []byte(content), 0600); err != nil {
t.Fatalf("创建测试文件失败: %v", err)
}
hashValues, hashBytes, err := ParseHashFile(testFile)
if err != nil {
t.Fatalf("ParseHashFile error = %v", err)
}
// 应该只有3个有效hash
if len(hashValues) != 3 {
t.Errorf("ParseHashFile 返回%d个hash,期望3个", len(hashValues))
}
if len(hashBytes) != 3 {
t.Errorf("ParseHashFile hashBytes 返回%d个,期望3个", len(hashBytes))
}
// 验证hash bytes长度
for i, hb := range hashBytes {
if len(hb) != 16 { // MD5 = 16 bytes
t.Errorf("hashBytes[%d] 长度=%d,期望16", i, len(hb))
}
}
t.Logf("✓ 哈希文件解析正确: %d个有效hash", len(hashValues))
}
// =============================================================================
// 边缘情况测试 - 端口解析
// =============================================================================
// TestParsePort_LargeRange 测试大范围端口
func TestParsePort_LargeRange(t *testing.T) {
result := ParsePort("1-1000")
if len(result) != 1000 {
t.Errorf("ParsePort(1-1000) 返回%d个端口,期望1000个", len(result))
}
// 验证第一个和最后一个
if result[0] != 1 || result[len(result)-1] != 1000 {
t.Errorf("ParsePort(1-1000) 范围不正确: first=%d, last=%d",
result[0], result[len(result)-1])
}
t.Logf("✓ 大范围端口解析正确: %d个", len(result))
}
// TestParsePort_EmptyElements 测试空元素
func TestParsePort_EmptyElements(t *testing.T) {
tests := []struct {
name string
input string
expected []int
}{
{"连续逗号", "80,,443", []int{80, 443}},
{"开头逗号", ",80,443", []int{80, 443}},
{"结尾逗号", "80,443,", []int{80, 443}},
{"多个空", "80,,,443,,,", []int{80, 443}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := ParsePort(tt.input)
if !reflect.DeepEqual(result, tt.expected) {
t.Errorf("ParsePort(%q) = %v, want %v", tt.input, result, tt.expected)
}
t.Logf("✓ ParsePort(%q) 正确处理空元素", tt.input)
})
}
}
// =============================================================================
// 辅助函数
// =============================================================================
func min(a, b int) int {
if a < b {
return a
}
return b
}