package validators import ( "testing" "github.com/stretchr/testify/assert" ) func TestNewPasswordValidator(t *testing.T) { validator := NewPasswordValidator() assert.NotNil(t, validator) assert.Equal(t, 12, validator.MinLength) } func TestPasswordValidator_Validate_ValidPassword(t *testing.T) { validator := NewPasswordValidator() tests := []struct { name string password string }{ { name: "valid password with all requirements", password: "SecurePass123!", }, { name: "valid password with special chars", password: "MyP@ssw0rd!Test", }, { name: "valid password with multiple special chars", password: "Test#123$Pass", }, { name: "valid password exactly 12 chars", password: "Test123!Pass", }, { name: "valid password longer than 12 chars", password: "VerySecurePassword123!@#", }, { name: "valid password with parentheses", password: "Test(123)Pass", }, { name: "valid password with comma", password: "Test,123Pass", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { strength, err := validator.Validate(tt.password) assert.NoError(t, err) assert.True(t, strength.Valid, "Password should be valid: %s", tt.password) assert.Equal(t, 4, strength.Score, "Password should have score 4") assert.Empty(t, strength.Details, "Should have no validation errors") }) } } func TestPasswordValidator_Validate_InvalidLength(t *testing.T) { validator := NewPasswordValidator() tests := []struct { name string password string }{ { name: "password too short - 11 chars", password: "Test123!Pas", }, { name: "password too short - 5 chars", password: "Test1", }, { name: "empty password", password: "", }, { name: "password too short - 1 char", password: "T", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { strength, err := validator.Validate(tt.password) assert.NoError(t, err) assert.False(t, strength.Valid, "Password should be invalid: %s", tt.password) assert.Contains(t, strength.Details, "Password must be at least 12 characters long") }) } } func TestPasswordValidator_Validate_MissingUpperCase(t *testing.T) { validator := NewPasswordValidator() tests := []struct { name string password string }{ { name: "password without uppercase", password: "testpassword123!", }, { name: "password without uppercase - all lowercase", password: "lowercase123!@#", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { strength, err := validator.Validate(tt.password) assert.NoError(t, err) assert.False(t, strength.Valid, "Password should be invalid: %s", tt.password) assert.Contains(t, strength.Details, "Must contain uppercase letter") }) } } func TestPasswordValidator_Validate_MissingLowerCase(t *testing.T) { validator := NewPasswordValidator() tests := []struct { name string password string }{ { name: "password without lowercase", password: "TESTPASSWORD123!", }, { name: "password without lowercase - all uppercase", password: "UPPERCASE123!@#", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { strength, err := validator.Validate(tt.password) assert.NoError(t, err) assert.False(t, strength.Valid, "Password should be invalid: %s", tt.password) assert.Contains(t, strength.Details, "Must contain lowercase letter") }) } } func TestPasswordValidator_Validate_MissingNumber(t *testing.T) { validator := NewPasswordValidator() tests := []struct { name string password string }{ { name: "password without number", password: "TestPassword!", }, { name: "password without number - only letters and special", password: "TestPass!@#$", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { strength, err := validator.Validate(tt.password) assert.NoError(t, err) assert.False(t, strength.Valid, "Password should be invalid: %s", tt.password) assert.Contains(t, strength.Details, "Must contain number") }) } } func TestPasswordValidator_Validate_MissingSpecialChar(t *testing.T) { validator := NewPasswordValidator() tests := []struct { name string password string }{ { name: "password without special char", password: "TestPassword123", }, { name: "password without special char - only alphanumeric", password: "TestPass9753", // Pas de pattern séquentiel (9753 n'est pas dans les séquences) }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { strength, err := validator.Validate(tt.password) assert.NoError(t, err) assert.False(t, strength.Valid, "Password should be invalid: %s", tt.password) assert.Contains(t, strength.Details, "Must contain special character") }) } } func TestPasswordValidator_Validate_MultipleMissing(t *testing.T) { validator := NewPasswordValidator() tests := []struct { name string password string expectedErrors []string }{ { name: "missing uppercase and lowercase", password: "9753108642!!", // 12 caractères, pas de pattern séquentiel (mélange de chiffres non séquentiels) expectedErrors: []string{"Must contain uppercase letter", "Must contain lowercase letter"}, }, { name: "missing number and special char", password: "TestPassword", expectedErrors: []string{"Must contain number", "Must contain special character"}, }, { name: "missing uppercase, number and special", password: "testpassword", expectedErrors: []string{"Must contain uppercase letter", "Must contain number", "Must contain special character"}, }, { name: "missing everything except length", password: "alllowercase", expectedErrors: []string{"Must contain uppercase letter", "Must contain number", "Must contain special character"}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { strength, err := validator.Validate(tt.password) assert.NoError(t, err) assert.False(t, strength.Valid, "Password should be invalid: %s", tt.password) for _, expectedError := range tt.expectedErrors { assert.Contains(t, strength.Details, expectedError) } }) } } func TestPasswordValidator_Validate_Score(t *testing.T) { validator := NewPasswordValidator() tests := []struct { name string password string expectedScore int expectedValid bool }{ { name: "valid password - score 4", password: "Test123!Pass", expectedScore: 4, expectedValid: true, }, { name: "missing uppercase - score 3", password: "test123!pass", expectedScore: 3, expectedValid: false, }, { name: "missing lowercase - score 3", password: "TEST123!PASS", expectedScore: 3, expectedValid: false, }, { name: "missing number - score 3", password: "TestPass!Word", expectedScore: 3, expectedValid: false, }, { name: "missing special - score 3", password: "TestPass9753", // Pas de pattern séquentiel (9753 n'est pas dans les séquences) expectedScore: 3, expectedValid: false, }, { name: "missing 3 requirements - score 1 (only lowercase)", password: "testpassword", expectedScore: 1, expectedValid: false, }, { name: "too short - returns early with score 0", password: "testonly", expectedScore: 0, expectedValid: false, }, { name: "only lowercase - score 1", password: "lowercaseonly", expectedScore: 1, expectedValid: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { strength, err := validator.Validate(tt.password) assert.NoError(t, err) assert.Equal(t, tt.expectedValid, strength.Valid) assert.Equal(t, tt.expectedScore, strength.Score) }) } } func TestPasswordValidator_Validate_SpecialCharacters(t *testing.T) { validator := NewPasswordValidator() specialChars := []string{"!", "@", "#", "$", "%", "^", "&", "*", "(", ")", ",", ".", "?", ":", "\"", "{", "}", "|", "<", ">"} for _, special := range specialChars { t.Run("special char: "+special, func(t *testing.T) { password := "TestPass123" + special strength, err := validator.Validate(password) assert.NoError(t, err) assert.True(t, strength.Valid, "Password with special char %s should be valid", special) }) } } func TestPasswordValidator_Validate_EdgeCases(t *testing.T) { validator := NewPasswordValidator() tests := []struct { name string password string want bool }{ { name: "password with unicode characters", password: "Test123!Passé", want: true, }, { name: "password with spaces", password: "Test 123!Pass", want: true, // Spaces are allowed, just not in the special char regex }, { name: "password with tabs", password: "Test123!Pass\t", want: true, }, { name: "password with newlines", password: "Test123!Pass\n", want: true, }, { name: "password with special chars not in regex", password: "Test123-Pass_", // - and _ are not in the special char regex want: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { strength, err := validator.Validate(tt.password) assert.NoError(t, err) assert.Equal(t, tt.want, strength.Valid) }) } }