Files
RenewCMS/features/public_article_read.feature

133 lines
7.9 KiB
Gherkin

Feature: Single article read follows the same online/offline visibility rule as the list
As a visitor of the public site and as an authenticated RenewCMS user
I want GET /v1/article/{id} to return an online article to anyone, and an offline article
only to a caller resolved as verified, with an unknown id and a hidden offline id
answering the exact same 404
So that the public site can read a single published article through the API, and a verified
user can open their own unpublished draft to edit it, while a draft never leaks its existence
to anyone else
Background:
Given internal/domain/article.Article has a json "is_online" field and the sentinel
article.ErrArticleNotFound
And GetArticleUseCase.GetArticle(id) returns the article regardless of is_online, and
GetArticleUseCase.GetOnlineArticle(id) returns article.ErrArticleNotFound when the article
exists but is offline, both already implemented and unchanged by this slice
And before this slice, GET /v1/article/{id} sits inside the jwtauth Verifier + Authenticator +
middleware.RequireVerifiedUser group in api/router.go, so a missing jwt cookie answers 401, an
authenticated-but-unverified caller answers 403, and even a verified caller always resolves
through GetOnlineArticle, so an offline article answers 404 no matter who asks
(features/not_found_errors.feature, features/verified_account_required.feature)
And this slice moves GET /v1/article/{id} out of that group and into the jwtauth.Verifier-only
group that already holds GET /v1/article/ (features/public_article_listing.feature), so a
missing or invalid jwt cookie no longer blocks the request; POST /v1/article/,
DELETE /v1/article/{id}, PATCH /v1/article/{id}, PATCH /v1/article/{id}/publish,
and PATCH /v1/article/{id}/unpublish keep requiring
Verifier + Authenticator + RequireVerifiedUser, unchanged
And Handler.GetArticle reuses the existing Handler.callerIsVerified(r) helper already used by
Handler.ListArticles: when the request resolves to a verified account it calls
GetUseCase.GetArticle(id); otherwise (no jwt, malformed jwt, unresolvable user_id, a deleted
account, or an account that exists but is not yet verified) it calls
GetUseCase.GetOnlineArticle(id), exactly the same fallback rule
features/verified_account_required.feature already documents for the listing endpoint
And an id that does not parse as a uint32 still answers 400 with apperr.IdUint32ErrorMessage
before any lookup happens, for every caller, unchanged
And a domain article.ErrArticleNotFound, whether the id is truly unknown or is an offline
article hidden from this caller, still answers 404 with apperr.ArticleNotFoundErrorMessage
("Article not found"), the identical body either way, so no response distinguishes the two
And a repository failure other than article.ErrArticleNotFound still answers 500 with the text
"Internal server error", regardless of caller state, unchanged
And this slice amends features/verified_account_required.feature: the Examples table of
"An authenticated but unverified user is refused on every protected article route" and of "A
request with no jwt cookie is still refused with 401, never 403" both drop their
"GET /v1/article/1" row, since that route no longer applies RequireVerifiedUser or requires a
cookie at all
And this slice amends features/not_found_errors.feature: "Fetching an offline article still
answers 404 with the same message" now holds only for an anonymous or unverified caller, not
a verified one, and "The article route still requires authentication before existence is even
checked" is replaced by this feature's public-visibility scenarios below
Scenario: An anonymous visitor can read an online article
Given the article repository contains an online article titled "Backyard Renovation Update" with id 1
When the client sends GET /v1/article/1 with no jwt cookie
Then the response status is 200
And the response body is the article's JSON representation
Scenario: An anonymous visitor is told an offline article does not exist
Given the article repository contains an offline article titled "Draft: Kitchen Remodel" with id 2
When the client sends GET /v1/article/2 with no jwt cookie
Then the response status is 404
And the response body is the text "Article not found"
Scenario: An anonymous visitor is told an unknown id does not exist
Given the article repository has no article with id 9999
When the client sends GET /v1/article/9999 with no jwt cookie
Then the response status is 404
And the response body is the text "Article not found"
Scenario: A verified user can open their own offline draft
Given a user repository containing user id 1, is_verified true
And a valid jwt cookie for user id 1
And the article repository contains an offline article titled "Draft: Kitchen Remodel" with id 2
When the client sends GET /v1/article/2 with that cookie
Then the response status is 200
And the response body is the article's JSON representation
Scenario: A verified user still reads an online article normally
Given a user repository containing user id 1, is_verified true
And a valid jwt cookie for user id 1
And the article repository contains an online article titled "Backyard Renovation Update" with id 1
When the client sends GET /v1/article/1 with that cookie
Then the response status is 200
And the response body is the article's JSON representation
Scenario: A verified user requesting an unknown id gets the same not-found response
Given a user repository containing user id 1, is_verified true
And a valid jwt cookie for user id 1
And the article repository has no article with id 9999
When the client sends GET /v1/article/9999 with that cookie
Then the response status is 404
And the response body is the text "Article not found"
Scenario: An authenticated but unverified user is told an offline article does not exist, not refused with 403
Given a user repository containing user id 1, is_verified false
And a valid jwt cookie for user id 1
And the article repository contains an offline article titled "Draft: Kitchen Remodel" with id 2
When the client sends GET /v1/article/2 with that cookie
Then the response status is 404
And the response body is the text "Article not found"
Scenario: A malformed or expired jwt cookie falls back to the public online-only visibility
Given a jwt cookie whose value is not a token issued by JWT_SECRET
And the article repository contains an online article with id 1 and an offline article with id 2
When the client sends GET /v1/article/1 with that cookie
Then the response status is 200
When the client sends GET /v1/article/2 with that cookie
Then the response status is 404
And the response body is the text "Article not found"
Scenario: An id that isn't a valid uint32 still answers 400, regardless of caller
When the client sends GET /v1/article/not-a-number with no jwt cookie
Then the response status is 400
And the response body is the text apperr.IdUint32ErrorMessage
Scenario Outline: The rest of the article routes still require a verified account
Given no jwt cookie is sent
When the client sends <method> <path>
Then the response status is 401
Examples:
| method | path |
| POST | /v1/article/ |
| DELETE | /v1/article/1 |
| PATCH | /v1/article/1 |
| PATCH | /v1/article/1/publish |
| PATCH | /v1/article/1/unpublish |
Scenario: A repository failure still answers 500 regardless of caller state
Given the article repository fails on its next call to Get
When the client sends GET /v1/article/1 with no jwt cookie
Then the response status is 500
And the response body is the text "Internal server error"