package password import ( "testing" "github.com/atlasos/calypso/internal/common/config" ) func min(a, b int) int { if a < b { return a } return b } func contains(s, substr string) bool { for i := 0; i <= len(s)-len(substr); i++ { if s[i:i+len(substr)] == substr { return true } } return false } func TestHashPassword(t *testing.T) { params := config.Argon2Params{ Memory: 64 * 1024, Iterations: 3, Parallelism: 4, SaltLength: 16, KeyLength: 32, } password := "test-password-123" hash, err := HashPassword(password, params) if err != nil { t.Fatalf("HashPassword failed: %v", err) } // Verify hash format if hash == "" { t.Error("HashPassword returned empty string") } // Verify hash starts with Argon2id prefix if len(hash) < 12 || hash[:12] != "$argon2id$v=" { t.Errorf("Hash does not start with expected prefix, got: %s", hash[:min(30, len(hash))]) } // Verify hash contains required components if !contains(hash, "$m=") || !contains(hash, ",t=") || !contains(hash, ",p=") { t.Errorf("Hash missing required components, got: %s", hash[:min(50, len(hash))]) } // Verify hash is different each time (due to random salt) hash2, err := HashPassword(password, params) if err != nil { t.Fatalf("HashPassword failed on second call: %v", err) } if hash == hash2 { t.Error("HashPassword returned same hash for same password (salt should be random)") } } func TestVerifyPassword(t *testing.T) { params := config.Argon2Params{ Memory: 64 * 1024, Iterations: 3, Parallelism: 4, SaltLength: 16, KeyLength: 32, } password := "test-password-123" hash, err := HashPassword(password, params) if err != nil { t.Fatalf("HashPassword failed: %v", err) } // Test correct password valid, err := VerifyPassword(password, hash) if err != nil { t.Fatalf("VerifyPassword failed: %v", err) } if !valid { t.Error("VerifyPassword returned false for correct password") } // Test wrong password valid, err = VerifyPassword("wrong-password", hash) if err != nil { t.Fatalf("VerifyPassword failed: %v", err) } if valid { t.Error("VerifyPassword returned true for wrong password") } // Test empty password valid, err = VerifyPassword("", hash) if err != nil { t.Fatalf("VerifyPassword failed: %v", err) } if valid { t.Error("VerifyPassword returned true for empty password") } } func TestVerifyPassword_InvalidHash(t *testing.T) { tests := []struct { name string hash string }{ {"empty hash", ""}, {"invalid format", "not-a-hash"}, {"wrong algorithm", "$argon2$v=19$m=65536,t=3,p=4$salt$hash"}, {"incomplete hash", "$argon2id$v=19"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { valid, err := VerifyPassword("test-password", tt.hash) if err == nil { t.Error("VerifyPassword should return error for invalid hash") } if valid { t.Error("VerifyPassword should return false for invalid hash") } }) } } func TestHashPassword_DifferentPasswords(t *testing.T) { params := config.Argon2Params{ Memory: 64 * 1024, Iterations: 3, Parallelism: 4, SaltLength: 16, KeyLength: 32, } password1 := "password1" password2 := "password2" hash1, err := HashPassword(password1, params) if err != nil { t.Fatalf("HashPassword failed: %v", err) } hash2, err := HashPassword(password2, params) if err != nil { t.Fatalf("HashPassword failed: %v", err) } // Hashes should be different if hash1 == hash2 { t.Error("Different passwords produced same hash") } // Each password should verify against its own hash valid, err := VerifyPassword(password1, hash1) if err != nil || !valid { t.Error("Password1 should verify against its own hash") } valid, err = VerifyPassword(password2, hash2) if err != nil || !valid { t.Error("Password2 should verify against its own hash") } // Passwords should not verify against each other's hash valid, err = VerifyPassword(password1, hash2) if err != nil || valid { t.Error("Password1 should not verify against password2's hash") } valid, err = VerifyPassword(password2, hash1) if err != nil || valid { t.Error("Password2 should not verify against password1's hash") } }