Files
fscan/common/config/constants_test.go
T
ZacharyZcR 71b92d4408 feat: v2.1.0 核心重构与功能增强
## 架构重构
- 全局变量消除,迁移至 Config/State 对象
- SMB 插件融合(smb/smb2/smbghost/smbinfo)
- 服务探测重构,实现 Nmap 风格 fallback 机制
- 输出系统重构,TXT 实时刷盘 + 双写机制
- i18n 框架升级至 go-i18n

## 性能优化
- 正则表达式预编译
- 内存优化 map[string]struct{}
- 并发指纹匹配
- SOCKS5 连接复用
- 滑动窗口调度 + 自适应线程池

## 新功能
- Web 管理界面
- 多格式 POC 适配(xray/afrog)
- 增强指纹库(3139条)
- Favicon hash 指纹识别
- 插件选择性编译(Build Tags)
- fscan-lab 靶场环境
- 默认端口扩展(62→133)

## 构建系统
- 添加 no_local tag 支持排除本地插件
- 多版本构建:fscan/fscan-nolocal/fscan-web
- CI 添加 snapshot 模式支持仅测试构建

## Bug 修复
- 修复 120+ 个问题,包括 RDP panic、批量扫描漏报、
  JSON 输出格式、Redis 检测、Context 超时等

## 测试增强
- 单元测试覆盖率 74-100%
- 并发安全测试
- 集成测试(Web/端口/服务/SSH/ICMP)
2026-01-11 20:16:23 +08:00

394 lines
10 KiB
Go

