98 lines
6.0 KiB
Gherkin
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
|