Files
RenewCMS/features/not_found_errors.qa.md

3.9 KiB

QA procedure: not_found_errors

Package: test (black-box, alongside the existing article_test.go / user_test.go / use_case_baseline_test.go).

Setup shared by every case

  1. Call validator.InitValidator() once.
  2. Build in-memory fakes for article.Repository and user.Repository (extend the existing mockArticleRepo / mockUserRepo patterns in test/), updated so that a lookup by an unknown id/name/username/email now returns article.ErrArticleNotFound / user.ErrUserNotFound instead of gorm.ErrRecordNotFound — mirroring the fixed real repositories.
  3. Wire the real use cases (useCases.NewGetArticleUseCase, useCases.NewCreateArticleUseCase if needed, useCases.NewGetUserUseCase, useCases.NewListUsersUseCase, useCases.NewCreateUserUseCase) over those fakes.
  4. Build article.NewHandler(...) and auth.NewHandler(...) from those use cases plus a test jwtauth.New("HS256", []byte("test-secret"), nil).
  5. Mount a fresh chi.NewRouter() mirroring api/router.go: the article handler's routes inside a group wrapped by jwtauth.Verifier(token) + jwtauth.Authenticator(token), the auth handler's routes unprotected. No live server.

Repository-level checks (real persistence repositories, in-memory SQLite, no HTTP)

  1. Open a gorm.DB with sqlite.Open("file:" + t.Name() + "?mode=memory&cache=shared") (one named in-memory DB per test, so tests never share rows) (the project's github.com/glebarez/sqlite driver, never the real DB_FILE), and db.AutoMigrate(&models.Article{}). Construct a real persistence.NewArticleRepository(db), seed it with an article id 1 via Create. Call Get(9) directly; assert errors.Is(err, article.ErrArticleNotFound) is true and errors.Is(err, gorm.ErrRecordNotFound) is false. Repeat for GetByName("Unknown Title"). Close the in-memory DB (or let it drop) at the end of the test.
  2. Open a second gorm.DB the same way with db.AutoMigrate(&models.User{}). Construct a real persistence.NewUserRepository(db), seed it with a user id 1 / username "alice" / email "alice@example.com" via Create. Call Get(9), GetByUsername("bob"), GetByEmail("bob@example.com") directly; assert each returns user.ErrUserNotFound and not gorm.ErrRecordNotFound.

HTTP checks (fakes, per the shared setup above)

  1. Mint a valid jwt cookie for user id 1 with the test jwtauth.JWTAuth (encode with token.Encode(map[string]any{"user_id": uint32(1)}) and set it as the jwt cookie on the httptest.NewRequest).
  2. GET /v1/article/9999 with that cookie, empty repo id 9999 → assert status 404, body "Article not found\n" (http.Error appends a trailing newline).
  3. GET /v1/article/2 with that cookie, repo seeded with an offline article id 2 → assert status 404, body "Article not found\n" (regression: unchanged from before this fix).
  4. GET /v1/article/1 with that cookie, repo seeded with an online article id 1 → assert status 200, body decodes to the article JSON with id 1.
  5. GET /v1/article/9999 with no cookie → assert status 401 (regression: jwtauth still gates the route).
  6. POST /v1/auth/login with {"username":"alice","password":"wrongpassword12"} (repo seeded with user "alice"/bcrypt-hashed "hunter2example") → assert status 403, body equals apperr.LogsErrorMessage + "\n", no Set-Cookie: jwt=... header present.
  7. POST /v1/auth/login with {"username":"bob","password":"hunter2example"} (no such user in the repo) → assert status 403, identical body to step 13, no Set-Cookie header — proves the domain-error change didn't turn this into a distinguishable 404/500 and didn't start leaking existence.

Manual verification (not exercised by the Go test)

  • None. This slice has no SPA-visible behavior and no SMTP involvement; the SPA's article-detail view already treats any non-2xx as "not found" generically (verify only if the SPA has an article-detail page — otherwise skip).