64 lines
4.3 KiB
Markdown
64 lines
4.3 KiB
Markdown
# QA procedure: hasher_domain_interface
|
||
|
||
Package: `test` (black-box, alongside the existing `article_test.go` / `user_test.go` /
|
||
`use_case_baseline_test.go` / `not_found_errors_test.go`).
|
||
|
||
## Fakes needed
|
||
1. A `fakeHasher` implementing `hash.Hasher`:
|
||
- `Hash(value string) (string, error)`: by default returns `"hashed:" + value, nil`;
|
||
configurable to return `("", someErr)` unconditionally.
|
||
- `Compare(hashed, value string) error`: by default returns `nil` when
|
||
`hashed == "hashed:"+value`, else a sentinel mismatch error; configurable to always
|
||
error, and to record whether it was called.
|
||
2. Extend the existing `mockUserRepo` (from `test/user_test.go` /
|
||
`not_found_errors_test.go`) with call-recording flags already used elsewhere in the
|
||
package (`createCalled`, `updatePasswordCalled`, etc.) if not already present, so tests
|
||
can assert a hashing failure short-circuits before the repository is touched.
|
||
|
||
## Use-case-level checks (fake hasher + fake repo, no HTTP)
|
||
3. `useCases.NewCreateUserUseCase(fakeRepo, fakeHasher)`, `CreateUser(...)`: assert the
|
||
`User` argument the fake repo's `Create` receives has `Password == "hashed:s3cretPassw0rd"`
|
||
and a `VerificationCode` that starts with `"hashed:"`; assert the `User` returned by the
|
||
use case has a `VerificationCode` that does **not** start with `"hashed:"` (it is the raw
|
||
UUID, unchanged from current behaviour).
|
||
4. Same use case with `fakeHasher` configured to always error on `Hash`: assert
|
||
`CreateUser` returns that error and `fakeRepo.Create` was never called.
|
||
7. `useCases.NewAuthenticateUserUseCase(fakeRepo, fakeHasher)` (new use case): seed
|
||
`fakeRepo` with a user "alice" whose stored `Password` is `"hashed:hunter2example"`.
|
||
- `Authenticate("alice", "hunter2example")` with `fakeHasher.Compare` returning `nil` →
|
||
assert the returned user matches and `err` is `nil`.
|
||
- `Authenticate("alice", "wrongpassword12")` with `fakeHasher.Compare` returning an error
|
||
→ assert `errors.Is(err, user.ErrInvalidCredentials)` is `true`.
|
||
- `Authenticate("bob", "hunter2example")` with no "bob" in the repo → assert
|
||
`errors.Is(err, user.ErrUserNotFound)` is `true` and `fakeHasher`'s `Compare` was never
|
||
invoked (record a `compareCalled` flag).
|
||
|
||
## HTTP checks (real BcryptHasher, in-memory fake repo, mirroring api/router.go)
|
||
8. Call `validator.InitValidator()`.
|
||
9. Build `hashing.NewBcryptHasher()`, wire `useCases.NewCreateUserUseCase(fakeRepo,
|
||
bcryptHasher)` and `useCases.NewAuthenticateUserUseCase(fakeRepo, bcryptHasher)` (plus
|
||
the existing `GetUserUseCase` / `ListUsersUseCase` the auth handler still needs), build
|
||
`auth.NewHandler(token, get, list, create, authenticate)` (constructor signature grows by
|
||
the new use case) with a test `jwtauth.New("HS256", []byte("test-secret"), nil)`, mount
|
||
its routes unprotected on a fresh `chi.NewRouter()` as in `api/router.go`.
|
||
10. Seed the fake user repo directly with a user "alice" whose `Password` field holds a real
|
||
`bcryptHasher.Hash("hunter2example")` result (not `bcrypt.GenerateFromPassword` called
|
||
from the test, so the test never imports bcrypt itself).
|
||
11. `POST /v1/auth/login` with `{"username":"alice","password":"wrongpassword12"}` → assert
|
||
status `403`, body `apperr.LogsErrorMessage + "\n"`, no `Set-Cookie: jwt=...` header.
|
||
12. `POST /v1/auth/login` with `{"username":"bob","password":"hunter2example"}` (no such
|
||
user) → assert identical status/body/no-cookie outcome to step 11 (regression, same as
|
||
`not_found_errors.qa.md` steps 13–14).
|
||
13. `POST /v1/auth/register` with `{"username":"alice","password":"s3cretPassw0rd","email":"alice@example.com"}`
|
||
against an empty fake repo → assert status `200`, a `Set-Cookie: jwt=...` header is
|
||
present, and the `Password` the fake repo's `Create` received is neither empty nor equal
|
||
to `"s3cretPassw0rd"`; assert `bcryptHasher.Compare(storedPassword, "s3cretPassw0rd")`
|
||
returns `nil`.
|
||
14. Rebuild the handler with a `fakeHasher` (from step 1) whose `Hash` always errors in
|
||
place of `bcryptHasher`, same empty fake repo. `POST /v1/auth/register` with the same
|
||
valid body → assert status `500`, no `Set-Cookie` header, and the fake repo's `Create`
|
||
was never called.
|
||
|
||
## Manual verification (not exercised by the Go test)
|
||
- None. This slice has no SPA-visible behaviour and no SMTP involvement.
|