package database_test import ( "strings" "testing" "sneak.berlin/go/webhooker/internal/database" ) func TestGenerateRandomPassword(t *testing.T) { t.Parallel() tests := []struct { name string length int }{ {"Short password", 8}, {"Medium password", 16}, {"Long password", 32}, {"Very short password", 3}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() password, err := database.GenerateRandomPassword( tt.length, ) if err != nil { t.Fatalf( "GenerateRandomPassword() error = %v", err, ) } if len(password) != tt.length { t.Errorf( "Password length = %v, want %v", len(password), tt.length, ) } checkPasswordComplexity( t, password, tt.length, ) }) } } func checkPasswordComplexity( t *testing.T, password string, length int, ) { t.Helper() // For passwords >= 4 chars, check complexity if length < 4 { return } flags := classifyChars(password) if !flags[0] || !flags[1] || !flags[2] || !flags[3] { t.Errorf( "Password lacks required complexity: "+ "upper=%v, lower=%v, digit=%v, special=%v", flags[0], flags[1], flags[2], flags[3], ) } } func classifyChars(s string) [4]bool { var flags [4]bool // upper, lower, digit, special for _, char := range s { switch { case char >= 'A' && char <= 'Z': flags[0] = true case char >= 'a' && char <= 'z': flags[1] = true case char >= '0' && char <= '9': flags[2] = true case strings.ContainsRune( "!@#$%^&*()_+-=[]{}|;:,.<>?", char, ): flags[3] = true } } return flags } func TestGenerateRandomPasswordUniqueness(t *testing.T) { t.Parallel() // Generate multiple passwords and ensure they're different passwords := make(map[string]bool) const numPasswords = 100 for range numPasswords { password, err := database.GenerateRandomPassword(16) if err != nil { t.Fatalf( "GenerateRandomPassword() error = %v", err, ) } if passwords[password] { t.Errorf( "Duplicate password generated: %s", password, ) } passwords[password] = true } } func TestHashPassword(t *testing.T) { t.Parallel() password := "testPassword123!" hash, err := database.HashPassword(password) if err != nil { t.Fatalf("HashPassword() error = %v", err) } // Check that hash has correct format if !strings.HasPrefix(hash, "$argon2id$") { t.Errorf( "Hash doesn't have correct prefix: %s", hash, ) } // Verify password valid, err := database.VerifyPassword(password, hash) if err != nil { t.Fatalf("VerifyPassword() error = %v", err) } if !valid { t.Error( "VerifyPassword() returned false " + "for correct password", ) } // Verify wrong password fails valid, err = database.VerifyPassword( "wrongPassword", hash, ) if err != nil { t.Fatalf("VerifyPassword() error = %v", err) } if valid { t.Error( "VerifyPassword() returned true " + "for wrong password", ) } } func TestHashPasswordUniqueness(t *testing.T) { t.Parallel() password := "testPassword123!" // Same password should produce different hashes hash1, err := database.HashPassword(password) if err != nil { t.Fatalf("HashPassword() error = %v", err) } hash2, err := database.HashPassword(password) if err != nil { t.Fatalf("HashPassword() error = %v", err) } if hash1 == hash2 { t.Error( "Same password produced identical hashes " + "(salt not working)", ) } }