package kate import ( "strings" "testing" "testing/quick" ) func TestHashPasswordComparePasswordRoundtrip(t *testing.T) { property := func(password string) bool { hash, err := HashPassword(password, nil) if err != nil { return false } match, err := ComparePassword(password, hash) if err != nil { return false } return match } if err := quick.Check(property, nil); err != nil { t.Errorf("roundtrip property failed: %v", err) } } func TestComparePasswordWrongPassword(t *testing.T) { property := func(password, wrongPassword string) bool { if password == wrongPassword { return true } hash, err := HashPassword(password, nil) if err != nil { return false } match, err := ComparePassword(wrongPassword, hash) if err != nil { return false } return !match } if err := quick.Check(property, nil); err != nil { t.Errorf("wrong password property failed: %v", err) } } func TestHashPasswordUniqueness(t *testing.T) { property := func(password string) bool { hash1, err1 := HashPassword(password, nil) hash2, err2 := HashPassword(password, nil) if err1 != nil || err2 != nil { return false } return hash1 != hash2 } if err := quick.Check(property, nil); err != nil { t.Errorf("uniqueness property failed: %v", err) } } func TestHashPasswordFormat(t *testing.T) { property := func(password string) bool { hash, err := HashPassword(password, nil) if err != nil { return false } parts := strings.Split(hash, "$") if len(parts) != 6 { return false } return parts[0] == "" && parts[1] == "argon2id" } if err := quick.Check(property, nil); err != nil { t.Errorf("format property failed: %v", err) } } func TestComparePasswordMalformedHash(t *testing.T) { tests := []string{ "", "invalid", "$argon2id", "$argon2id$v=19$m=65536,t=1,p=4", "$argon2id$v=19$m=65536,t=1,p=4$salt", "$wrong$v=19$m=65536,t=1,p=4$salt$hash", "$argon2id$v=999$m=65536,t=1,p=4$salt$hash", "$argon2id$v=19$invalid$salt$hash", "$argon2id$v=19$m=65536,t=1,p=4$!!!$hash", "$argon2id$v=19$m=65536,t=1,p=4$salt$!!!", } for _, malformedHash := range tests { t.Run("malformed_"+malformedHash, func(t *testing.T) { match, err := ComparePassword("password", malformedHash) if err == nil { t.Error("expected error for malformed hash") } if match { t.Error("should not match with malformed hash") } }) } } func TestEmptyPassword(t *testing.T) { hash, err := HashPassword("", nil) if err != nil { t.Fatalf("HashPassword failed for empty password: %v", err) } match, err := ComparePassword("", hash) if err != nil { t.Fatalf("ComparePassword failed: %v", err) } if !match { t.Error("empty password should match its hash") } match, err = ComparePassword("nonempty", hash) if err != nil { t.Fatalf("ComparePassword failed: %v", err) } if match { t.Error("non-empty password should not match empty password hash") } } func TestHashPasswordWithConfig(t *testing.T) { tests := []struct { name string config *Argon2Config want string // Expected parameters in PHC format }{ { name: "nil config uses defaults", config: nil, want: "$argon2id$v=19$m=65536,t=1,p=4$", }, { name: "custom time parameter", config: &Argon2Config{ Time: 3, }, want: "$argon2id$v=19$m=65536,t=3,p=4$", }, { name: "custom memory parameter", config: &Argon2Config{ Memory: 32 * 1024, // 32MB }, want: "$argon2id$v=19$m=32768,t=1,p=4$", }, { name: "custom threads parameter", config: &Argon2Config{ Threads: 2, }, want: "$argon2id$v=19$m=65536,t=1,p=2$", }, { name: "all custom parameters", config: &Argon2Config{ Time: 2, Memory: 128 * 1024, Threads: 8, KeyLen: 64, }, want: "$argon2id$v=19$m=131072,t=2,p=8$", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { hash, err := HashPassword("test", tt.config) if err != nil { t.Fatalf("HashPassword() error = %v", err) } // Check that the hash starts with expected PHC format parameters if !strings.HasPrefix(hash, tt.want) { t.Errorf("HashPassword() = %v, want prefix %v", hash, tt.want) } // Verify password still works match, err := ComparePassword("test", hash) if err != nil { t.Fatalf("ComparePassword() error = %v", err) } if !match { t.Error("Password should match its hash") } // Verify wrong password doesn't match match, err = ComparePassword("wrong", hash) if err != nil { t.Fatalf("ComparePassword() error = %v", err) } if match { t.Error("Wrong password should not match hash") } }) } } func TestDefaultArgon2Config(t *testing.T) { if defaultArgon2Config.Time != 1 { t.Errorf("Expected defaultArgon2Config.Time=1, got %d", defaultArgon2Config.Time) } if defaultArgon2Config.Memory != 64*1024 { t.Errorf("Expected defaultArgon2Config.Memory=65536, got %d", defaultArgon2Config.Memory) } if defaultArgon2Config.Threads != 4 { t.Errorf("Expected defaultArgon2Config.Threads=4, got %d", defaultArgon2Config.Threads) } if defaultArgon2Config.KeyLen != 32 { t.Errorf("Expected defaultArgon2Config.KeyLen=32, got %d", defaultArgon2Config.KeyLen) } }