5.1 KiB
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
- Call
validator.InitValidator()once. - Use-case-level checks build:
useCases.NewCreateUserUseCase(repo, &stubHasher{}, mailRepo, "https://cms.example.com")whererepois amockUserRepoandmailRepois amockMailRepo. UsemailRepo.fail.failNext("Send")to force the nextSendcall to fail for the rollback cases. - HTTP-level checks wire
mockUserRepo,mockMailRepo,useCases.NewCreateUserUseCase(repo, &stubHasher{}, mailRepo, publicURL),useCases.NewListUsersUseCase(repo),useCases.NewGetUserUseCase(repo),useCases.NewAuthenticateUserUseCase(repo, ...)intoauth.NewHandler(...)(which no longer takes a mail use case or a public URL) with a testjwtauth.New("HS256", []byte("test-secret"), nil). MountPOST /v1/auth/registerunprotected on a freshchi.NewRouter()mirroringapi/router.go. No live server, no real SMTP. DefaultpublicURLfor cases that don't care about its value is"https://cms.example.com".
Use-case-level checks (no HTTP)
- Repository pre-populated with one verified user (
IsVerified: true). CallCreateUserwith a new username/email/password. Asserterrors.Is(err, user.ErrRegistrationClosed). Assert the repository'sCreatewas never called (e.g. repo's user count is unchanged, or afail.failNext("Create")sentinel that would only trip if reached). AssertmailRepo.SentEmailsis empty. - Repository pre-populated with two unverified users. Call
CreateUserwith a new username. Asserterr == nil, the returned user hasIsVerified == false, andmailRepo.SentEmailshas exactly one entry for the new user's email withTemplate == "mailValidation". - On an empty repository, call
CreateUser. Asserterr == nilandabs(result.VerificationExpiration.Sub(time.Now().Add(2*time.Hour))) < 5*time.Second. - On an empty repository, set
mailRepo.fail.failNext("Send"). CallCreateUserwith username "alice"/email "alice@example.com". Asserterr != nil(wrapping the mail repository's failure, e.g.errors.Is(err, errRepositoryFailure)per the existingfailSwitchconvention). Assertrepo.GetByUsername("alice")returnsuser.ErrUserNotFound(the created row was deleted). Then clear the failure and callCreateUseragain with the same username/email → asserterr == nil.
HTTP-level checks (fakes, per the shared setup above)
POST /v1/auth/registerwith{"username":"alice","password":"hunter2example12","email":"alice@example.com"}against an emptymockUserRepo→ assert status200, aSet-Cookie: jwt=...header present. AssertmailRepo.SentEmailshas exactly one entry withAddress == "alice@example.com"andTemplate == "mailValidation".- Inspect that entry's
Dataand assert it carries the raw verification code (compare against theVerificationCodefield on the user returned byCreateUser/stored via the mock repo lookup) — not the hashed value the repository stores. - Build the handler from a
CreateUserUseCaseconstructed withpublicURL = "https://cms.example.com". Repeat the registration call. Assert the recorded mail data'sHostfield equals"https://cms.example.com". Then set the process env varPUBLIC_URLto a different value and register a second user with the same handler instance; assert the second email'sHostis still"https://cms.example.com"(construction-time value, not read again from the environment). - Seed
mockUserRepowith one user havingIsVerified: true.POST /v1/auth/registerwith a new username/email → assert status403, body exactly"You are not allowed to create a user. Log in or reset database."(the value of the newapperrconstant),mockUserRepostill holds only the original user, andmailRepo.SentEmailsis still empty. - On an empty
mockUserRepo, setmailRepo.fail.failNext("Send").POST /v1/auth/registerwith{"username":"alice","password":"hunter2example12","email":"alice@example.com"}→ assert status500, noSet-Cookie: jwt=...header present, andmockUserRepodoes not contain a user named "alice". Then, without re-arming the failure,POST /v1/auth/registeragain with the same body → assert status200and aSet-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.Sendactually dialing a server) is never exercised automatically; verify by hand against a real or sandboxed SMTP server that the renderedmailValidation.htmllink matchesPUBLIC_URLfrom the deployed.envand that clicking it reaches the SPA's/register/validateroute. - 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.