299 lines
7.3 KiB
Go
299 lines
7.3 KiB
Go
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))
|
|
})
|
|
}
|
|
}
|