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) } }