122 lines
8.2 KiB
Gherkin
122 lines
8.2 KiB
Gherkin
Feature: Public URL pattern of a site
|
|
An owner can give each site one pattern for the address of its articles on
|
|
the website, with {lang} and {slug}. It is validated on the server. When it
|
|
is unset the product shows "Public URL not configured" and invents no URL.
|
|
|
|
Background:
|
|
Given error responses are plain text whose body is the snake_case code
|
|
And an "owner" is a verified user holding settings.manage_sites and a "reader" is a verified user without it
|
|
And the pattern is stored on the site as public_url_pattern, a string or null, at most 512 characters
|
|
And it is read through the existing GET /v1/site/ list (every verified user) and written through the existing PATCH /v1/site/{id} (owner)
|
|
And no new route is added
|
|
And the design in docs/design/localization.md (L1b) is the visual spec of record
|
|
|
|
Scenario: Reading the pattern
|
|
Given site 1 has public_url_pattern "https://journal.example.com/{lang}/{slug}"
|
|
When a reader sends GET /v1/site/
|
|
Then the item for site 1 contains "public_url_pattern" with that value
|
|
And every key present before this change is present with an unchanged value and type
|
|
|
|
Scenario Outline: Writing the pattern
|
|
When an owner sends PATCH /v1/site/1 with {"public_url_pattern": <value>}
|
|
Then the status is <status> and <effect>
|
|
|
|
Examples:
|
|
| value | status | effect |
|
|
| "https://journal.example.com/{lang}/{slug}" | 200 | the site returns that value |
|
|
| "https://journal.example.com/{lang}/articles/{slug}" | 200 | the site returns that value |
|
|
| "http://localhost:3000/{slug}" | 200 | the site returns that value |
|
|
| "https://journal.example.com/{slug}/{slug}" | 200 | the site returns that value (repeats allowed) |
|
|
| null | 200 | the pattern is unset |
|
|
| "" | 200 | the pattern is unset |
|
|
| " https://journal.example.com/{slug} " | 200 | the stored value is trimmed |
|
|
|
|
Scenario Outline: Patterns that are refused
|
|
When an owner sends PATCH /v1/site/1 with {"public_url_pattern": "<value>"}
|
|
Then the status is 422 and the body is "<code>"
|
|
And the stored pattern is unchanged
|
|
|
|
Examples:
|
|
| value | code |
|
|
| ftp://journal.example.com/{lang}/{slug} | invalid_scheme |
|
|
| journal.example.com/{slug} | invalid_scheme |
|
|
| https:///{slug} | invalid_host |
|
|
| https://journal.example.com/{language}/{slug} | unknown_placeholder |
|
|
| https://journal.example.com/{lang | unknown_placeholder |
|
|
| https://journal.example.com/my pages/{lang}/{slug} | not_encoded |
|
|
| https://journal.example.com/é/{slug} | not_encoded |
|
|
| https://journal.example.com/{lang} | missing_slug |
|
|
| a pattern of 513 characters | invalid_pattern |
|
|
|
|
Scenario: A pattern is checked in a fixed order
|
|
When the pattern is "ftp://x.example.com/{language} here"
|
|
Then the code is "invalid_scheme"
|
|
And with scheme fixed the code is "unknown_placeholder", then "not_encoded", then "missing_slug"
|
|
|
|
Scenario Outline: Only an owner can write
|
|
When <caller> sends PATCH /v1/site/1 with a valid pattern
|
|
Then the status is <status> and the stored pattern is unchanged
|
|
|
|
Examples:
|
|
| caller | status |
|
|
| an anonymous client | 401 |
|
|
| a reader | 403 |
|
|
|
|
Scenario: Saving the pattern changes nothing else
|
|
When an owner sets the pattern
|
|
Then no article, canonical URL or public response changes
|
|
And the site's other fields are unchanged
|
|
|
|
Scenario: Existing site behaviour is unchanged
|
|
Then the existing site tests pass without edits and a PATCH without public_url_pattern leaves it unchanged
|
|
|
|
Scenario: Route and access
|
|
Given the route /settings/sites/:key/public-url
|
|
Then it is open to every verified signed-in user
|
|
And owners can edit, other users see the Read-only view
|
|
And owners reach it from the "Public URL" action on the Settings, Sites row, non-owners by URL only
|
|
|
|
Scenario Outline: Public URL states
|
|
When <trigger>
|
|
Then the screen is "<state>" (<id>) and shows <visible>
|
|
|
|
Examples:
|
|
| trigger | state | id | visible |
|
|
| an owner opens Journal with the pattern saved | Set | loc.l1b.set | heading "Public URL", subtitle "Where Journal articles appear on your website. Used for canonical and alternate links.", the monospace field "Public URL pattern" holding https://journal.example.com/{lang}/{slug}, the help "Placeholders: {lang} is the language code, such as fr or pt-BR. {slug} is the article address in that language.", the preview line and "Save" and "Cancel" |
|
|
| the owner types https://journal.example.com/{lang}/articles/{slug} | Editing | loc.l1b.editing | the preview follows the field on each change |
|
|
| the owner types a pattern with {language} | Unknown placeholder | loc.l1b.unknown | the error "Unknown placeholder {language}. Use {lang} and {slug}." and Save disabled |
|
|
| the owner types ftp://journal.example.com/{lang}/{slug} | Bad scheme | loc.l1b.scheme | the error "Start with https:// or http://." and Save disabled |
|
|
| the owner types a pattern with a space in the path | Not encoded | loc.l1b.encoded | the error "Remove spaces and characters that need encoding. Use letters, digits, - and _." and Save disabled |
|
|
| the owner types https://journal.example.com/{lang} | Missing {slug} | loc.l1b.noSlug | the error "Add {slug} so each article has its own address." and Save disabled |
|
|
| the owner saves and the PATCH returns 200 | Saved | loc.l1b.saved | a success toast; the field holds the saved value |
|
|
| an owner opens a site with no pattern from "Public URL not configured" | Unset | loc.l1b.unset | the info alert "Public URL not configured" / "Page, canonical and alternate addresses stay hidden until you add a pattern. Links by article ID still work.", the placeholder https://example.com/{lang}/{slug}, no preview and Save disabled |
|
|
| a reader opens Journal | Read-only view | loc.l1b.viewOnly | the alert "Only owners can change this" / "You can read the pattern. Ask an owner to change it.", the field read-only and no Save or Cancel |
|
|
|
|
Scenario: Client and server agree
|
|
Given the page validates as the owner types
|
|
When the server still returns 422 with unknown_placeholder, invalid_scheme, not_encoded or missing_slug
|
|
Then the field shows the same error as the matching state above
|
|
|
|
Scenario: Preview
|
|
Given the preview uses a real article of the site, shown with the site's default language
|
|
Then {lang} is replaced by the tag and {slug} by the article slug
|
|
|
|
Scenario: Preview of a site without articles
|
|
Given the site has no articles
|
|
Then the preview line is not shown
|
|
And this state has no frame, so the assumption is recorded in the QA notes
|
|
|
|
Scenario: Cancel
|
|
When the owner edits the field and presses "Cancel"
|
|
Then the field returns to the saved value and nothing is sent
|
|
And the design does not describe Cancel, so the assumption is recorded in the QA notes
|
|
|
|
Scenario: Clearing the field
|
|
When the owner empties the field and saves
|
|
Then a PATCH with null is sent and the site shows the Unset state
|
|
|
|
Scenario: Failures with no frame
|
|
When the load or the save fails with a 5xx
|
|
Then the page reuses the Languages "Couldn't load languages" / "Couldn't save languages" alert copy
|
|
And these states are skipped by the visual gate
|