package parsers import ( "os" "path/filepath" "reflect" "sort" "testing" ) /* parse_test.go - 简化解析器测试 测试目标:ParseIP和ParsePort两个核心解析函数 价值:解析错误会导致: - 错误的扫描目标(用户扫描了错误的主机) - 错误的端口范围(遗漏关键服务) - 性能问题(重复目标导致浪费) "解析器是扫描器的入口。解析错误=整个扫描就是错的。 端口范围解析bug会让用户遗漏漏洞。这是真实问题。" */ // ============================================================================= // ParsePort - 端口解析测试 // ============================================================================= // TestParsePort_Empty 测试空字符串 // // 验证:空输入返回nil而不是空切片 // // empty slice表示'有数据但是空的'。这个区别很重要。" func TestParsePort_Empty(t *testing.T) { result := ParsePort("") if result != nil { t.Errorf("ParsePort(\"\") = %v, want nil", result) } t.Logf("✓ 空字符串正确返回nil") } // TestParsePort_SinglePort 测试单个端口 func TestParsePort_SinglePort(t *testing.T) { tests := []struct { name string input string expected []int }{ {"HTTP", "80", []int{80}}, {"HTTPS", "443", []int{443}}, {"SSH", "22", []int{22}}, {"MinPort", "1", []int{1}}, {"MaxPort", "65535", []int{65535}}, } 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) → %v", tt.input, result) }) } } // TestParsePort_MultiplePorts 测试多个端口 func TestParsePort_MultiplePorts(t *testing.T) { tests := []struct { name string input string expected []int }{ { "Web端口", "80,443,8080", []int{80, 443, 8080}, }, { "数据库端口", "3306,5432,27017", []int{3306, 5432, 27017}, }, { "带空格", " 80 , 443 , 8080 ", []int{80, 443, 8080}, }, } 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) → %v", tt.input, result) }) } } // TestParsePort_PortRange 测试端口范围 // // 验证:范围解析正确,包含起始和结束端口 // // 1-5应该是[1,2,3,4,5]还是[1,2,3,4]?搞错了就是bug。" func TestParsePort_PortRange(t *testing.T) { tests := []struct { name string input string expected []int }{ { "小范围", "1-5", []int{1, 2, 3, 4, 5}, }, { "HTTP备用端口", "8000-8003", []int{8000, 8001, 8002, 8003}, }, { "单端口范围", "80-80", []int{80}, }, } 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) → %v", tt.input, result) }) } } // TestParsePort_MixedFormat 测试混合格式 func TestParsePort_MixedFormat(t *testing.T) { tests := []struct { name string input string expected []int }{ { "端口+范围", "80,100-102,443", []int{80, 100, 101, 102, 443}, }, { "多个范围", "1-3,10-12", []int{1, 2, 3, 10, 11, 12}, }, { "复杂混合", "22,80-82,443,8000-8001", []int{22, 80, 81, 82, 443, 8000, 8001}, }, } 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) → %v", tt.input, result) }) } } // TestParsePort_InvalidRange 测试无效范围 // // 验证:无效范围被正确过滤 func TestParsePort_InvalidRange(t *testing.T) { tests := []struct { name string input string expected []int }{ { "反向范围", "100-50", nil, }, { "超出上限起始", "65536-65540", nil, }, { "低于下限", "0-5", nil, }, { "无效格式", "80-90-100", nil, }, { "非数字", "abc-xyz", nil, }, } 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) }) } } // TestParsePort_OutOfRange 测试超出范围的端口 func TestParsePort_OutOfRange(t *testing.T) { tests := []struct { name string input string expected []int }{ { "端口0", "0", nil, }, { "端口65536", "65536", nil, }, { "混合有效和无效", "0,80,443,65536", []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) }) } } // TestParsePort_Deduplicate 测试去重 // // 验证:重复端口被去重,结果已排序 func TestParsePort_Deduplicate(t *testing.T) { tests := []struct { name string input string expected []int }{ { "简单重复", "80,80,80", []int{80}, }, { "多个重复", "80,443,80,22,443", []int{22, 80, 443}, }, { "范围重复", "1-3,2-4", []int{1, 2, 3, 4}, }, } 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) } // 验证已排序 if !sort.IntsAreSorted(result) { t.Errorf("ParsePort(%q) 结果未排序: %v", tt.input, result) } t.Logf("✓ ParsePort(%q) 正确去重并排序 → %v", tt.input, result) }) } } // TestParsePort_Sorted 测试排序 func TestParsePort_Sorted(t *testing.T) { tests := []struct { name string input string }{ {"乱序端口", "8080,22,443,80"}, {"乱序范围", "1000-1002,80-82"}, {"混合乱序", "443,100-102,22,80"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := ParsePort(tt.input) if !sort.IntsAreSorted(result) { t.Errorf("ParsePort(%q) 结果未排序: %v", tt.input, result) } t.Logf("✓ ParsePort(%q) 结果已排序: %v", tt.input, result) }) } } // TestParsePort_PortGroups 测试端口组展开 // // 验证:预定义端口组被正确展开 func TestParsePort_PortGroups(t *testing.T) { tests := []struct { name string input string shouldContain []int shouldNotBeNil bool }{ { "web组", "web", []int{80, 443, 8080, 8443}, true, }, { "all组", "all", []int{1, 100, 1000, 10000, 65535}, true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := ParsePort(tt.input) if tt.shouldNotBeNil && result == nil { t.Errorf("ParsePort(%q) = nil, want non-nil", tt.input) return } // 验证包含特定端口 resultMap := make(map[int]bool) for _, port := range result { resultMap[port] = true } for _, port := range tt.shouldContain { if !resultMap[port] { t.Errorf("ParsePort(%q) 应该包含端口 %d,但不包含", tt.input, port) } } t.Logf("✓ ParsePort(%q) 正确展开端口组(%d个端口)", tt.input, len(result)) }) } } // TestParsePort_WhitespaceHandling 测试空格处理 func TestParsePort_WhitespaceHandling(t *testing.T) { tests := []struct { name string input string expected []int }{ { "端口前后空格", " 80 , 443 ", []int{80, 443}, }, { "范围中的空格", " 1 - 3 ", []int{1, 2, 3}, }, { "混合空格", " 80 , 100 - 102 , 443 ", []int{80, 100, 101, 102, 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) }) } } // ============================================================================= // ParseIP - IP解析测试 // ============================================================================= // TestParseIP_SingleIP 测试单个IP func TestParseIP_SingleIP(t *testing.T) { tests := []struct { name string host string expected []string }{ { "IPv4", "192.168.1.1", []string{"192.168.1.1"}, }, { "域名", "example.com", []string{"example.com"}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result, err := ParseIP(tt.host, "", "") if err != nil { t.Fatalf("ParseIP(%q) error = %v", tt.host, err) } if !reflect.DeepEqual(result, tt.expected) { t.Errorf("ParseIP(%q) = %v, want %v", tt.host, result, tt.expected) } t.Logf("✓ ParseIP(%q) → %v", tt.host, result) }) } } // TestParseIP_MultipleIPs 测试多个IP func TestParseIP_MultipleIPs(t *testing.T) { tests := []struct { name string host string expected []string }{ { "两个IP", "192.168.1.1,192.168.1.2", []string{"192.168.1.1", "192.168.1.2"}, }, { "三个IP带空格", " 192.168.1.1 , 192.168.1.2 , 192.168.1.3 ", []string{"192.168.1.1", "192.168.1.2", "192.168.1.3"}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result, err := ParseIP(tt.host, "", "") if err != nil { t.Fatalf("ParseIP(%q) error = %v", tt.host, err) } if !reflect.DeepEqual(result, tt.expected) { t.Errorf("ParseIP(%q) = %v, want %v", tt.host, result, tt.expected) } t.Logf("✓ ParseIP(%q) → %d个IP", tt.host, len(result)) }) } } // TestParseIP_CIDR 测试CIDR格式 // // 验证:CIDR被正确展开为IP列表 func TestParseIP_CIDR(t *testing.T) { tests := []struct { name string cidr string expectCount int }{ { "/30网络", "192.168.1.0/30", 2, // .1, .2 (排除网络地址和广播地址) }, { "/29网络", "10.0.0.0/29", 6, // .1-.6 }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result, err := ParseIP(tt.cidr, "", "") if err != nil { t.Fatalf("ParseIP(%q) error = %v", tt.cidr, err) } if len(result) != tt.expectCount { t.Errorf("ParseIP(%q) 返回%d个IP,期望%d个", tt.cidr, len(result), tt.expectCount) } // 验证已排序 if !sort.StringsAreSorted(result) { t.Errorf("ParseIP(%q) 结果未排序", tt.cidr) } t.Logf("✓ ParseIP(%q) → %d个IP", tt.cidr, len(result)) }) } } // TestParseIP_IPRange 测试IP范围 func TestParseIP_IPRange(t *testing.T) { tests := []struct { name string rangeStr string expectCount int }{ { "小范围", "192.168.1.1-3", 3, }, { "单IP范围", "192.168.1.1-1", 1, }, } 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) } t.Logf("✓ ParseIP(%q) → %d个IP", tt.rangeStr, len(result)) }) } } // TestParseIP_FromFile 测试从文件读取 // // 验证:文件中的IP列表被正确读取 func TestParseIP_FromFile(t *testing.T) { // 创建临时文件 tmpDir := t.TempDir() hostFile := filepath.Join(tmpDir, "hosts.txt") content := `# 这是注释 192.168.1.1 192.168.1.2 # 空行会被忽略 192.168.1.3 ` if err := os.WriteFile(hostFile, []byte(content), 0600); err != nil { t.Fatalf("创建测试文件失败: %v", err) } result, err := ParseIP("", hostFile, "") if err != nil { t.Fatalf("ParseIP(file=%q) error = %v", hostFile, err) } expected := []string{"192.168.1.1", "192.168.1.2", "192.168.1.3"} if !reflect.DeepEqual(result, expected) { t.Errorf("ParseIP(file) = %v, want %v", result, expected) } t.Logf("✓ 从文件读取%d个IP(正确过滤注释和空行)", len(result)) } // TestParseIP_FileNotFound 测试文件不存在 func TestParseIP_FileNotFound(t *testing.T) { _, err := ParseIP("", "nonexistent_file_12345.txt", "") if err == nil { t.Error("ParseIP(不存在的文件) 应该返回错误") } t.Logf("✓ 文件不存在时正确返回错误: %v", err) } // TestParseIP_Exclude 测试排除主机 // // 验证:排除列表中的主机被正确过滤 func TestParseIP_Exclude(t *testing.T) { tests := []struct { name string hosts string exclude string expected []string }{ { "排除单个", "192.168.1.1,192.168.1.2,192.168.1.3", "192.168.1.2", []string{"192.168.1.1", "192.168.1.3"}, }, { "排除多个", "192.168.1.1,192.168.1.2,192.168.1.3", "192.168.1.1,192.168.1.3", []string{"192.168.1.2"}, }, { "排除不存在的", "192.168.1.1,192.168.1.2", "192.168.1.100", []string{"192.168.1.1", "192.168.1.2"}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result, err := ParseIP(tt.hosts, "", tt.exclude) if err != nil { t.Fatalf("ParseIP error = %v", err) } if !reflect.DeepEqual(result, tt.expected) { t.Errorf("ParseIP(hosts=%q, exclude=%q) = %v, want %v", tt.hosts, tt.exclude, result, tt.expected) } t.Logf("✓ 正确排除指定主机: %d → %d", len(tt.expected)+len(result)-len(tt.expected), len(result)) }) } } // TestParseIP_Deduplicate 测试去重 func TestParseIP_Deduplicate(t *testing.T) { result, err := ParseIP("192.168.1.1,192.168.1.1,192.168.1.2,192.168.1.2", "", "") if err != nil { t.Fatalf("ParseIP error = %v", err) } expected := []string{"192.168.1.1", "192.168.1.2"} if !reflect.DeepEqual(result, expected) { t.Errorf("ParseIP(重复IP) = %v, want %v", result, expected) } t.Logf("✓ 正确去重: 4个输入 → %d个输出", len(result)) } // TestParseIP_Sorted 测试排序 func TestParseIP_Sorted(t *testing.T) { result, err := ParseIP("192.168.1.3,192.168.1.1,192.168.1.2", "", "") if err != nil { t.Fatalf("ParseIP error = %v", err) } if !sort.StringsAreSorted(result) { t.Errorf("ParseIP 结果未排序: %v", result) } t.Logf("✓ 结果已排序: %v", result) } // TestParseIP_NoHosts 测试无有效主机 func TestParseIP_NoHosts(t *testing.T) { _, err := ParseIP("", "", "") if err == nil { t.Error("ParseIP(空输入) 应该返回错误") } if err.Error() != "没有找到有效的主机" { t.Errorf("错误信息不匹配: %v", err) } t.Logf("✓ 无有效主机时正确返回错误") } // TestParseIP_MixedSources 测试混合来源 func TestParseIP_MixedSources(t *testing.T) { // 创建临时文件 tmpDir := t.TempDir() hostFile := filepath.Join(tmpDir, "hosts.txt") if err := os.WriteFile(hostFile, []byte("192.168.1.1\n192.168.1.2\n"), 0600); err != nil { t.Fatalf("创建测试文件失败: %v", err) } // 命令行 + 文件 result, err := ParseIP("192.168.1.3,192.168.1.4", hostFile, "") if err != nil { t.Fatalf("ParseIP error = %v", err) } expected := []string{"192.168.1.1", "192.168.1.2", "192.168.1.3", "192.168.1.4"} if !reflect.DeepEqual(result, expected) { t.Errorf("ParseIP(混合来源) = %v, want %v", result, expected) } t.Logf("✓ 正确合并多个来源: %d个IP", len(result)) } // ============================================================================= // 辅助函数测试 // ============================================================================= // TestParsePortRange 测试端口范围解析 func TestParsePortRange(t *testing.T) { tests := []struct { name string input string expected []int }{ {"正常范围", "1-5", []int{1, 2, 3, 4, 5}}, {"单端口", "80-80", []int{80}}, {"反向范围", "5-1", nil}, {"超出范围", "65535-65540", nil}, {"格式错误", "1-2-3", nil}, {"非数字", "a-b", nil}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := parsePortRange(tt.input) if !reflect.DeepEqual(result, tt.expected) { t.Errorf("parsePortRange(%q) = %v, want %v", tt.input, result, tt.expected) } }) } } // TestExcludeHosts 测试排除主机 func TestExcludeHosts(t *testing.T) { hosts := []string{"host1", "host2", "host3", "host4"} exclude := []string{"host2", "host4"} result := excludeHosts(hosts, exclude) expected := []string{"host1", "host3"} if !reflect.DeepEqual(result, expected) { t.Errorf("excludeHosts = %v, want %v", result, expected) } t.Logf("✓ excludeHosts: %d → %d", len(hosts), len(result)) } // TestExcludeHosts_EmptyExclude 测试空排除列表 func TestExcludeHosts_EmptyExclude(t *testing.T) { hosts := []string{"host1", "host2"} result := excludeHosts(hosts, []string{}) if !reflect.DeepEqual(result, hosts) { t.Errorf("excludeHosts(空排除列表) 应该返回原列表") } } // TestRemoveDuplicates 测试去重 func TestRemoveDuplicates(t *testing.T) { input := []string{"a", "b", "a", "c", "b", "d"} result := removeDuplicates(input) // 验证无重复 seen := make(map[string]bool) for _, item := range result { if seen[item] { t.Errorf("removeDuplicates 结果包含重复项: %s", item) } seen[item] = true } // 验证长度 if len(result) != 4 { t.Errorf("removeDuplicates 返回%d项,期望4项", len(result)) } t.Logf("✓ removeDuplicates: %d → %d", len(input), len(result)) } // TestRemoveDuplicatePorts 测试端口去重 func TestRemoveDuplicatePorts(t *testing.T) { input := []int{80, 443, 80, 22, 443, 8080} result := removeDuplicatePorts(input) // 验证无重复 seen := make(map[int]bool) for _, port := range result { if seen[port] { t.Errorf("removeDuplicatePorts 结果包含重复项: %d", port) } seen[port] = true } // 验证长度 if len(result) != 4 { t.Errorf("removeDuplicatePorts 返回%d项,期望4项", len(result)) } t.Logf("✓ removeDuplicatePorts: %d → %d", len(input), len(result)) }