Files
RenewCMS/features/write_not_found_errors.feature

98 lines
6.0 KiB
Gherkin

Feature: Domain not-found errors on repository writes, image attachment and deletes
As the maintainer of RenewCMS
I want an update, an image attachment or a delete against an unknown article, user or image id
to report a domain not-found error instead of a raw GORM error or a silent no-op
So that a caller can rely on errors.Is(err, article.ErrArticleNotFound), errors.Is(err, user.ErrUserNotFound)
or errors.Is(err, image.ErrImageNotFound) on every repository method, not just the read side,
and so that DELETE /v1/article/{id} reports 404 instead of leaking a 500 for an id that no longer exists
Background:
Given internal/domain/article defines the sentinel article.ErrArticleNotFound
And internal/domain/user defines the sentinel user.ErrUserNotFound
And internal/domain/image defines the sentinel image.ErrImageNotFound
And ArticleRepository.UpdateBody, UpdateIsOnline, Delete and AddImage return article.ErrArticleNotFound,
not gorm.ErrRecordNotFound and not a silent no-op, when the article id does not match any row
And ArticleRepository.AddImage returns image.ErrImageNotFound, not gorm.ErrRecordNotFound,
when the article exists but the image id does not match any row
And UserRepository.UpdateVerificationStatus, UpdatePassword, UpdatePasswordResetCode and Delete return
user.ErrUserNotFound, not gorm.ErrRecordNotFound and not a silent no-op, when the user id does not match any row
And ImageRepository.Delete returns image.ErrImageNotFound, not gorm.ErrRecordNotFound, when the image id
does not match any row
And DELETE /v1/article/{id} sits behind the jwtauth Verifier and Authenticator group, and requires a valid jwt cookie
And UpdateArticle, AddImage, UpdateUser, DeleteUser, CreateImage and DeleteImage have no handler mounted in
api/router.go today, so this slice has no other HTTP-observable surface beyond DELETE /v1/article/{id}
Scenario Outline: The article repository reports a missing article as a domain error on a write
Given a repository backed by an in-memory SQLite database containing an article titled "Backyard Renovation Update" with id 1
When <method> is called with id 9 <extra>
Then the result is the error article.ErrArticleNotFound
And errors.Is(err, gorm.ErrRecordNotFound) is false
Examples:
| method | extra |
| UpdateBody | and body "New body" |
| UpdateIsOnline | and is_online true |
| Delete | |
Scenario: AddImage reports a missing article as a domain error, not a missing image
Given a repository backed by an in-memory SQLite database containing an article with id 1
And the same database containing an image with id 1
When AddImage is called with article id 9 and image id 1
Then the result is the error article.ErrArticleNotFound
And errors.Is(err, gorm.ErrRecordNotFound) is false
Scenario: AddImage reports a missing image as a domain error once the article exists
Given a repository backed by an in-memory SQLite database containing an article with id 1
And the same database containing no image with id 9
When AddImage is called with article id 1 and image id 9
Then the result is the error image.ErrImageNotFound
And errors.Is(err, gorm.ErrRecordNotFound) is false
Scenario Outline: The user repository reports a missing user as a domain error on a write
Given a repository backed by an in-memory SQLite database containing a user with id 1, username "alice" and email "alice@example.com"
When <method> is called with id 9 <extra>
Then the result is the error user.ErrUserNotFound
And errors.Is(err, gorm.ErrRecordNotFound) is false
Examples:
| method | extra |
| UpdateVerificationStatus | and is_verified true |
| UpdatePassword | and password "newHashedPassword" |
| UpdatePasswordResetCode | and code "newHashedResetCode" |
| Delete | |
Scenario: The image repository reports a missing image as a domain error on delete
Given a repository backed by an in-memory SQLite database containing an image with id 1
When Delete is called with id 9
Then the result is the error image.ErrImageNotFound
And errors.Is(err, gorm.ErrRecordNotFound) is false
Scenario: The test fakes mirror the real repositories on every write not-found path
Given the in-memory article, user and image fakes used in test/
When UpdateBody, UpdateIsOnline, Delete or AddImage is called against an id absent from the fake's map
Then the fake returns article.ErrArticleNotFound or image.ErrImageNotFound, matching the real repository
And when UpdateVerificationStatus, UpdatePassword, UpdatePasswordResetCode or Delete is called against an id
absent from the fake's map, the fake returns user.ErrUserNotFound, matching the real repository
And none of the fakes construct or return gorm.ErrRecordNotFound anymore
Scenario: Deleting an unknown article answers 404, not a silent success or a 500
Given a valid jwt cookie for a user with id 1
And the article repository has no article with id 9999
When the client sends DELETE /v1/article/9999 with that cookie
Then the response status is 404
And the response body is the text "Article not found"
Scenario: Deleting an existing article still answers 204
Given a valid jwt cookie for a user with id 1
And the article repository contains an article with id 1
When the client sends DELETE /v1/article/1 with that cookie
Then the response status is 204
And the article repository no longer contains an article with id 1
Scenario: The delete-article route still requires authentication before existence is even checked
Given no jwt cookie is sent
And the article repository has no article with id 9999
When the client sends DELETE /v1/article/9999 without a cookie
Then the response status is 401