Files
fscan/common/output/buffer_test.go
T
ZacharyZcR 5a8583a195
测试构建 / 代码检查 (push) Has been cancelled
测试构建 / 单元测试和构建 (push) Has been cancelled
测试构建 / 构建验证 (push) Has been cancelled
fix csv web title output (#575)
2026-05-09 14:00:06 +08:00

476 lines
12 KiB
Go

package output
import (
"fmt"
"sync"
"testing"
)
/*
buffer_test.go - ResultBuffer 高价值测试
测试重点:
1. 去重逻辑 - 不同结果类型的去重策略差异
2. 完整度评分 - 决定是否替换已有服务记录
3. 并发安全 - 多goroutine同时Add
*/
// =============================================================================
// 基本去重测试
// =============================================================================
// TestResultBuffer_HostDeduplication 测试主机去重
func TestResultBuffer_HostDeduplication(t *testing.T) {
buf := NewResultBuffer()
// 添加相同主机多次
for i := 0; i < 10; i++ {
buf.Add(&ScanResult{
Type: TypeHost,
Target: "192.168.1.1",
Status: "alive",
})
}
hosts, _, _, _ := buf.Summary()
if hosts != 1 {
t.Errorf("主机应去重为1个,实际 %d", hosts)
}
}
// TestResultBuffer_PortDeduplication 测试端口去重
func TestResultBuffer_PortDeduplication(t *testing.T) {
buf := NewResultBuffer()
// 相同IP:Port应去重
for i := 0; i < 5; i++ {
buf.Add(&ScanResult{
Type: TypePort,
Target: "192.168.1.1",
Details: map[string]interface{}{"port": 80},
})
}
// 不同端口不去重
buf.Add(&ScanResult{
Type: TypePort,
Target: "192.168.1.1",
Details: map[string]interface{}{"port": 443},
})
_, ports, _, _ := buf.Summary()
if ports != 2 {
t.Errorf("端口应有2个(80和443),实际 %d", ports)
}
}
// TestResultBuffer_ServiceDeduplication 测试服务去重
func TestResultBuffer_ServiceDeduplication(t *testing.T) {
buf := NewResultBuffer()
// 相同Target的服务应去重
buf.Add(&ScanResult{
Type: TypeService,
Target: "192.168.1.1:80",
Status: "http",
})
buf.Add(&ScanResult{
Type: TypeService,
Target: "192.168.1.1:80",
Status: "nginx",
})
_, _, services, _ := buf.Summary()
if services != 1 {
t.Errorf("相同Target的服务应去重为1个,实际 %d", services)
}
}
// TestResultBuffer_VulnDeduplication 测试漏洞去重
func TestResultBuffer_VulnDeduplication(t *testing.T) {
buf := NewResultBuffer()
// 相同Target+Status的漏洞应去重
for i := 0; i < 3; i++ {
buf.Add(&ScanResult{
Type: TypeVuln,
Target: "192.168.1.1:445",
Status: "MS17-010",
})
}
// 不同漏洞不去重
buf.Add(&ScanResult{
Type: TypeVuln,
Target: "192.168.1.1:445",
Status: "CVE-2020-0796",
})
_, _, _, vulns := buf.Summary()
if vulns != 2 {
t.Errorf("漏洞应有2个,实际 %d", vulns)
}
}
// =============================================================================
// 完整度评分测试
// =============================================================================
// TestResultBuffer_CompletenessScore 测试完整度评分
func TestResultBuffer_CompletenessScore(t *testing.T) {
buf := NewResultBuffer()
tests := []struct {
name string
result *ScanResult
expectedScore int
}{
{
name: "空Details",
result: &ScanResult{Details: nil},
expectedScore: 0,
},
{
name: "只有status",
result: &ScanResult{Details: map[string]interface{}{"status": 200}},
expectedScore: 2,
},
{
name: "有server",
result: &ScanResult{Details: map[string]interface{}{"server": "nginx/1.18.0"}},
expectedScore: 2,
},
{
name: "有title",
result: &ScanResult{Details: map[string]interface{}{"title": "Welcome"}},
expectedScore: 1,
},
{
name: "有指纹-[]string",
result: &ScanResult{Details: map[string]interface{}{"fingerprints": []string{"nginx"}}},
expectedScore: 3,
},
{
name: "有指纹-[]interface{}",
result: &ScanResult{Details: map[string]interface{}{"fingerprints": []interface{}{"apache", "php"}}},
expectedScore: 3,
},
{
name: "有banner",
result: &ScanResult{Details: map[string]interface{}{"banner": "SSH-2.0-OpenSSH"}},
expectedScore: 1,
},
{
name: "完整记录",
result: &ScanResult{
Details: map[string]interface{}{
"status": 200,
"server": "nginx",
"title": "Home",
"fingerprints": []string{"nginx", "php"},
"banner": "test",
},
},
expectedScore: 9, // 2+2+1+3+1
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
score := buf.CalculateCompleteness(tt.result)
if score != tt.expectedScore {
t.Errorf("完整度评分 = %d, 期望 %d", score, tt.expectedScore)
}
})
}
}
// TestResultBuffer_ServiceUpdate 测试服务记录更新
//
// 当新记录比旧记录更完整时,应该替换
func TestResultBuffer_ServiceUpdate(t *testing.T) {
buf := NewResultBuffer()
// 先添加简单记录
buf.Add(&ScanResult{
Type: TypeService,
Target: "192.168.1.1:80",
Status: "http",
Details: map[string]interface{}{},
})
// 再添加更完整的记录
buf.Add(&ScanResult{
Type: TypeService,
Target: "192.168.1.1:80",
Status: "http",
Details: map[string]interface{}{
"status": 200,
"server": "nginx/1.18.0",
"title": "Welcome",
"fingerprints": []string{"nginx", "php"},
},
})
_, _, services, _ := buf.Summary()
if services != 1 {
t.Fatal("服务数量应为1")
}
// 验证是更完整的记录
if buf.ServiceResults[0].Details == nil {
t.Fatal("Details不应为nil")
}
if buf.ServiceResults[0].Details["server"] != "nginx/1.18.0" {
t.Error("应保留更完整的记录")
}
}
func TestResultBuffer_ServiceUpdateMergesDetails(t *testing.T) {
buf := NewResultBuffer()
buf.Add(&ScanResult{
Type: TypeService,
Target: "192.168.1.1:80",
Status: "identified",
Details: map[string]interface{}{
"service": "http",
"banner": "HTTP/1.1 200 OK",
},
})
buf.Add(&ScanResult{
Type: TypeService,
Target: "192.168.1.1:80",
Status: "web",
Details: map[string]interface{}{
"title": "Home",
"status": 200,
"server": "nginx",
},
})
if len(buf.ServiceResults) != 1 {
t.Fatalf("期望1条服务记录,实际 %d", len(buf.ServiceResults))
}
details := buf.ServiceResults[0].Details
for _, key := range []string{"service", "banner", "title", "status", "server"} {
if _, ok := details[key]; !ok {
t.Errorf("合并后的服务记录缺少字段 %q: %#v", key, details)
}
}
}
// TestResultBuffer_ServiceNoDowngrade 测试不降级服务记录
//
// 当新记录不如旧记录完整时,不应替换
func TestResultBuffer_ServiceNoDowngrade(t *testing.T) {
buf := NewResultBuffer()
// 先添加完整记录
buf.Add(&ScanResult{
Type: TypeService,
Target: "192.168.1.1:80",
Status: "http",
Details: map[string]interface{}{
"status": 200,
"server": "nginx/1.18.0",
"fingerprints": []string{"nginx"},
},
})
// 再添加简单记录
buf.Add(&ScanResult{
Type: TypeService,
Target: "192.168.1.1:80",
Status: "http",
Details: map[string]interface{}{},
})
// 验证仍保留完整记录
if buf.ServiceResults[0].Details["server"] != "nginx/1.18.0" {
t.Error("不应降级到不完整的记录")
}
}
// =============================================================================
// 并发安全测试
// =============================================================================
// TestResultBuffer_ConcurrentAdd 测试并发添加
func TestResultBuffer_ConcurrentAdd(t *testing.T) {
buf := NewResultBuffer()
const goroutines = 100
const resultsPerGoroutine = 100
var wg sync.WaitGroup
wg.Add(goroutines)
for i := 0; i < goroutines; i++ {
go func(id int) {
defer wg.Done()
for j := 0; j < resultsPerGoroutine; j++ {
// 每个goroutine添加不同类型的结果
switch j % 4 {
case 0:
buf.Add(&ScanResult{
Type: TypeHost,
Target: fmt.Sprintf("192.168.%d.%d", id, j),
})
case 1:
buf.Add(&ScanResult{
Type: TypePort,
Target: fmt.Sprintf("192.168.%d.%d", id, j),
Details: map[string]interface{}{"port": j},
})
case 2:
buf.Add(&ScanResult{
Type: TypeService,
Target: fmt.Sprintf("192.168.%d.%d:%d", id, j, j),
})
case 3:
buf.Add(&ScanResult{
Type: TypeVuln,
Target: fmt.Sprintf("192.168.%d.%d", id, j),
Status: fmt.Sprintf("CVE-%d", j),
})
}
}
}(i)
}
wg.Wait()
// 验证没有panic,数据完整
hosts, ports, services, vulns := buf.Summary()
total := hosts + ports + services + vulns
if total == 0 {
t.Error("并发添加后应有结果")
}
t.Logf("并发测试完成: %d hosts, %d ports, %d services, %d vulns",
hosts, ports, services, vulns)
}
// TestResultBuffer_ConcurrentSummary 测试并发获取摘要
func TestResultBuffer_ConcurrentSummary(t *testing.T) {
buf := NewResultBuffer()
// 预填充一些数据
for i := 0; i < 100; i++ {
buf.Add(&ScanResult{
Type: TypeHost,
Target: fmt.Sprintf("192.168.1.%d", i),
})
}
var wg sync.WaitGroup
wg.Add(100)
for i := 0; i < 100; i++ {
go func() {
defer wg.Done()
// 同时获取摘要和添加
buf.Summary()
buf.Add(&ScanResult{
Type: TypeHost,
Target: "10.0.0.1",
})
}()
}
wg.Wait()
// 没有panic即为成功
}
// =============================================================================
// 边界情况测试
// =============================================================================
// TestResultBuffer_NilResult 测试nil结果
func TestResultBuffer_NilResult(t *testing.T) {
buf := NewResultBuffer()
buf.Add(nil) // 不应panic
hosts, ports, services, vulns := buf.Summary()
if hosts+ports+services+vulns != 0 {
t.Error("添加nil后应无结果")
}
}
// TestResultBuffer_PortWithoutDetails 测试无Details的端口
func TestResultBuffer_PortWithoutDetails(t *testing.T) {
buf := NewResultBuffer()
buf.Add(&ScanResult{
Type: TypePort,
Target: "192.168.1.1",
Details: nil,
})
_, ports, _, _ := buf.Summary()
if ports != 1 {
t.Error("无Details的端口也应被添加")
}
}
// TestResultBuffer_Clear 测试清空
func TestResultBuffer_Clear(t *testing.T) {
buf := NewResultBuffer()
// 添加各类结果
buf.Add(&ScanResult{Type: TypeHost, Target: "192.168.1.1"})
buf.Add(&ScanResult{Type: TypePort, Target: "192.168.1.1", Details: map[string]interface{}{"port": 80}})
buf.Add(&ScanResult{Type: TypeService, Target: "192.168.1.1:80"})
buf.Add(&ScanResult{Type: TypeVuln, Target: "192.168.1.1", Status: "CVE-2021-1234"})
// 清空
buf.Clear()
hosts, ports, services, vulns := buf.Summary()
if hosts+ports+services+vulns != 0 {
t.Error("Clear后应无结果")
}
// 验证可以继续添加
buf.Add(&ScanResult{Type: TypeHost, Target: "10.0.0.1"})
hosts, _, _, _ = buf.Summary()
if hosts != 1 {
t.Error("Clear后应能继续添加")
}
}
// TestResultBuffer_EmptyFingerprints 测试空指纹数组
func TestResultBuffer_EmptyFingerprints(t *testing.T) {
buf := NewResultBuffer()
// 空字符串数组
score1 := buf.CalculateCompleteness(&ScanResult{
Details: map[string]interface{}{"fingerprints": []string{}},
})
if score1 != 0 {
t.Errorf("空指纹数组不应加分,实际 %d", score1)
}
// 空interface数组
score2 := buf.CalculateCompleteness(&ScanResult{
Details: map[string]interface{}{"fingerprints": []interface{}{}},
})
if score2 != 0 {
t.Errorf("空interface数组不应加分,实际 %d", score2)
}
}
// TestResultBuffer_StatusZero 测试status为0
func TestResultBuffer_StatusZero(t *testing.T) {
buf := NewResultBuffer()
score := buf.CalculateCompleteness(&ScanResult{
Details: map[string]interface{}{"status": 0},
})
if score != 0 {
t.Errorf("status为0不应加分,实际 %d", score)
}
}