Feature: An authenticated user can publish and unpublish an article Superseded in part by roles_permissions_content_scope: the article routes are gated by the content.* permissions of the caller's role (content.create, content.update, content.delete and content.publish; a refusal is 403 "missing_permission" with the header X-Missing-Permission), and content.read decides which drafts show: every draft at all, only the caller's own drafts at own, none otherwise. A draft the caller may not read answers 404 "Article not found", and a role that cannot be resolved is treated as holding no permission. Extended by article_story_lifecycle: publishing a version in a disabled language answers 422 language_disabled. As an authenticated RenewCMS user managing the CMS I want to flip an article's is_online flag from an HTTP endpoint So that a draft can be published to the public site and a published article can be pulled back offline, without deleting it Background: Given internal/domain/article.Repository already exposes UpdateIsOnline(id uint32, isOnline bool) (Article, error), and UpdateArticleUseCase.UpdateIsOnline delegates to it unchanged And UpdateArticleUseCase is already built in api/dependecyInjection.go as container.UpdateArticleUseCase, but before this slice no handler or route calls it And this slice adds Handler.PublishArticle and Handler.UnpublishArticle, backed by UpdateArticleUseCase.UpdateIsOnline(id, true) and UpdateIsOnline(id, false) And this slice mounts PATCH /v1/article/{id}/publish and PATCH /v1/article/{id}/unpublish inside the same jwtauth Verifier + Authenticator group as GET /v1/article/{id}, POST /v1/article/ and DELETE /v1/article/{id} in api/router.go, so both require a valid jwt cookie And a successful publish or unpublish answers 200 with the JSON body of the updated article.Article, the same shape GetArticle and PostArticle already return And an id that does not parse as a uint32 answers 400 with body apperr.IdUint32ErrorMessage, the same as GetArticle and DeleteArticle And an id absent from the article repository answers 404 with body apperr.ArticleNotFoundErrorMessage, backed by errors.Is(err, article.ErrArticleNotFound), the same as DeleteArticle already does per features/write_not_found_errors.feature And publishing and unpublishing act on one article id, one language version; sibling versions are untouched And GET /v1/article/ with no jwt cookie only returns online articles, per features/public_article_listing.feature; this slice does not change that behavior, only feeds it Scenario: Publishing an offline article makes it online Given a valid jwt cookie for a user with id 1 And the article repository contains an offline article titled "Draft: Kitchen Remodel" with id 2 When the client sends PATCH /v1/article/2/publish with that cookie Then the response status is 200 And the response body is the article with id 2 and "is_online": true Scenario: Unpublishing an online article makes it offline Given a valid jwt cookie for a user with id 1 And the article repository contains an online article titled "Backyard Renovation Update" with id 1 When the client sends PATCH /v1/article/1/unpublish with that cookie Then the response status is 200 And the response body is the article with id 1 and "is_online": false Scenario: Publishing an already-online article is idempotent Given a valid jwt cookie for a user with id 1 And the article repository contains an online article with id 1 When the client sends PATCH /v1/article/1/publish with that cookie Then the response status is 200 And the response body is the article with id 1 and "is_online": true And no error is returned Scenario: Unpublishing an already-offline article is idempotent Given a valid jwt cookie for a user with id 1 And the article repository contains an offline article with id 2 When the client sends PATCH /v1/article/2/unpublish with that cookie Then the response status is 200 And the response body is the article with id 2 and "is_online": false And no error is returned Scenario Outline: Publishing or unpublishing an unknown article answers 404 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 PATCH /v1/article/9999/ with that cookie Then the response status is 404 And the response body is the text "Article not found" Examples: | action | | publish | | unpublish | Scenario Outline: A malformed id answers 400 before any repository call Given a valid jwt cookie for a user with id 1 When the client sends PATCH /v1/article/not-a-number/ with that cookie Then the response status is 400 And the response body is the text apperr.IdUint32ErrorMessage Examples: | action | | publish | | unpublish | Scenario Outline: Publish and unpublish require authentication Given no jwt cookie is sent And the article repository contains an article with id 1 When the client sends PATCH /v1/article/1/ without a cookie Then the response status is 401 Examples: | action | | publish | | unpublish | Scenario: Publishing a draft in a disabled language is refused Given a valid jwt cookie for a user holding content.publish And article 15 is offline in language "es", which is disabled on its site When the client sends PATCH /v1/article/15/publish with that cookie Then the response status is 422 And the response body is the text "language_disabled" And article 15 stays offline Scenario: An online version in a disabled language can still be unpublished, and publish is idempotent Given a valid jwt cookie for a user holding content.publish And article 16 is online in language "es", which is disabled on its site When the client sends PATCH /v1/article/16/unpublish with that cookie Then the response status is 200 And the response body is the article with id 16 and "is_online": false When article 16 is online again and the client sends PATCH /v1/article/16/publish with that cookie Then the response status is 200 And the response body is the article with id 16 and "is_online": true Scenario: Publishing one language leaves the other versions alone Given a valid jwt cookie for a user holding content.publish And story 5 holds article 10 (en, online) and article 12 (fr, offline) When the client sends PATCH /v1/article/12/publish with that cookie Then the response status is 200 And article 12 is online And the publication and updated_at of article 10 are unchanged Scenario: Publishing an article makes it appear in the public online-only list Given a valid jwt cookie for a user with id 1 And the article repository contains an offline article titled "Draft: Kitchen Remodel" with id 2 When the client sends PATCH /v1/article/2/publish with that cookie And the client then sends GET /v1/article/ with no jwt cookie Then the response status of the second request is 200 And the response body of the second request is a JSON array containing the article with id 2 Scenario: Unpublishing an article removes it from the public online-only list Given a valid jwt cookie for a user with id 1 And the article repository contains an online article titled "Backyard Renovation Update" with id 1 When the client sends PATCH /v1/article/1/unpublish with that cookie And the client then sends GET /v1/article/ with no jwt cookie Then the response status of the second request is 200 And the article with id 1 is absent from the response body of the second request