github.com/pyroscope-io/pyroscope@v0.37.3-0.20230725203016-5f6947968bd0/pkg/service/user_test.go (about)

     1  package service_test
     2  
     3  import (
     4  	"context"
     5  
     6  	. "github.com/onsi/ginkgo/v2"
     7  	. "github.com/onsi/gomega"
     8  
     9  	"github.com/pyroscope-io/pyroscope/pkg/model"
    10  	"github.com/pyroscope-io/pyroscope/pkg/service"
    11  )
    12  
    13  var _ = Describe("UserService", func() {
    14  	s := new(testSuite)
    15  	BeforeEach(s.BeforeEach)
    16  	AfterEach(s.AfterEach)
    17  
    18  	var svc service.UserService
    19  	BeforeEach(func() {
    20  		svc = service.NewUserService(s.DB())
    21  	})
    22  
    23  	Describe("user creation", func() {
    24  		var (
    25  			params = testCreateUserParams()[0]
    26  			user   model.User
    27  			err    error
    28  		)
    29  
    30  		JustBeforeEach(func() {
    31  			user, err = svc.CreateUser(context.Background(), params)
    32  		})
    33  
    34  		Context("when a new user created", func() {
    35  			It("does not return error", func() {
    36  				Expect(err).ToNot(HaveOccurred())
    37  			})
    38  
    39  			It("should populate the fields correctly", func() {
    40  				expectUserMatches(user, params)
    41  			})
    42  
    43  			It("creates valid password hash", func() {
    44  				err = model.VerifyPassword(user.PasswordHash, params.Password)
    45  				Expect(err).ToNot(HaveOccurred())
    46  			})
    47  		})
    48  
    49  		Context("when user email is not provided", func() {
    50  			BeforeEach(func() {
    51  				params.Email = nil
    52  			})
    53  
    54  			It("does not return error", func() {
    55  				Expect(err).ToNot(HaveOccurred())
    56  			})
    57  
    58  			It("returns validation error", func() {
    59  				expectUserMatches(user, params)
    60  			})
    61  		})
    62  
    63  		Context("when user name is already in use", func() {
    64  			BeforeEach(func() {
    65  				_, err = svc.CreateUser(context.Background(), params)
    66  				Expect(err).ToNot(HaveOccurred())
    67  				params.Email = model.String("another@example.local")
    68  			})
    69  
    70  			It("returns validation error", func() {
    71  				Expect(err).To(MatchError(model.ErrUserNameExists))
    72  			})
    73  		})
    74  
    75  		Context("when user email is already in use", func() {
    76  			BeforeEach(func() {
    77  				_, err = svc.CreateUser(context.Background(), params)
    78  				Expect(err).ToNot(HaveOccurred())
    79  			})
    80  
    81  			It("returns validation error", func() {
    82  				Expect(err).To(MatchError(model.ErrUserEmailExists))
    83  			})
    84  		})
    85  
    86  		Context("when user is invalid", func() {
    87  			BeforeEach(func() {
    88  				params = model.CreateUserParams{}
    89  			})
    90  
    91  			It("returns validation error", func() {
    92  				Expect(model.IsValidationError(err)).To(BeTrue())
    93  			})
    94  		})
    95  	})
    96  
    97  	Describe("user retrieval", func() {
    98  		var (
    99  			params = testCreateUserParams()[0]
   100  			user   model.User
   101  			err    error
   102  		)
   103  
   104  		Context("when an existing user is queried", func() {
   105  			BeforeEach(func() {
   106  				user, err = svc.CreateUser(context.Background(), params)
   107  				Expect(err).ToNot(HaveOccurred())
   108  			})
   109  
   110  			It("can be found", func() {
   111  				By("id", func() {
   112  					user, err = svc.FindUserByID(context.Background(), user.ID)
   113  					Expect(err).ToNot(HaveOccurred())
   114  					expectUserMatches(user, params)
   115  				})
   116  
   117  				By("email", func() {
   118  					user, err = svc.FindUserByEmail(context.Background(), *params.Email)
   119  					Expect(err).ToNot(HaveOccurred())
   120  					expectUserMatches(user, params)
   121  				})
   122  
   123  				By("name", func() {
   124  					user, err = svc.FindUserByName(context.Background(), params.Name)
   125  					Expect(err).ToNot(HaveOccurred())
   126  					expectUserMatches(user, params)
   127  				})
   128  			})
   129  		})
   130  
   131  		Context("when a non-existing user is queried", func() {
   132  			It("returns ErrUserNotFound error of NotFoundError type", func() {
   133  				By("id", func() {
   134  					_, err = svc.FindUserByID(context.Background(), 0)
   135  					Expect(err).To(MatchError(model.ErrUserNotFound))
   136  				})
   137  
   138  				By("email", func() {
   139  					_, err = svc.FindUserByEmail(context.Background(), *params.Email)
   140  					Expect(err).To(MatchError(model.ErrUserNotFound))
   141  				})
   142  
   143  				By("name", func() {
   144  					_, err = svc.FindUserByName(context.Background(), params.Name)
   145  					Expect(err).To(MatchError(model.ErrUserNotFound))
   146  				})
   147  			})
   148  		})
   149  	})
   150  
   151  	Describe("users retrieval", func() {
   152  		var (
   153  			params = testCreateUserParams()
   154  			users  []model.User
   155  			err    error
   156  		)
   157  
   158  		JustBeforeEach(func() {
   159  			users, err = svc.GetAllUsers(context.Background())
   160  		})
   161  
   162  		Context("when all users are queried", func() {
   163  			BeforeEach(func() {
   164  				for _, user := range params {
   165  					_, err = svc.CreateUser(context.Background(), user)
   166  					Expect(err).ToNot(HaveOccurred())
   167  				}
   168  			})
   169  
   170  			It("does not return error", func() {
   171  				Expect(err).ToNot(HaveOccurred())
   172  			})
   173  
   174  			It("returns all users", func() {
   175  				user1, err := svc.FindUserByEmail(context.Background(), *params[0].Email)
   176  				Expect(err).ToNot(HaveOccurred())
   177  				user2, err := svc.FindUserByEmail(context.Background(), *params[1].Email)
   178  				Expect(err).ToNot(HaveOccurred())
   179  				Expect(users).To(ConsistOf(user1, user2))
   180  			})
   181  		})
   182  
   183  		Context("when no users exist", func() {
   184  			It("returns no error", func() {
   185  				Expect(err).ToNot(HaveOccurred())
   186  				Expect(users).To(BeEmpty())
   187  			})
   188  		})
   189  	})
   190  
   191  	Describe("user update", func() {
   192  		var (
   193  			params  = testCreateUserParams()
   194  			update  model.UpdateUserParams
   195  			user    model.User
   196  			updated model.User
   197  			err     error
   198  		)
   199  
   200  		JustBeforeEach(func() {
   201  			updated, err = svc.UpdateUserByID(context.Background(), user.ID, update)
   202  		})
   203  
   204  		Context("when no parameters specified", func() {
   205  			BeforeEach(func() {
   206  				user, err = svc.CreateUser(context.Background(), params[0])
   207  				Expect(err).ToNot(HaveOccurred())
   208  			})
   209  
   210  			It("does not return error", func() {
   211  				Expect(err).ToNot(HaveOccurred())
   212  			})
   213  
   214  			It("does not change user", func() {
   215  				updated, err = svc.FindUserByID(context.Background(), user.ID)
   216  				Expect(err).ToNot(HaveOccurred())
   217  				expectUserMatches(updated, params[0])
   218  			})
   219  		})
   220  
   221  		Context("when parameters provided", func() {
   222  			BeforeEach(func() {
   223  				user, err = svc.CreateUser(context.Background(), params[0])
   224  				Expect(err).ToNot(HaveOccurred())
   225  				update = model.UpdateUserParams{
   226  					Name:     model.String("not-a-johndoe"),
   227  					Email:    model.String("john.doe@example.com"),
   228  					FullName: model.String("John Doe"),
   229  					Password: model.String("qwerty")}.
   230  					SetRole(model.ReadOnlyRole).
   231  					SetIsDisabled(true)
   232  			})
   233  
   234  			It("does not return error", func() {
   235  				Expect(err).ToNot(HaveOccurred())
   236  			})
   237  
   238  			It("updates user fields", func() {
   239  				updated, err = svc.FindUserByID(context.Background(), user.ID)
   240  				Expect(err).ToNot(HaveOccurred())
   241  				Expect(updated.Name).To(Equal(*update.Name))
   242  				Expect(updated.Email).To(Equal(update.Email))
   243  				Expect(updated.FullName).To(Equal(update.FullName))
   244  				Expect(updated.Role).To(Equal(*update.Role))
   245  				Expect(*updated.IsDisabled).To(BeTrue())
   246  				Expect(updated.CreatedAt).ToNot(BeZero())
   247  				Expect(updated.UpdatedAt).ToNot(BeZero())
   248  				Expect(updated.UpdatedAt).ToNot(Equal(updated.CreatedAt))
   249  				Expect(updated.PasswordHash).ToNot(Equal(user.PasswordHash))
   250  				Expect(updated.PasswordChangedAt).ToNot(BeZero())
   251  				Expect(updated.PasswordChangedAt).ToNot(Equal(user.PasswordChangedAt))
   252  			})
   253  		})
   254  
   255  		Context("when parameters invalid", func() {
   256  			BeforeEach(func() {
   257  				user, err = svc.CreateUser(context.Background(), params[0])
   258  				Expect(err).ToNot(HaveOccurred())
   259  				update = model.UpdateUserParams{
   260  					Name:     model.String(""),
   261  					Email:    model.String(""),
   262  					FullName: model.String(""),
   263  					Password: model.String("")}.
   264  					SetRole(model.InvalidRole)
   265  			})
   266  
   267  			It("returns ValidationError", func() {
   268  				Expect(model.IsValidationError(err)).To(BeTrue())
   269  				Expect(err).To(MatchError(model.ErrUserNameEmpty))
   270  				Expect(err).To(MatchError(model.ErrUserEmailInvalid))
   271  				Expect(err).To(MatchError(model.ErrRoleUnknown))
   272  				Expect(err).To(MatchError(model.ErrUserPasswordEmpty))
   273  			})
   274  		})
   275  
   276  		Context("when user is disabled", func() {
   277  			BeforeEach(func() {
   278  				user, err = svc.CreateUser(context.Background(), params[0])
   279  				Expect(err).ToNot(HaveOccurred())
   280  				update = model.UpdateUserParams{}.SetIsDisabled(true)
   281  			})
   282  
   283  			It("can be enabled again", func() {
   284  				Expect(err).ToNot(HaveOccurred())
   285  
   286  				update = model.UpdateUserParams{}.SetIsDisabled(false)
   287  				_, err = svc.UpdateUserByID(context.Background(), user.ID, update)
   288  				Expect(err).ToNot(HaveOccurred())
   289  
   290  				updated, err = svc.FindUserByID(context.Background(), user.ID)
   291  				Expect(err).ToNot(HaveOccurred())
   292  				Expect(model.IsUserDisabled(updated)).To(BeFalse())
   293  			})
   294  		})
   295  
   296  		Context("when email is already in use", func() {
   297  			BeforeEach(func() {
   298  				var user2 model.User
   299  				user, err = svc.CreateUser(context.Background(), params[0])
   300  				Expect(err).ToNot(HaveOccurred())
   301  				user2, err = svc.CreateUser(context.Background(), params[1])
   302  				Expect(err).ToNot(HaveOccurred())
   303  				update = model.UpdateUserParams{Email: user2.Email}
   304  			})
   305  
   306  			It("returns ErrUserEmailExists error", func() {
   307  				Expect(err).To(MatchError(model.ErrUserEmailExists))
   308  			})
   309  		})
   310  
   311  		Context("when user name is already in use", func() {
   312  			BeforeEach(func() {
   313  				var user2 model.User
   314  				user, err = svc.CreateUser(context.Background(), params[0])
   315  				Expect(err).ToNot(HaveOccurred())
   316  				user2, err = svc.CreateUser(context.Background(), params[1])
   317  				Expect(err).ToNot(HaveOccurred())
   318  				update = model.UpdateUserParams{Name: &user2.Name}
   319  			})
   320  
   321  			It("returns ErrUserNameExists error", func() {
   322  				Expect(err).To(MatchError(model.ErrUserNameExists))
   323  			})
   324  		})
   325  
   326  		Context("when user not found", func() {
   327  			It("returns ErrUserNotFound error", func() {
   328  				Expect(err).To(MatchError(model.ErrUserNotFound))
   329  			})
   330  		})
   331  	})
   332  
   333  	Describe("user password change", func() {
   334  		var (
   335  			userParams = testCreateUserParams()[0]
   336  
   337  			params model.UpdateUserPasswordParams
   338  			user   model.User
   339  			err    error
   340  		)
   341  
   342  		JustBeforeEach(func() {
   343  			err = svc.UpdateUserPasswordByID(context.Background(), user.ID, params)
   344  		})
   345  
   346  		Context("when user exists", func() {
   347  			BeforeEach(func() {
   348  				user, err = svc.CreateUser(context.Background(), userParams)
   349  				Expect(err).ToNot(HaveOccurred())
   350  			})
   351  
   352  			Context("when new password does not meet criteria", func() {
   353  				It("returns validation error", func() {
   354  					Expect(model.IsValidationError(err)).To(BeTrue())
   355  				})
   356  			})
   357  
   358  			Context("when old password does not match", func() {
   359  				BeforeEach(func() {
   360  					// params.OldPassword = "invalid"
   361  					params.NewPassword = "whatever"
   362  				})
   363  
   364  				It("returns ErrUserPasswordInvalid", func() {
   365  					Expect(err).To(MatchError(model.ErrUserPasswordInvalid))
   366  				})
   367  			})
   368  
   369  			Context("when old password matches", func() {
   370  				BeforeEach(func() {
   371  					params.OldPassword = userParams.Password
   372  					params.NewPassword = "qwerty2"
   373  				})
   374  
   375  				It("returns ErrCredentialsInvalid", func() {
   376  					Expect(err).ToNot(HaveOccurred())
   377  				})
   378  			})
   379  		})
   380  
   381  		Context("when user does not exist", func() {
   382  			It("returns ErrUserNotFound", func() {
   383  				Expect(err).To(MatchError(model.ErrUserNotFound))
   384  			})
   385  		})
   386  	})
   387  
   388  	Describe("user delete", func() {
   389  		var (
   390  			params = testCreateUserParams()
   391  			users  []model.User
   392  			err    error
   393  		)
   394  
   395  		JustBeforeEach(func() {
   396  			err = svc.DeleteUserByID(context.Background(), users[0].ID)
   397  		})
   398  
   399  		Context("when existing user deleted", func() {
   400  			BeforeEach(func() {
   401  				users = users[:0]
   402  				for _, u := range params {
   403  					user, err := svc.CreateUser(context.Background(), u)
   404  					Expect(err).ToNot(HaveOccurred())
   405  					users = append(users, user)
   406  				}
   407  			})
   408  
   409  			It("does not return error", func() {
   410  				Expect(err).ToNot(HaveOccurred())
   411  			})
   412  
   413  			It("removes user from the database", func() {
   414  				_, err = svc.FindUserByID(context.Background(), users[0].ID)
   415  				Expect(err).To(MatchError(model.ErrUserNotFound))
   416  			})
   417  
   418  			It("does not affect other users", func() {
   419  				_, err = svc.FindUserByID(context.Background(), users[1].ID)
   420  				Expect(err).ToNot(HaveOccurred())
   421  			})
   422  
   423  			It("allows user with the same email or name to be created", func() {
   424  				_, err = svc.CreateUser(context.Background(), params[0])
   425  				Expect(err).ToNot(HaveOccurred())
   426  			})
   427  		})
   428  
   429  		Context("when non-existing user deleted", func() {
   430  			It("does not return error", func() {
   431  				Expect(err).ToNot(HaveOccurred())
   432  			})
   433  		})
   434  	})
   435  })
   436  
   437  func testCreateUserParams() []model.CreateUserParams {
   438  	return []model.CreateUserParams{
   439  		{
   440  			Name:     "johndoe",
   441  			Email:    model.String("john@example.com"),
   442  			FullName: model.String("John Doe"),
   443  			Password: "qwerty",
   444  			Role:     model.ReadOnlyRole,
   445  		},
   446  		{
   447  			Name:     "admin",
   448  			Email:    model.String("admin@local.domain"),
   449  			FullName: model.String("Administrator"),
   450  			Password: "qwerty",
   451  			Role:     model.AdminRole,
   452  		},
   453  	}
   454  }
   455  
   456  func expectUserMatches(user model.User, params model.CreateUserParams) {
   457  	Expect(user.Name).To(Equal(params.Name))
   458  	Expect(user.Email).To(Equal(params.Email))
   459  	Expect(user.FullName).To(Equal(params.FullName))
   460  	Expect(user.Role).To(Equal(params.Role))
   461  	Expect(*user.IsDisabled).To(BeFalse())
   462  	Expect(user.CreatedAt).ToNot(BeZero())
   463  	Expect(user.UpdatedAt).ToNot(BeZero())
   464  	Expect(user.LastSeenAt).To(BeZero())
   465  	Expect(user.PasswordChangedAt).ToNot(BeZero())
   466  	err := model.VerifyPassword(user.PasswordHash, params.Password)
   467  	Expect(err).ToNot(HaveOccurred())
   468  }