mirror of
https://github.com/shadow1ng/fscan.git
synced 2026-09-26 05:01:53 +08:00
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)
This commit is contained in:
@@ -0,0 +1,766 @@
|
||||
package parsers
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// =============================================================================
|
||||
// CredentialParser 构造函数测试
|
||||
// =============================================================================
|
||||
|
||||
func TestNewCredentialParser(t *testing.T) {
|
||||
fileReader := NewFileReader(nil)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
options *CredentialParserOptions
|
||||
wantNil bool
|
||||
}{
|
||||
{
|
||||
name: "使用默认选项",
|
||||
options: nil,
|
||||
wantNil: false,
|
||||
},
|
||||
{
|
||||
name: "使用自定义选项",
|
||||
options: &CredentialParserOptions{
|
||||
MaxUsernameLength: 32,
|
||||
MaxPasswordLength: 64,
|
||||
AllowEmptyPasswords: false,
|
||||
},
|
||||
wantNil: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
parser := NewCredentialParser(fileReader, tt.options)
|
||||
|
||||
if tt.wantNil && parser != nil {
|
||||
t.Error("期望parser为nil,但不是")
|
||||
}
|
||||
if !tt.wantNil && parser == nil {
|
||||
t.Error("期望parser不为nil,但是nil")
|
||||
}
|
||||
|
||||
if parser != nil {
|
||||
if parser.options == nil {
|
||||
t.Error("parser.options为nil")
|
||||
}
|
||||
if parser.hashRegex == nil {
|
||||
t.Error("parser.hashRegex为nil")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Parse 主函数测试
|
||||
// =============================================================================
|
||||
|
||||
func TestCredentialParser_Parse(t *testing.T) {
|
||||
fileReader := NewFileReader(nil)
|
||||
parser := NewCredentialParser(fileReader, nil)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
input *CredentialInput
|
||||
wantSuccess bool
|
||||
wantUsernames int
|
||||
wantPasswords int
|
||||
wantHashes int
|
||||
wantError bool
|
||||
}{
|
||||
{
|
||||
name: "空输入",
|
||||
input: nil,
|
||||
wantSuccess: false,
|
||||
wantError: true,
|
||||
},
|
||||
{
|
||||
name: "单个用户名",
|
||||
input: &CredentialInput{
|
||||
Username: "admin",
|
||||
},
|
||||
wantSuccess: true,
|
||||
wantUsernames: 1,
|
||||
},
|
||||
{
|
||||
name: "多个用户名(逗号分隔)",
|
||||
input: &CredentialInput{
|
||||
Username: "admin,root,user",
|
||||
},
|
||||
wantSuccess: true,
|
||||
wantUsernames: 3,
|
||||
},
|
||||
{
|
||||
name: "单个密码",
|
||||
input: &CredentialInput{
|
||||
Password: "password123",
|
||||
},
|
||||
wantSuccess: true,
|
||||
wantPasswords: 1,
|
||||
},
|
||||
{
|
||||
name: "多个密码",
|
||||
input: &CredentialInput{
|
||||
Password: "pass1,pass2,pass3",
|
||||
},
|
||||
wantSuccess: true,
|
||||
wantPasswords: 3,
|
||||
},
|
||||
{
|
||||
name: "用户名和密码组合",
|
||||
input: &CredentialInput{
|
||||
Username: "admin,root",
|
||||
Password: "123456,password",
|
||||
},
|
||||
wantSuccess: true,
|
||||
wantUsernames: 2,
|
||||
wantPasswords: 2,
|
||||
},
|
||||
{
|
||||
name: "有效MD5哈希",
|
||||
input: &CredentialInput{
|
||||
HashValue: "5f4dcc3b5aa765d61d8327deb882cf99",
|
||||
},
|
||||
wantSuccess: true,
|
||||
wantHashes: 1,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result, err := parser.Parse(tt.input, nil)
|
||||
|
||||
if tt.wantError {
|
||||
if err == nil {
|
||||
t.Error("期望错误,但没有错误")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("意外错误: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if result == nil {
|
||||
t.Fatal("result为nil")
|
||||
}
|
||||
|
||||
if result.Success != tt.wantSuccess {
|
||||
t.Errorf("Success = %v, want %v", result.Success, tt.wantSuccess)
|
||||
}
|
||||
|
||||
if tt.wantUsernames > 0 && len(result.Config.Credentials.Usernames) != tt.wantUsernames {
|
||||
t.Errorf("用户名数量 = %d, want %d", len(result.Config.Credentials.Usernames), tt.wantUsernames)
|
||||
}
|
||||
|
||||
if tt.wantPasswords > 0 && len(result.Config.Credentials.Passwords) != tt.wantPasswords {
|
||||
t.Errorf("密码数量 = %d, want %d", len(result.Config.Credentials.Passwords), tt.wantPasswords)
|
||||
}
|
||||
|
||||
if tt.wantHashes > 0 && len(result.Config.Credentials.HashValues) != tt.wantHashes {
|
||||
t.Errorf("哈希数量 = %d, want %d", len(result.Config.Credentials.HashValues), tt.wantHashes)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// validateUsername 测试
|
||||
// =============================================================================
|
||||
|
||||
func TestCredentialParser_ValidateUsername(t *testing.T) {
|
||||
fileReader := NewFileReader(nil)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
username string
|
||||
options *CredentialParserOptions
|
||||
wantValid bool
|
||||
}{
|
||||
{
|
||||
name: "有效用户名",
|
||||
username: "admin",
|
||||
options: nil,
|
||||
wantValid: true,
|
||||
},
|
||||
{
|
||||
name: "带数字的用户名",
|
||||
username: "user123",
|
||||
options: nil,
|
||||
wantValid: true,
|
||||
},
|
||||
{
|
||||
name: "空用户名",
|
||||
username: "",
|
||||
options: nil,
|
||||
wantValid: false,
|
||||
},
|
||||
{
|
||||
name: "过长的用户名",
|
||||
username: strings.Repeat("a", 100),
|
||||
options: &CredentialParserOptions{
|
||||
MaxUsernameLength: 64,
|
||||
},
|
||||
wantValid: false,
|
||||
},
|
||||
{
|
||||
name: "包含换行符的用户名(无效)",
|
||||
username: "admin\ntest",
|
||||
options: nil,
|
||||
wantValid: false,
|
||||
},
|
||||
{
|
||||
name: "包含制表符的用户名(无效)",
|
||||
username: "admin\ttest",
|
||||
options: nil,
|
||||
wantValid: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
parser := NewCredentialParser(fileReader, tt.options)
|
||||
|
||||
_, valid, err := parser.validateUsername(tt.username)
|
||||
|
||||
if valid != tt.wantValid {
|
||||
t.Errorf("validateUsername() = %v (err: %v), want %v", valid, err, tt.wantValid)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// validatePassword 测试
|
||||
// =============================================================================
|
||||
|
||||
func TestCredentialParser_ValidatePassword(t *testing.T) {
|
||||
fileReader := NewFileReader(nil)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
password string
|
||||
options *CredentialParserOptions
|
||||
wantValid bool
|
||||
}{
|
||||
{
|
||||
name: "有效密码",
|
||||
password: "password123",
|
||||
options: nil,
|
||||
wantValid: true,
|
||||
},
|
||||
{
|
||||
name: "复杂密码",
|
||||
password: "P@ssw0rd!#$",
|
||||
options: nil,
|
||||
wantValid: true,
|
||||
},
|
||||
{
|
||||
name: "空密码(允许)",
|
||||
password: "",
|
||||
options: &CredentialParserOptions{
|
||||
AllowEmptyPasswords: true,
|
||||
},
|
||||
wantValid: true,
|
||||
},
|
||||
{
|
||||
name: "空密码(不允许)",
|
||||
password: "",
|
||||
options: &CredentialParserOptions{
|
||||
AllowEmptyPasswords: false,
|
||||
},
|
||||
wantValid: false,
|
||||
},
|
||||
{
|
||||
name: "过长的密码",
|
||||
password: strings.Repeat("a", 200),
|
||||
options: &CredentialParserOptions{
|
||||
MaxPasswordLength: 128,
|
||||
},
|
||||
wantValid: false,
|
||||
},
|
||||
{
|
||||
name: "带空格的密码",
|
||||
password: "my password",
|
||||
options: nil,
|
||||
wantValid: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
parser := NewCredentialParser(fileReader, tt.options)
|
||||
|
||||
_, valid, err := parser.validatePassword(tt.password)
|
||||
|
||||
if valid != tt.wantValid {
|
||||
t.Errorf("validatePassword() = %v (err: %v), want %v", valid, err, tt.wantValid)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// validateHash 测试
|
||||
// =============================================================================
|
||||
|
||||
func TestCredentialParser_ValidateHash(t *testing.T) {
|
||||
fileReader := NewFileReader(nil)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
hash string
|
||||
options *CredentialParserOptions
|
||||
wantValid bool
|
||||
}{
|
||||
{
|
||||
name: "有效MD5哈希(小写)",
|
||||
hash: "5f4dcc3b5aa765d61d8327deb882cf99",
|
||||
options: nil,
|
||||
wantValid: true,
|
||||
},
|
||||
{
|
||||
name: "有效MD5哈希(大写)",
|
||||
hash: "5F4DCC3B5AA765D61D8327DEB882CF99",
|
||||
options: nil,
|
||||
wantValid: true,
|
||||
},
|
||||
{
|
||||
name: "有效MD5哈希(混合大小写)",
|
||||
hash: "5f4DcC3b5Aa765d61D8327dEb882Cf99",
|
||||
options: nil,
|
||||
wantValid: true,
|
||||
},
|
||||
{
|
||||
name: "空哈希",
|
||||
hash: "",
|
||||
options: nil,
|
||||
wantValid: false,
|
||||
},
|
||||
{
|
||||
name: "过短的哈希",
|
||||
hash: "5f4dcc3b5aa765d61d8327deb882cf",
|
||||
options: nil,
|
||||
wantValid: false,
|
||||
},
|
||||
{
|
||||
name: "过长的哈希",
|
||||
hash: "5f4dcc3b5aa765d61d8327deb882cf9900",
|
||||
options: nil,
|
||||
wantValid: false,
|
||||
},
|
||||
{
|
||||
name: "包含非法字符的哈希",
|
||||
hash: "5f4dcc3b5aa765d61d8327deb882cfgg",
|
||||
options: nil,
|
||||
wantValid: false,
|
||||
},
|
||||
{
|
||||
name: "禁用哈希验证",
|
||||
hash: "invalid-hash",
|
||||
options: &CredentialParserOptions{
|
||||
ValidateHashes: false,
|
||||
},
|
||||
wantValid: true, // 禁用验证时,任何哈希都有效
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
parser := NewCredentialParser(fileReader, tt.options)
|
||||
|
||||
valid, err := parser.validateHash(tt.hash)
|
||||
|
||||
if valid != tt.wantValid {
|
||||
t.Errorf("validateHash() = %v (err: %v), want %v", valid, err, tt.wantValid)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// 文件解析测试
|
||||
// =============================================================================
|
||||
|
||||
func TestCredentialParser_ParseFromFile(t *testing.T) {
|
||||
fileReader := NewFileReader(nil)
|
||||
parser := NewCredentialParser(fileReader, nil)
|
||||
|
||||
t.Run("用户名文件", func(t *testing.T) {
|
||||
usersFile := createTestFile(t, `admin
|
||||
root
|
||||
user
|
||||
# 这是注释
|
||||
test`)
|
||||
|
||||
input := &CredentialInput{
|
||||
UsersFile: usersFile,
|
||||
}
|
||||
|
||||
result, err := parser.Parse(input, nil)
|
||||
if err != nil {
|
||||
t.Errorf("意外错误: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 应该有4个用户名(注释被跳过)
|
||||
if len(result.Config.Credentials.Usernames) != 4 {
|
||||
t.Errorf("用户名数量 = %d, want 4", len(result.Config.Credentials.Usernames))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("密码文件", func(t *testing.T) {
|
||||
passFile := createTestFile(t, `password1
|
||||
password2
|
||||
# 注释
|
||||
password3
|
||||
`)
|
||||
|
||||
input := &CredentialInput{
|
||||
PasswordsFile: passFile,
|
||||
}
|
||||
|
||||
result, err := parser.Parse(input, nil)
|
||||
if err != nil {
|
||||
t.Errorf("意外错误: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(result.Config.Credentials.Passwords) != 3 {
|
||||
t.Errorf("密码数量 = %d, want 3", len(result.Config.Credentials.Passwords))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("用户密码对文件", func(t *testing.T) {
|
||||
pairsFile := createTestFile(t, `admin:admin123
|
||||
root:toor
|
||||
user:password
|
||||
# test:test123 (注释)
|
||||
guest:guest`)
|
||||
|
||||
input := &CredentialInput{
|
||||
UserPassFile: pairsFile,
|
||||
}
|
||||
|
||||
result, err := parser.Parse(input, nil)
|
||||
if err != nil {
|
||||
t.Errorf("意外错误: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 应该有4对用户名密码
|
||||
if len(result.Config.Credentials.UserPassPairs) != 4 {
|
||||
t.Errorf("用户密码对数量 = %d, want 4", len(result.Config.Credentials.UserPassPairs))
|
||||
}
|
||||
|
||||
// 验证第一对
|
||||
if result.Config.Credentials.UserPassPairs[0].Username != "admin" {
|
||||
t.Errorf("第一对用户名 = %s, want admin", result.Config.Credentials.UserPassPairs[0].Username)
|
||||
}
|
||||
if result.Config.Credentials.UserPassPairs[0].Password != "admin123" {
|
||||
t.Errorf("第一对密码 = %s, want admin123", result.Config.Credentials.UserPassPairs[0].Password)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("哈希文件", func(t *testing.T) {
|
||||
hashFile := createTestFile(t, `5f4dcc3b5aa765d61d8327deb882cf99
|
||||
e99a18c428cb38d5f260853678922e03
|
||||
# 注释
|
||||
098f6bcd4621d373cade4e832627b4f6`)
|
||||
|
||||
input := &CredentialInput{
|
||||
HashFile: hashFile,
|
||||
}
|
||||
|
||||
result, err := parser.Parse(input, nil)
|
||||
if err != nil {
|
||||
t.Errorf("意外错误: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(result.Config.Credentials.HashValues) != 3 {
|
||||
t.Errorf("哈希数量 = %d, want 3", len(result.Config.Credentials.HashValues))
|
||||
}
|
||||
|
||||
// 验证哈希字节数组也被生成
|
||||
if len(result.Config.Credentials.HashBytes) != 3 {
|
||||
t.Errorf("哈希字节数组数量 = %d, want 3", len(result.Config.Credentials.HashBytes))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// 去重功能测试
|
||||
// =============================================================================
|
||||
|
||||
func TestCredentialParser_Deduplication(t *testing.T) {
|
||||
fileReader := NewFileReader(nil)
|
||||
|
||||
t.Run("用户名去重(启用)", func(t *testing.T) {
|
||||
opts := DefaultCredentialParserOptions()
|
||||
opts.DeduplicateUsers = true
|
||||
parser := NewCredentialParser(fileReader, opts)
|
||||
|
||||
input := &CredentialInput{
|
||||
Username: "admin,root,admin,user,root",
|
||||
}
|
||||
|
||||
result, err := parser.Parse(input, nil)
|
||||
if err != nil {
|
||||
t.Errorf("意外错误: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 应该去重为3个用户名
|
||||
if len(result.Config.Credentials.Usernames) != 3 {
|
||||
t.Errorf("用户名数量 = %d, want 3", len(result.Config.Credentials.Usernames))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("用户名去重(禁用)", func(t *testing.T) {
|
||||
opts := DefaultCredentialParserOptions()
|
||||
opts.DeduplicateUsers = false
|
||||
parser := NewCredentialParser(fileReader, opts)
|
||||
|
||||
input := &CredentialInput{
|
||||
Username: "admin,root,admin",
|
||||
}
|
||||
|
||||
result, err := parser.Parse(input, nil)
|
||||
if err != nil {
|
||||
t.Errorf("意外错误: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 不去重,应该有3个用户名
|
||||
if len(result.Config.Credentials.Usernames) != 3 {
|
||||
t.Errorf("用户名数量 = %d, want 3", len(result.Config.Credentials.Usernames))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("密码去重(启用)", func(t *testing.T) {
|
||||
opts := DefaultCredentialParserOptions()
|
||||
opts.DeduplicatePasswords = true
|
||||
parser := NewCredentialParser(fileReader, opts)
|
||||
|
||||
input := &CredentialInput{
|
||||
Password: "123456,password,123456,admin,password",
|
||||
}
|
||||
|
||||
result, err := parser.Parse(input, nil)
|
||||
if err != nil {
|
||||
t.Errorf("意外错误: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 应该去重为3个密码
|
||||
if len(result.Config.Credentials.Passwords) != 3 {
|
||||
t.Errorf("密码数量 = %d, want 3", len(result.Config.Credentials.Passwords))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// 混合输入测试
|
||||
// =============================================================================
|
||||
|
||||
func TestCredentialParser_MixedInput(t *testing.T) {
|
||||
fileReader := NewFileReader(nil)
|
||||
parser := NewCredentialParser(fileReader, nil)
|
||||
|
||||
t.Run("命令行+文件混合", func(t *testing.T) {
|
||||
usersFile := createTestFile(t, "user1\nuser2")
|
||||
passFile := createTestFile(t, "pass1\npass2")
|
||||
|
||||
input := &CredentialInput{
|
||||
Username: "admin,root",
|
||||
UsersFile: usersFile,
|
||||
Password: "123456",
|
||||
PasswordsFile: passFile,
|
||||
AddUsers: "guest",
|
||||
AddPasswords: "guest123",
|
||||
}
|
||||
|
||||
result, err := parser.Parse(input, nil)
|
||||
if err != nil {
|
||||
t.Errorf("意外错误: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 用户名: 2(命令行) + 2(文件) + 1(AddUsers) = 5
|
||||
if len(result.Config.Credentials.Usernames) != 5 {
|
||||
t.Errorf("用户名数量 = %d, want 5", len(result.Config.Credentials.Usernames))
|
||||
}
|
||||
|
||||
// 密码: 1(命令行) + 2(文件) + 1(AddPasswords) = 4
|
||||
if len(result.Config.Credentials.Passwords) != 4 {
|
||||
t.Errorf("密码数量 = %d, want 4", len(result.Config.Credentials.Passwords))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// 错误处理测试
|
||||
// =============================================================================
|
||||
|
||||
func TestCredentialParser_ErrorHandling(t *testing.T) {
|
||||
fileReader := NewFileReader(nil)
|
||||
parser := NewCredentialParser(fileReader, nil)
|
||||
|
||||
t.Run("用户密码对格式错误", func(t *testing.T) {
|
||||
pairsFile := createTestFile(t, `admin:admin123
|
||||
invalidformat
|
||||
root:toor`)
|
||||
|
||||
input := &CredentialInput{
|
||||
UserPassFile: pairsFile,
|
||||
}
|
||||
|
||||
result, err := parser.Parse(input, nil)
|
||||
if err != nil {
|
||||
t.Errorf("意外错误: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 应该有警告
|
||||
if len(result.Warnings) == 0 {
|
||||
t.Error("期望有警告,但没有")
|
||||
}
|
||||
|
||||
// 应该只解析出2对有效的
|
||||
if len(result.Config.Credentials.UserPassPairs) != 2 {
|
||||
t.Errorf("用户密码对数量 = %d, want 2", len(result.Config.Credentials.UserPassPairs))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("用户密码对-空用户名", func(t *testing.T) {
|
||||
pairsFile := createTestFile(t, `admin:admin123
|
||||
:emptyuser
|
||||
root:toor`)
|
||||
|
||||
input := &CredentialInput{
|
||||
UserPassFile: pairsFile,
|
||||
}
|
||||
|
||||
result, err := parser.Parse(input, nil)
|
||||
if err != nil {
|
||||
t.Errorf("意外错误: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 空用户名的行应该被跳过
|
||||
if len(result.Config.Credentials.UserPassPairs) != 2 {
|
||||
t.Errorf("用户密码对数量 = %d, want 2", len(result.Config.Credentials.UserPassPairs))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("密码中包含冒号", func(t *testing.T) {
|
||||
pairsFile := createTestFile(t, `admin:pass:word:123
|
||||
root:simple`)
|
||||
|
||||
input := &CredentialInput{
|
||||
UserPassFile: pairsFile,
|
||||
}
|
||||
|
||||
result, err := parser.Parse(input, nil)
|
||||
if err != nil {
|
||||
t.Errorf("意外错误: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(result.Config.Credentials.UserPassPairs) != 2 {
|
||||
t.Errorf("用户密码对数量 = %d, want 2", len(result.Config.Credentials.UserPassPairs))
|
||||
}
|
||||
|
||||
// 验证密码中的冒号被正确保留
|
||||
if result.Config.Credentials.UserPassPairs[0].Password != "pass:word:123" {
|
||||
t.Errorf("密码 = %s, want pass:word:123", result.Config.Credentials.UserPassPairs[0].Password)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("不存在的文件", func(t *testing.T) {
|
||||
input := &CredentialInput{
|
||||
UsersFile: "/nonexistent/users.txt",
|
||||
}
|
||||
|
||||
result, err := parser.Parse(input, nil)
|
||||
if err != nil {
|
||||
t.Errorf("Parse不应返回错误: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 应该有文件错误
|
||||
if result.Success {
|
||||
t.Error("解析不应成功(文件不存在)")
|
||||
}
|
||||
|
||||
if len(result.Errors) == 0 {
|
||||
t.Error("应该有错误记录")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// removeDuplicateStrings 测试
|
||||
// =============================================================================
|
||||
|
||||
func TestCredentialParser_RemoveDuplicateStrings(t *testing.T) {
|
||||
fileReader := NewFileReader(nil)
|
||||
parser := NewCredentialParser(fileReader, nil)
|
||||
|
||||
input := []string{"a", "b", "a", "c", "b", "d"}
|
||||
result := parser.removeDuplicateStrings(input)
|
||||
|
||||
expected := []string{"a", "b", "c", "d"}
|
||||
|
||||
if len(result) != len(expected) {
|
||||
t.Errorf("结果数量 = %d, want %d", len(result), len(expected))
|
||||
}
|
||||
|
||||
// 检查所有元素都存在(顺序可能不同)
|
||||
resultMap := make(map[string]bool)
|
||||
for _, item := range result {
|
||||
resultMap[item] = true
|
||||
}
|
||||
|
||||
for _, item := range expected {
|
||||
if !resultMap[item] {
|
||||
t.Errorf("缺少元素: %s", item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// SSH和Domain字段测试
|
||||
// =============================================================================
|
||||
|
||||
func TestCredentialParser_SSHAndDomain(t *testing.T) {
|
||||
fileReader := NewFileReader(nil)
|
||||
parser := NewCredentialParser(fileReader, nil)
|
||||
|
||||
input := &CredentialInput{
|
||||
SSHKeyPath: "/path/to/ssh/key",
|
||||
Domain: "example.com",
|
||||
Username: "admin",
|
||||
}
|
||||
|
||||
result, err := parser.Parse(input, nil)
|
||||
if err != nil {
|
||||
t.Errorf("意外错误: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if result.Config.Credentials.SSHKeyPath != "/path/to/ssh/key" {
|
||||
t.Errorf("SSHKeyPath = %s, want /path/to/ssh/key", result.Config.Credentials.SSHKeyPath)
|
||||
}
|
||||
|
||||
if result.Config.Credentials.Domain != "example.com" {
|
||||
t.Errorf("Domain = %s, want example.com", result.Config.Credentials.Domain)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user