Files
RenewCMS/features/registration_gate_and_verification_email.qa.md

5.1 KiB

QA procedure: registration_gate_and_verification_email

Package: test (black-box), extending test/user_test.go (mockUserRepo, stubHasher) and test/mail_test.go (mockMailRepo, recordedMail, failSwitch).

Setup shared by every case

  1. Call validator.InitValidator() once.
  2. Use-case-level checks build: useCases.NewCreateUserUseCase(repo, &stubHasher{}, mailRepo, "https://cms.example.com") where repo is a mockUserRepo and mailRepo is a mockMailRepo. Use mailRepo.fail.failNext("Send") to force the next Send call to fail for the rollback cases.
  3. HTTP-level checks wire mockUserRepo, mockMailRepo, useCases.NewCreateUserUseCase(repo, &stubHasher{}, mailRepo, publicURL), useCases.NewListUsersUseCase(repo), useCases.NewGetUserUseCase(repo), useCases.NewAuthenticateUserUseCase(repo, ...) into auth.NewHandler(...) (which no longer takes a mail use case or a public URL) with a test jwtauth.New("HS256", []byte("test-secret"), nil). Mount POST /v1/auth/register unprotected on a fresh chi.NewRouter() mirroring api/router.go. No live server, no real SMTP. Default publicURL for cases that don't care about its value is "https://cms.example.com".

Use-case-level checks (no HTTP)

  1. Repository pre-populated with one verified user (IsVerified: true). Call CreateUser with a new username/email/password. Assert errors.Is(err, user.ErrRegistrationClosed). Assert the repository's Create was never called (e.g. repo's user count is unchanged, or a fail.failNext("Create") sentinel that would only trip if reached). Assert mailRepo.SentEmails is empty.
  2. Repository pre-populated with two unverified users. Call CreateUser with a new username. Assert err == nil, the returned user has IsVerified == false, and mailRepo.SentEmails has exactly one entry for the new user's email with Template == "mailValidation".
  3. On an empty repository, call CreateUser. Assert err == nil and abs(result.VerificationExpiration.Sub(time.Now().Add(2*time.Hour))) < 5*time.Second.
  4. On an empty repository, set mailRepo.fail.failNext("Send"). Call CreateUser with username "alice"/email "alice@example.com". Assert err != nil (wrapping the mail repository's failure, e.g. errors.Is(err, errRepositoryFailure) per the existing failSwitch convention). Assert repo.GetByUsername("alice") returns user.ErrUserNotFound (the created row was deleted). Then clear the failure and call CreateUser again with the same username/email → assert err == nil.

HTTP-level checks (fakes, per the shared setup above)

  1. POST /v1/auth/register with {"username":"alice","password":"hunter2example12","email":"alice@example.com"} against an empty mockUserRepo → assert status 200, a Set-Cookie: jwt=... header present. Assert mailRepo.SentEmails has exactly one entry with Address == "alice@example.com" and Template == "mailValidation".
  2. Inspect that entry's Data and assert it carries the raw verification code (compare against the VerificationCode field on the user returned by CreateUser/stored via the mock repo lookup) — not the hashed value the repository stores.
  3. Build the handler from a CreateUserUseCase constructed with publicURL = "https://cms.example.com". Repeat the registration call. Assert the recorded mail data's Host field equals "https://cms.example.com". Then set the process env var PUBLIC_URL to a different value and register a second user with the same handler instance; assert the second email's Host is still "https://cms.example.com" (construction-time value, not read again from the environment).
  4. Seed mockUserRepo with one user having IsVerified: true. POST /v1/auth/register with a new username/email → assert status 403, body exactly "You are not allowed to create a user. Log in or reset database." (the value of the new apperr constant), mockUserRepo still holds only the original user, and mailRepo.SentEmails is still empty.
  5. On an empty mockUserRepo, set mailRepo.fail.failNext("Send"). POST /v1/auth/register with {"username":"alice","password":"hunter2example12","email":"alice@example.com"} → assert status 500, no Set-Cookie: jwt=... header present, and mockUserRepo does not contain a user named "alice". Then, without re-arming the failure, POST /v1/auth/register again with the same body → assert status 200 and a Set-Cookie: jwt=... header present (the username and email are free again).

Manual verification (not exercised by the Go test)

  • The real SMTP send (internal/infrastructure/mailer.MailRepository.Send actually dialing a server) is never exercised automatically; verify by hand against a real or sandboxed SMTP server that the rendered mailValidation.html link matches PUBLIC_URL from the deployed .env and that clicking it reaches the SPA's /register/validate route.
  • The SPA's handling of an expired or already-used verification code (any UI/UX around /register/validate?c=...) is out of scope for this slice and not covered here.