package accountserviceimp import ( "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/tech/sendico/pkg/model" apiconfig "github.com/tech/sendico/server/internal/api/config" "go.uber.org/zap" ) // TestValidatePassword tests the password validation logic directly func TestValidatePassword(t *testing.T) { config := &apiconfig.PasswordConfig{ Check: apiconfig.PasswordChecks{ MinLength: 8, Digit: true, Upper: true, Lower: true, Special: true, }, TokenLength: 32, } // Create a minimal service for testing password validation logger := zap.NewNop() // Use no-op logger for tests service := &service{ config: config, logger: logger, } t.Run("ValidPassword", func(t *testing.T) { err := service.ValidatePassword("TestPassword123!", nil) assert.NoError(t, err) }) t.Run("PasswordTooShort", func(t *testing.T) { err := service.ValidatePassword("Test1!", nil) assert.Error(t, err) assert.Contains(t, err.Error(), "at least 8 characters") }) t.Run("PasswordMissingDigit", func(t *testing.T) { err := service.ValidatePassword("TestPassword!", nil) assert.Error(t, err) assert.Contains(t, err.Error(), "at least one digit") }) t.Run("PasswordMissingUppercase", func(t *testing.T) { err := service.ValidatePassword("testpassword123!", nil) assert.Error(t, err) assert.Contains(t, err.Error(), "at least one uppercase") }) t.Run("PasswordMissingLowercase", func(t *testing.T) { err := service.ValidatePassword("TESTPASSWORD123!", nil) assert.Error(t, err) assert.Contains(t, err.Error(), "at least one lowercase") }) t.Run("PasswordMissingSpecialCharacter", func(t *testing.T) { err := service.ValidatePassword("TestPassword123", nil) assert.Error(t, err) assert.Contains(t, err.Error(), "at least one special character") }) t.Run("PasswordSameAsOld", func(t *testing.T) { oldPassword := "TestPassword123!" err := service.ValidatePassword("TestPassword123!", &oldPassword) assert.Error(t, err) assert.Contains(t, err.Error(), "cannot be the same as the old password") }) } // TestValidateAccount tests the account validation logic directly func TestValidateAccount(t *testing.T) { config := &apiconfig.PasswordConfig{ Check: apiconfig.PasswordChecks{ MinLength: 8, Digit: true, Upper: true, Lower: true, Special: true, }, TokenLength: 32, } logger := zap.NewNop() // Use no-op logger for tests service := &service{ config: config, logger: logger, } t.Run("ValidAccount", func(t *testing.T) { account := &model.Account{ AccountPublic: model.AccountPublic{ AccountBase: model.AccountBase{ Describable: model.Describable{ Name: "Test User", }, }, UserDataBase: model.UserDataBase{ Login: "test@example.com", }, }, Password: "TestPassword123!", } originalPassword := account.Password err := service.ValidateAccount(account) require.NoError(t, err) // Password should be hashed after validation assert.NotEqual(t, originalPassword, account.Password) assert.NotEmpty(t, account.VerifyToken) assert.Equal(t, config.TokenLength, len(account.VerifyToken)) }) t.Run("AccountMissingName", func(t *testing.T) { account := &model.Account{ AccountPublic: model.AccountPublic{ UserDataBase: model.UserDataBase{ Login: "test@example.com", }, }, Password: "TestPassword123!", } err := service.ValidateAccount(account) assert.Error(t, err) assert.Contains(t, err.Error(), "Name must not be empty") }) t.Run("AccountMissingLogin", func(t *testing.T) { account := &model.Account{ AccountPublic: model.AccountPublic{ AccountBase: model.AccountBase{ Describable: model.Describable{ Name: "Test User", }, }, }, Password: "TestPassword123!", } err := service.ValidateAccount(account) assert.Error(t, err) assert.Contains(t, err.Error(), "Login must not be empty") }) t.Run("AccountMissingPassword", func(t *testing.T) { account := &model.Account{ AccountPublic: model.AccountPublic{ AccountBase: model.AccountBase{ Describable: model.Describable{ Name: "Test User", }, }, UserDataBase: model.UserDataBase{ Login: "test@example.com", }, }, Password: "", } err := service.ValidateAccount(account) assert.Error(t, err) assert.Contains(t, err.Error(), "Password must not be empty") }) t.Run("AccountInvalidPassword", func(t *testing.T) { account := &model.Account{ AccountPublic: model.AccountPublic{ AccountBase: model.AccountBase{ Describable: model.Describable{ Name: "Test User", }, }, UserDataBase: model.UserDataBase{ Login: "test@example.com", }, }, Password: "weak", // Should fail validation } err := service.ValidateAccount(account) assert.Error(t, err) // Should fail on password validation assert.Contains(t, err.Error(), "at least 8 characters") }) } // TestPasswordConfiguration verifies different password rule configurations func TestPasswordConfiguration(t *testing.T) { t.Run("MinimalRequirements", func(t *testing.T) { config := &apiconfig.PasswordConfig{ Check: apiconfig.PasswordChecks{ MinLength: 4, Digit: false, Upper: false, Lower: false, Special: false, }, TokenLength: 16, } logger := zap.NewNop() // Use no-op logger for tests service := &service{ config: config, logger: logger, } // Should pass with minimal requirements err := service.ValidatePassword("test", nil) assert.NoError(t, err) }) t.Run("StrictRequirements", func(t *testing.T) { config := &apiconfig.PasswordConfig{ Check: apiconfig.PasswordChecks{ MinLength: 12, Digit: true, Upper: true, Lower: true, Special: true, }, TokenLength: 64, } logger := zap.NewNop() // Use no-op logger for tests service := &service{ config: config, logger: logger, } // Should fail with shorter password err := service.ValidatePassword("Test123!", nil) assert.Error(t, err) assert.Contains(t, err.Error(), "at least 12 characters") // Should pass with longer password err = service.ValidatePassword("TestPassword123!", nil) assert.NoError(t, err) }) } // TestTokenGeneration verifies that verification tokens are generated with correct length func TestTokenGeneration(t *testing.T) { testCases := []struct { name string tokenLength int }{ {"Short", 8}, {"Medium", 32}, {"Long", 64}, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { config := &apiconfig.PasswordConfig{ Check: apiconfig.PasswordChecks{ MinLength: 8, Digit: true, Upper: true, Lower: true, Special: true, }, TokenLength: tc.tokenLength, } logger := zap.NewNop() // Use no-op logger for tests service := &service{ config: config, logger: logger, } account := &model.Account{ AccountPublic: model.AccountPublic{ AccountBase: model.AccountBase{ Describable: model.Describable{ Name: "Test User", }, }, UserDataBase: model.UserDataBase{ Login: "test@example.com", }, }, Password: "TestPassword123!", } err := service.ValidateAccount(account) require.NoError(t, err) assert.Equal(t, tc.tokenLength, len(account.VerifyToken)) }) } }