package config
import (
"strconv"
"strings"
"testing"
)
/*
constants_test.go - 配置常量测试
测试目标:端口组、探测器配置、字典数据
价值:配置错误会导致:
- 端口组错误 → 扫描范围错误(用户遗漏目标)
- 字典错误 → 暴力破解失败(无法登录系统)
- 探测器配置错误 → 服务识别失败
"配置是数据,但数据也会有bug。端口范围错误、字典重复、
空值遗漏——这些都是真实问题。测试数据和测试代码一样重要。"
*/
// =============================================================================
// 端口组测试
// =============================================================================
// TestPortGroups_Format 测试端口组格式
//
// 验证:所有端口组字符串格式正确(可解析为端口列表)
func TestPortGroups_Format(t *testing.T) {
tests := []struct {
name string
portGroup string
}{
{"WebPorts", WebPorts},
{"MainPorts", MainPorts},
{"DbPorts", DbPorts},
{"ServicePorts", ServicePorts},
{"CommonPorts", CommonPorts},
{"AllPorts", AllPorts},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// 验证格式:逗号分隔的数字或范围
if tt.portGroup == "" {
t.Error("端口组不应为空")
return
}
// AllPorts是特殊格式"1-65535"
if tt.portGroup == "1-65535" {
t.Logf("✓ %s 格式正确(范围格式)", tt.name)
return
}
// 其他端口组应该是逗号分隔的数字
ports := strings.Split(tt.portGroup, ",")
if len(ports) == 0 {
t.Error("端口组应该包含至少一个端口")
return
}
// 验证每个端口都是有效数字
for i, portStr := range ports {
port, err := strconv.Atoi(strings.TrimSpace(portStr))
if err != nil {
t.Errorf("第%d个端口 '%s' 不是有效数字: %v", i+1, portStr, err)
continue
}
// 验证端口范围
if port < 1 || port > 65535 {
t.Errorf("第%d个端口 %d 超出有效范围 [1-65535]", i+1, port)
}
}
t.Logf("✓ %s 格式正确(%d个端口)", tt.name, len(ports))
})
}
}
// TestPortGroups_NoEmpty 测试端口组非空
func TestPortGroups_NoEmpty(t *testing.T) {
groups := map[string]string{
"WebPorts": WebPorts,
"MainPorts": MainPorts,
"DbPorts": DbPorts,
"ServicePorts": ServicePorts,
"CommonPorts": CommonPorts,
"AllPorts": AllPorts,
}
for name, ports := range groups {
if ports == "" {
t.Errorf("%s 不应为空字符串", name)
}
}
t.Logf("✓ 所有端口组非空")
}
// TestPortGroups_NoDuplicates 测试端口组无重复
func TestPortGroups_NoDuplicates(t *testing.T) {
tests := []struct {
name string
portGroup string
}{
{"WebPorts", WebPorts},
{"MainPorts", MainPorts},
{"DbPorts", DbPorts},
{"ServicePorts", ServicePorts},
{"CommonPorts", CommonPorts},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.portGroup == "1-65535" {
t.Skip("范围格式无需检查重复")
return
}
ports := strings.Split(tt.portGroup, ",")
seen := make(map[string]bool)
duplicates := []string{}
for _, port := range ports {
port = strings.TrimSpace(port)
if seen[port] {
duplicates = append(duplicates, port)
}
seen[port] = true
}
if len(duplicates) > 0 {
t.Errorf("%s 包含重复端口: %v", tt.name, duplicates)
} else {
t.Logf("✓ %s 无重复端口", tt.name)
}
})
}
}
// TestGetPortGroups_Completeness 测试GetPortGroups完整性
//
// 验证:返回的map包含所有预定义的端口组
func TestGetPortGroups_Completeness(t *testing.T) {
groups := GetPortGroups()
expectedKeys := []string{"web", "main", "db", "service", "common", "all"}
for _, key := range expectedKeys {
if _, ok := groups[key]; !ok {
t.Errorf("GetPortGroups缺少键: %s", key)
}
}
if len(groups) != len(expectedKeys) {
t.Errorf("GetPortGroups返回%d个组,期望%d个", len(groups), len(expectedKeys))
}
t.Logf("✓ GetPortGroups包含所有%d个端口组", len(expectedKeys))
}
// TestGetPortGroups_Values 测试GetPortGroups返回正确的值
func TestGetPortGroups_Values(t *testing.T) {
groups := GetPortGroups()
tests := []struct {
key string
expected string
}{
{"web", WebPorts},
{"main", MainPorts},
{"db", DbPorts},
{"service", ServicePorts},
{"common", CommonPorts},
{"all", AllPorts},
}
for _, tt := range tests {
t.Run(tt.key, func(t *testing.T) {
actual, ok := groups[tt.key]
if !ok {
t.Fatalf("GetPortGroups缺少键: %s", tt.key)
}
if actual != tt.expected {
t.Errorf("GetPortGroups[%s] 值不匹配\n期望前20字符: %s...\n实际前20字符: %s...",
tt.key, tt.expected[:20], actual[:20])
}
t.Logf("✓ %s 映射正确", tt.key)
})
}
}
// =============================================================================
// 探测器配置测试
// =============================================================================
// TestDefaultProbeMap_NoEmpty 测试默认探测器列表非空
func TestDefaultProbeMap_NoEmpty(t *testing.T) {
if len(DefaultProbeMap) == 0 {
t.Error("DefaultProbeMap不应为空")
return
}
// 验证每个探测器名称非空
for i, probe := range DefaultProbeMap {
if probe == "" {
t.Errorf("第%d个探测器名称为空", i+1)
}
}
t.Logf("✓ DefaultProbeMap包含%d个探测器", len(DefaultProbeMap))
}
// TestDefaultPortMap_ValidKeys 测试DefaultPortMap的键有效
func TestDefaultPortMap_ValidKeys(t *testing.T) {
invalidPorts := []int{}
for port := range DefaultPortMap {
if port < 1 || port > 65535 {
invalidPorts = append(invalidPorts, port)
}
}
if len(invalidPorts) > 0 {
t.Errorf("DefaultPortMap包含无效端口号: %v", invalidPorts)
} else {
t.Logf("✓ DefaultPortMap的%d个端口号都有效", len(DefaultPortMap))
}
}
// TestDefaultPortMap_NoEmptyValues 测试DefaultPortMap值非空
func TestDefaultPortMap_NoEmptyValues(t *testing.T) {
emptyPorts := []int{}
for port, probes := range DefaultPortMap {
if len(probes) == 0 {
emptyPorts = append(emptyPorts, port)
}
}
if len(emptyPorts) > 0 {
t.Errorf("以下端口的探测器列表为空: %v", emptyPorts)
} else {
t.Logf("✓ DefaultPortMap所有端口都有探测器")
}
}
// =============================================================================
// 字典数据测试
// =============================================================================
// TestDefaultUserDict_NoEmptyKeys 测试DefaultUserDict键非空
func TestDefaultUserDict_NoEmptyKeys(t *testing.T) {
for service, users := range DefaultUserDict {
if service == "" {
t.Error("DefaultUserDict包含空服务名")
}
if len(users) == 0 {
t.Errorf("服务 '%s' 的用户列表为空", service)
}
}
t.Logf("✓ DefaultUserDict包含%d个服务", len(DefaultUserDict))
}
// TestDefaultUserDict_CommonServices 测试DefaultUserDict包含常见服务
func TestDefaultUserDict_CommonServices(t *testing.T) {
commonServices := []string{"ftp", "mysql", "mssql", "ssh", "redis", "mongodb"}
for _, service := range commonServices {
if _, ok := DefaultUserDict[service]; !ok {
t.Errorf("DefaultUserDict缺少常见服务: %s", service)
}
}
t.Logf("✓ DefaultUserDict包含所有常见服务")
}
// TestDefaultUserDict_AllowsEmptyUser 测试DefaultUserDict允许空用户名
//
// 验证:某些服务(如redis)允许空用户名
func TestDefaultUserDict_AllowsEmptyUser(t *testing.T) {
// redis服务应该包含空用户名
redisUsers, ok := DefaultUserDict["redis"]
if !ok {
t.Skip("DefaultUserDict不包含redis,跳过测试")
return
}
hasEmptyUser := false
for _, user := range redisUsers {
if user == "" {
hasEmptyUser = true
break
}
}
if !hasEmptyUser {
t.Error("redis用户列表应该包含空用户名(默认无认证)")
} else {
t.Logf("✓ redis用户列表正确包含空用户名")
}
}
// TestDefaultPasswords_NoEmpty 测试DefaultPasswords非空
func TestDefaultPasswords_NoEmpty(t *testing.T) {
if len(DefaultPasswords) == 0 {
t.Error("DefaultPasswords不应为空")
return
}
t.Logf("✓ DefaultPasswords包含%d个密码", len(DefaultPasswords))
}
// TestDefaultPasswords_AllowsEmptyPassword 测试DefaultPasswords允许空密码
func TestDefaultPasswords_AllowsEmptyPassword(t *testing.T) {
// 应该包含空密码(某些服务默认无密码)
hasEmptyPassword := false
for _, pass := range DefaultPasswords {
if pass == "" {
hasEmptyPassword = true
break
}
}
if !hasEmptyPassword {
t.Error("DefaultPasswords应该包含空密码(某些服务默认无密码)")
} else {
t.Logf("✓ DefaultPasswords正确包含空密码")
}
}
// TestDefaultPasswords_HasPlaceholder 测试DefaultPasswords包含占位符
func TestDefaultPasswords_HasPlaceholder(t *testing.T) {
// 应该包含{user}占位符(密码=用户名的场景)
hasPlaceholder := false
for _, pass := range DefaultPasswords {
if strings.Contains(pass, "{user}") {
hasPlaceholder = true
break
}
}
if !hasPlaceholder {
t.Error("DefaultPasswords应该包含{user}占位符(密码=用户名变体)")
} else {
t.Logf("✓ DefaultPasswords正确包含{user}占位符")
}
}
// =============================================================================
// 结构体测试
// =============================================================================
// TestPocInfo_Fields 测试PocInfo结构体字段
func TestPocInfo_Fields(t *testing.T) {
poc := PocInfo{
Target: "http://example.com",
PocName: "test-poc",
}
if poc.Target != "http://example.com" {
t.Error("PocInfo.Target赋值失败")
}
if poc.PocName != "test-poc" {
t.Error("PocInfo.PocName赋值失败")
}
t.Logf("✓ PocInfo结构体正常工作")
}
// TestCredentialPair_Fields 测试CredentialPair结构体字段
func TestCredentialPair_Fields(t *testing.T) {
cred := CredentialPair{
Username: "admin",
Password: "password123",
}
if cred.Username != "admin" {
t.Error("CredentialPair.Username赋值失败")
}
if cred.Password != "password123" {
t.Error("CredentialPair.Password赋值失败")
}
t.Logf("✓ CredentialPair结构体正常工作")
}