Files
sendico/api/server/interface/accountservice/internal/service_test.go
Stephan D 49b86efecb
Some checks failed
ci/woodpecker/push/db Pipeline was successful
ci/woodpecker/push/fx/1 Pipeline failed
ci/woodpecker/push/nats Pipeline was successful
ci/woodpecker/push/fx/2 Pipeline failed
fx build fix
2025-11-08 00:30:29 +01:00

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))
})
}
}