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 }