diff --git a/internal/domain/model/object/error.go b/internal/domain/model/object/error.go index 2a7ed84..2900cff 100644 --- a/internal/domain/model/object/error.go +++ b/internal/domain/model/object/error.go @@ -20,6 +20,7 @@ var ERRORS []error = []error{ ErrorUserUsernameIsTooShort, ErrorUserUsernameIsTooLong, ErrorUserUsernameIsNotValid, + ErrorUserUsernameContainsSpecialChar, ErrorUserEmailIsEmpty, ErrorUserEmailIsNotValid, ErrorUserEmailIsExists, @@ -46,12 +47,13 @@ const ( ) const ( - ErrEmpty string = "empty" - ErrTooShort string = "tooshort" - ErrTooLong string = "toolong" - ErrNotValid string = "notvalid" - ErrInactive string = "inactive" - ErrAlreadyExists string = "alreadyexists" + ErrEmpty string = "empty" + ErrTooShort string = "tooshort" + ErrTooLong string = "toolong" + ErrNotValid string = "notvalid" + ErrContainsSpecialChar string = "containsSpecialChar" + ErrInactive string = "inactive" + ErrAlreadyExists string = "alreadyexists" ) var ( @@ -68,6 +70,7 @@ var ( ErrorUserUsernameIsTooShort error = errors.New(smodel.ErrBase + smodel.ErrSep + ErrUser + smodel.ErrSep + ErrUserName + smodel.ErrSep + ErrTooShort) ErrorUserUsernameIsTooLong error = errors.New(smodel.ErrBase + smodel.ErrSep + ErrUser + smodel.ErrSep + ErrUserName + smodel.ErrSep + ErrTooLong) ErrorUserUsernameIsNotValid error = errors.New(smodel.ErrBase + smodel.ErrSep + ErrUser + smodel.ErrSep + ErrUserName + smodel.ErrSep + ErrNotValid) + ErrorUserUsernameContainsSpecialChar error = errors.New(smodel.ErrBase + smodel.ErrSep + ErrUser + smodel.ErrSep + ErrUserName + smodel.ErrSep + ErrContainsSpecialChar) ErrorUserEmailIsEmpty error = errors.New(smodel.ErrBase + smodel.ErrSep + ErrUser + smodel.ErrSep + ErrEmail + smodel.ErrSep + ErrEmpty) ErrorUserEmailIsNotValid error = errors.New(smodel.ErrBase + smodel.ErrSep + ErrUser + smodel.ErrSep + ErrEmail + smodel.ErrSep + ErrNotValid) ErrorUserEmailIsExists error = errors.New(smodel.ErrBase + smodel.ErrSep + ErrUser + smodel.ErrSep + ErrEmail + smodel.ErrSep + ErrAlreadyExists) diff --git a/internal/domain/service/service.go b/internal/domain/service/service.go index 5e7164e..976ed73 100644 --- a/internal/domain/service/service.go +++ b/internal/domain/service/service.go @@ -2,21 +2,22 @@ package domain import ( "strings" + "unicode" me "github.com/octoposprime/op-be-user/internal/domain/model/entity" mo "github.com/octoposprime/op-be-user/internal/domain/model/object" ) -// This is the service layer of the domain layer. +// Service struct for user-related services type Service struct { } -// NewService creates a new *Service. +// NewService creates a new instance of Service func NewService() *Service { return &Service{} } -// ValidateUser validates the user. +// ValidateUser validates the user based on certain criteria func (s *Service) ValidateUser(user *me.User) error { if err := user.Validate(); err != nil { return err @@ -24,7 +25,7 @@ func (s *Service) ValidateUser(user *me.User) error { return nil } -// ValidatePassword validates the user password. +// ValidatePassword checks the validity of a user's password func (s *Service) ValidatePassword(userPassword *me.UserPassword) error { if err := userPassword.Validate(); err != nil { return err @@ -32,11 +33,12 @@ func (s *Service) ValidatePassword(userPassword *me.UserPassword) error { return nil } -// ValidateToken validates the token. +// ValidateToken checks the validity of a token func (s *Service) ValidateToken(token *mo.Token) error { return token.Validate() } +// CheckUserNameRules checks the rules for usernames func (s *Service) CheckUserNameRules(user *me.User) error { if user.UserName == "" { return mo.ErrorUserUsernameIsEmpty @@ -50,17 +52,31 @@ func (s *Service) CheckUserNameRules(user *me.User) error { if strings.Contains(user.UserName, " ") { return mo.ErrorUserUsernameIsNotValid } + if !isValidUsername(user.UserName) { + return mo.ErrorUserUsernameContainsSpecialChar + } return nil } +func isValidUsername(username string) bool { + var hasLetterOrDigit bool + for _, ch := range username { + if unicode.IsLetter(ch) || unicode.IsDigit(ch) { + hasLetterOrDigit = true + } + if !unicode.IsLetter(ch) && !unicode.IsDigit(ch) && ch != '_' && ch != '.' { + return false + } + } + return hasLetterOrDigit // Make sure there is at least one alphanumeric character +} + +// CheckEmailRules validates the email structure func (s *Service) CheckEmailRules(user *me.User) error { if user.Email == "" { return mo.ErrorUserEmailIsEmpty } - if !strings.Contains(user.Email, ".") { - return mo.ErrorUserEmailIsNotValid - } - if !strings.Contains(user.Email, "@") { + if !strings.Contains(user.Email, "@") || !strings.Contains(user.Email, ".") { return mo.ErrorUserEmailIsNotValid } if strings.Contains(user.Email, " ") { @@ -69,6 +85,7 @@ func (s *Service) CheckEmailRules(user *me.User) error { return nil } +// CheckPasswordRules checks the rules for passwords func (s *Service) CheckPasswordRules(userPassword *me.UserPassword) error { if userPassword.Password == "" { return mo.ErrorUserPasswordIsEmpty @@ -85,6 +102,7 @@ func (s *Service) CheckPasswordRules(userPassword *me.UserPassword) error { return nil } +// CheckIsAuthenticable checks if a user is authenticable based on their status func (s *Service) CheckIsAuthenticable(user *me.User) error { if user.UserStatus == mo.UserStatusINACTIVE { return mo.ErrorUserIsInactive diff --git a/internal/domain/service/service_test.go b/internal/domain/service/service_test.go index 7c90146..6b4e3ff 100644 --- a/internal/domain/service/service_test.go +++ b/internal/domain/service/service_test.go @@ -3,21 +3,123 @@ package domain import ( "testing" + "github.com/google/uuid" me "github.com/octoposprime/op-be-user/internal/domain/model/entity" + mo "github.com/octoposprime/op-be-user/internal/domain/model/object" ) func TestService_CheckUserNameRules(t *testing.T) { + type args struct { user *me.User } tests := []struct { name string - s *Service args args wantErr bool }{ - // TODO: Add test cases. + { + name: "Valid Username", + args: args{user: &me.User{ + Id: uuid.UUID{}, + User: mo.User{ + UserName: "Qwe123_ee", + }, + }}, + wantErr: false, + }, + { + name: "Username With Invalid Characters", + args: args{user: &me.User{ + Id: uuid.UUID{}, + User: mo.User{ + UserName: "asd!^+.++dfghdr", + }, + }}, + wantErr: true, + }, + { + name: "Username With Space", + args: args{user: &me.User{ + Id: uuid.UUID{}, + User: mo.User{ + UserName: "user name", + }, + }}, + wantErr: true, + }, + { + name: "Too Short Username", + args: args{user: &me.User{ + Id: uuid.UUID{}, + User: mo.User{ + UserName: "short", + }, + }}, + wantErr: true, + }, + { + name: "Too Long Username", + args: args{user: &me.User{ + Id: uuid.UUID{}, + User: mo.User{ + UserName: "toolongusernameisdefinitelytoolong", + }, + }}, + wantErr: true, + }, + { + name: "Valid Numeric Username", + args: args{user: &me.User{ + Id: uuid.UUID{}, + User: mo.User{ + UserName: "12345678", + }, + }}, + wantErr: false, + }, + { + name: "Valid Username With Period", + args: args{user: &me.User{ + Id: uuid.UUID{}, + User: mo.User{ + UserName: "John.Doe", + }, + }}, + wantErr: false, + }, + { + name: "Valid Username With Underscores", + args: args{user: &me.User{ + Id: uuid.UUID{}, + User: mo.User{ + UserName: "valid_user123", + }, + }}, + wantErr: false, + }, + { + name: "All Underscores", + args: args{user: &me.User{ + Id: uuid.UUID{}, + User: mo.User{ + UserName: "________", + }, + }}, + wantErr: true, + }, + { + name: "All Periods", + args: args{user: &me.User{ + Id: uuid.UUID{}, + User: mo.User{ + UserName: "......", + }, + }}, + wantErr: true, + }, } + for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { s := &Service{}