Files
user-system/internal/auth/password_test.go
long-agent 582ad7a069 test: add comprehensive test coverage and improve code quality
- Add new test files for auth, service, and handler modules
- Improve test organization and coverage
- Refactor code for better maintainability
- Add captcha, settings, stats, and theme handler tests
- Add auth module tests (CAS, OAuth, password, SSO, state)
- Add service layer tests for auth, export, permissions, roles
- All Go tests pass (exit code 0)
- All frontend tests pass (325 tests in 59 files)
2026-04-17 20:43:50 +08:00

235 lines
5.8 KiB
Go

package auth
import (
"strings"
"testing"
)
func TestBcryptHash(t *testing.T) {
tests := []struct {
name string
password string
wantErr bool
}{
{"valid password", "password123", false},
{"empty password", "", false}, // bcrypt allows empty
{"long password", strings.Repeat("a", 50), false},
{"too long password - bcrypt limit", strings.Repeat("a", 73), true}, // bcrypt returns error for >72 bytes
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
hash, err := BcryptHash(tt.password)
if (err != nil) != tt.wantErr {
t.Errorf("BcryptHash() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !tt.wantErr && hash == "" {
t.Error("BcryptHash() returned empty hash")
}
if !tt.wantErr && !strings.HasPrefix(hash, "$2") {
t.Errorf("BcryptHash() hash should start with $2, got %s", hash[:3])
}
})
}
}
func TestBcryptVerify(t *testing.T) {
// First create a hash to test against
hash, err := BcryptHash("correct-password")
if err != nil {
t.Fatalf("BcryptHash() error = %v", err)
}
tests := []struct {
name string
hash string
password string
want bool
}{
{"correct password", hash, "correct-password", true},
{"wrong password", hash, "wrong-password", false},
{"empty password", hash, "", false},
{"invalid hash", "invalid-hash", "password", false},
{"empty hash", "", "password", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := BcryptVerify(tt.hash, tt.password); got != tt.want {
t.Errorf("BcryptVerify() = %v, want %v", got, tt.want)
}
})
}
}
func TestBcryptVerify_DifferentPasswords(t *testing.T) {
hash1, _ := BcryptHash("password1")
hash2, _ := BcryptHash("password2")
// Each hash should only verify its own password
if !BcryptVerify(hash1, "password1") {
t.Error("hash1 should verify password1")
}
if BcryptVerify(hash1, "password2") {
t.Error("hash1 should not verify password2")
}
if !BcryptVerify(hash2, "password2") {
t.Error("hash2 should verify password2")
}
if BcryptVerify(hash2, "password1") {
t.Error("hash2 should not verify password1")
}
}
func TestPassword_Verify_Argon2id(t *testing.T) {
p := NewPassword()
hash, err := p.Hash("test-password")
if err != nil {
t.Fatalf("Hash() error = %v", err)
}
// Verify correct password
if !p.Verify(hash, "test-password") {
t.Error("Verify() should return true for correct password")
}
// Verify wrong password
if p.Verify(hash, "wrong-password") {
t.Error("Verify() should return false for wrong password")
}
}
func TestPassword_Verify_Bcrypt(t *testing.T) {
p := NewPassword()
// Create bcrypt hash
bcryptHash, err := BcryptHash("bcrypt-password")
if err != nil {
t.Fatalf("BcryptHash() error = %v", err)
}
// Verify using Argon2id password manager (should support bcrypt)
if !p.Verify(bcryptHash, "bcrypt-password") {
t.Error("Verify() should support bcrypt hashes")
}
if p.Verify(bcryptHash, "wrong-password") {
t.Error("Verify() should return false for wrong bcrypt password")
}
}
func TestPassword_Verify_InvalidFormat(t *testing.T) {
p := NewPassword()
tests := []struct {
name string
hash string
want bool
}{
{"empty hash", "", false},
{"invalid format", "invalid", false},
{"wrong number of parts", "$argon2id$v=19$m=65536,t=3,p=4$abc", false},
{"wrong algorithm", "$scrypt$v=19$m=65536,t=3,p=4$salt$hash", false},
{"invalid params", "$argon2id$v=19$m=abc,t=3,p=4$salt$hash", false},
{"invalid salt hex", "$argon2id$v=19$m=65536,t=3,p=4$ZZZZZZZZ$hash", false},
{"invalid hash hex", "$argon2id$v=19$m=65536,t=3,p=4$salt$ZZZZZZZZ", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := p.Verify(tt.hash, "password"); got != tt.want {
t.Errorf("Verify() = %v, want %v", got, tt.want)
}
})
}
}
func TestPassword_Hash_DifferentSalts(t *testing.T) {
p := NewPassword()
hash1, err := p.Hash("same-password")
if err != nil {
t.Fatalf("Hash() error = %v", err)
}
hash2, err := p.Hash("same-password")
if err != nil {
t.Fatalf("Hash() error = %v", err)
}
// Two hashes of the same password should be different (different salts)
if hash1 == hash2 {
t.Error("Hash() should generate different hashes for same password (different salts)")
}
// But both should verify the same password
if !p.Verify(hash1, "same-password") {
t.Error("hash1 should verify same-password")
}
if !p.Verify(hash2, "same-password") {
t.Error("hash2 should verify same-password")
}
}
func TestPassword_HashAndVerify_SpecialCharacters(t *testing.T) {
p := NewPassword()
tests := []string{
"p@ssw0rd!",
"密码测试",
"パスワード",
" spaces ",
"tab\ttab",
"newline\nnewline",
strings.Repeat("a", 100),
}
for _, password := range tests {
t.Run("password_"+password, func(t *testing.T) {
hash, err := p.Hash(password)
if err != nil {
t.Fatalf("Hash() error = %v", err)
}
if !p.Verify(hash, password) {
t.Errorf("Verify() failed for password: %q", password)
}
})
}
}
func TestVerifyPassword_Wrapper(t *testing.T) {
// Test Argon2id hash
argonHash, err := HashPassword("argon-password")
if err != nil {
t.Fatalf("HashPassword() error = %v", err)
}
if !VerifyPassword(argonHash, "argon-password") {
t.Error("VerifyPassword() should verify Argon2id hash")
}
// Test bcrypt hash
bcryptHash, err := BcryptHash("bcrypt-password")
if err != nil {
t.Fatalf("BcryptHash() error = %v", err)
}
if !VerifyPassword(bcryptHash, "bcrypt-password") {
t.Error("VerifyPassword() should verify bcrypt hash")
}
}
func TestHashPassword_Wrapper(t *testing.T) {
hash, err := HashPassword("test-password")
if err != nil {
t.Fatalf("HashPassword() error = %v", err)
}
if !strings.HasPrefix(hash, "$argon2id$") {
t.Errorf("HashPassword() should return argon2id hash, got: %s", hash)
}
}