feat: site languages, setup and site URL settings

This commit is contained in:
Florian Sylvain committed 2026-11-01 22:13:02 +01:00
1 parent 0806733d5c
commit d9158025d4
106 files changed
+10184 -56

No files matched your search

+177
View File
@@ -0,0 +1,177 @@
Feature: First-time language setup for existing articles
Existing articles have no language. One owner confirms a default language
for every site that has none, and all assignments apply in one transaction.
It labels articles. It never translates, links or moves them.
Background:
Given the language rules of features/article_localization_storage.feature apply
And error responses are plain text whose body is the snake_case code
And the setup page is global: its route is /settings/sites/:key/languages/setup, where :key is only the site it was opened from
And a site is "pending" when it has no confirmed default language
And these sites exist
| key | name | Locale | articles | state |
| journal | Journal | en | 8 | unconfirmed default row "en" |
| notes | Notes | null | 3 | no language row |
| archive | Archive | pt-BR | 5 | unconfirmed default row "pt-BR" |
| gallery | Gallery | en-GB | 0 | unconfirmed default row "en-GB" |
Scenario: Read what is pending
When an owner sends GET /v1/language/setup
Then the status is 200
And "sites" lists every pending site with key, name, locale, article_count and a suggestion
And the suggestion is { "lang": "pt-BR", "substituted": false } for archive and null for notes
And "available" lists the selectable tags like GET /v1/site/{key}/languages
And a site whose default is confirmed is not listed
Scenario Outline: Read access
When <caller> sends GET /v1/language/setup
Then the status is <status>
Examples:
| caller | status |
| an anonymous client | 401 |
| a reader | 403 |
| an owner | 200 |
Scenario: Apply for every pending site
When an owner sends POST /v1/language/setup with
| key | lang |
| journal | en |
| notes | en |
| archive | pt-BR |
| gallery | en-GB |
Then the status is 200
And every listed site has a confirmed default language row with the chosen tag, enabled
And all 16 articles carry the chosen tag of their site
And the body says 16 articles were assigned, per site
And every article keeps its id, title, body, slug, is_online, story_id and timestamps
And no story is merged, split or moved
And GET /v1/site/journal/languages has "needs_setup" false
Scenario: The owner may change a suggestion
When an owner confirms "archive" with "pt" instead of "pt-BR"
Then archive has one language row "pt", confirmed and default
And no row "pt-BR" remains
And its 5 articles carry "pt"
Scenario: A site with no articles still needs confirming
When an owner posts every pending site except "gallery"
Then the status is 422 and the body is "setup_incomplete"
And nothing is applied
Scenario Outline: Rows that are refused
When an owner posts <body>
Then the status is <status> and the body is "<code>"
And no site and no article has changed
Examples:
| body | status | code |
| notes with lang "english" | 422 | invalid_language |
| notes with lang "xx" | 422 | unsupported_language |
| notes with an empty lang | 422 | invalid_language |
| the same site key twice | 422 | duplicate_site |
| a site key "nowhere" | 404 | site_not_found |
| no "sites" key, or malformed JSON | 400 | Invalid request body |
Scenario: Retrying is safe
Given setup succeeded
When the same POST is sent again
Then the status is 200 and zero articles are assigned
And no row, article or story changes
And a request that asks for a different tag on an already confirmed site changes nothing for that site
And the response lists that site key in "skipped" instead of dropping the choice silently
Scenario: Failure leaves nothing half done
Given the write fails while handling the second site
When an owner posts every pending site
Then the status is 500
And no site has a confirmed default and no article is assigned
And a later POST completes every site
Scenario: Another setup is running
Given another owner's POST holds the setup
When an owner posts
Then the status is 409 and the body is "setup_in_progress"
And nothing is applied
When the first setup has finished and the owner posts again
Then the status is 200 and the finished sites are skipped
Scenario: A lock error anywhere in the setup is a conflict
Given a site row, a read, a write or the commit of the setup fails on a database lock
Then the status is 409 and the body is "setup_in_progress" and nothing is applied
Scenario: A site that became pending after the first read is locked too
Given a site is created after the setup read the pending sites and before it locked them
Then the setup locks it as well before deciding, so it must be listed or the status is 422 "setup_incomplete"
And no pending site is ever confirmed without its lock
Scenario: Two simultaneous setups on SQLite
Given a database on SQLite
When two owners post the same body at the same time
Then each answers 200 or 409 and never 500, at least one answers 200
And every article is assigned exactly once
Scenario: Setup is recorded in the activity log
When an owner completes the setup for several sites
Then one "site.language_setup" event of the sites category is recorded per confirmed site after the commit, with the default_language change and the assigned count
And a refused setup records one failed event with a plain reason, and skipped sites record nothing
Scenario: Create racing setup never strands an article
Given journal has unassigned articles
When setup commits while articles are being created concurrently in journal
Then every article of journal ends with the confirmed default tag
And none is left unassigned and none has another tag
Scenario: Locks are taken in a fixed order
Given two owners post the same pending sites in different row orders
Then neither request deadlocks and both finish with 200 or one with 409 "setup_in_progress"
Scenario: Persistence portability of the setup transaction
Then one targeted task test:mysql and task test:postgres run covers the setup transaction and its lock order
Scenario: Sites created after the migration stay pending
Given a site is created after slice 1 and has no language row
Then it is listed as pending by GET /v1/language/setup
And seeding a suggested row on site create is out of scope and tracked as a follow-up
Scenario: Route and access
Given the route /settings/sites/:key/languages/setup
Then it is open to owners only (settings.manage_sites)
And the breadcrumb and back link go to the Languages page of :key
And it is reached from the "Set up languages" banner of a site with needs_setup true
Scenario Outline: Setup states
When <trigger>
Then the screen is "<state>" (<id>) and shows <visible>
Examples:
| trigger | state | id | visible |
| the owner opens setup with Journal, Notes and Archive pending | Ready to assign | loc.l2.ready | heading "Set article languages", subtitle "Existing articles don't have a language yet. Confirm or change the language for each site.", the info alert "This labels articles. It doesn't translate them." / "Each existing article becomes the only version of its own article group. Each site's language starts from its locale with the region kept: pt-BR stays Portuguese (Brazil). Nothing is applied until you confirm."; rows "Journal 8 articles · Site locale en" with "English · en", "Notes 3 articles · No site locale" with "Choose a language" and the error "Required before you can apply.", "Archive 5 articles · Site locale pt-BR" with "Portuguese (Brazil) · pt-BR"; helper "Seeded from site locale en. Confirm or change.". The button "Confirm and assign to 16 articles" is disabled until every row has a language; the note "Counts are articles in 3 sites." |
| only Notes is pending | No-locale site | loc.l2.noLocale | the error alert "Choose a language for Notes" / "Notes has no site locale, so there is nothing to start from. Choose the language its articles are written in." and the confirm button disabled |
| only Gallery is pending (0 articles, en-GB) | Site with no articles | loc.l2.empty | the page "Languages" / "Content languages for Gallery." with the card "Confirm the default language", "Gallery has no articles yet. New articles will be written in this language.", the field "Default content language" "English (United Kingdom) · en-GB" with "Seeded from site locale en-GB. The full locale is kept. Confirm or change it.", and a "Save" button |
| the owner confirms and the POST is in flight | Applying | loc.l2.applying | the info alert "Assigning languages" / "Articles created while this runs get the site's default language." and the busy button "Assigning…" disabled |
| the POST returns 409 setup_in_progress | Setup already running | loc.l2.inProgress | the info alert "Setup is already running" / "Another owner started it. Languages appear here when it finishes. Articles created meanwhile get each site's default language. Starting it again is safe: sites that already have a language are skipped." with a "Check again" button that repeats GET /v1/language/setup; the rows are read-only and the confirm button disabled |
| the POST returns 200 with 16 articles assigned | Done | loc.l2.done | the success alert "Languages assigned" / "16 articles now have a language. Nothing was translated, and no articles were grouped." with an "Open Articles" button; the confirm button is gone |
| the POST fails with a server error | Failed | loc.l2.fail | the error alert "Nothing was changed" / "The update failed, so no article has a language yet. Your choices are kept. Try again. Running it twice is safe." with a "Try again" button; the rows keep their values |
Scenario: The page sends one request for all rows
When the owner confirms with Journal "en", Notes "en", Archive "pt-BR"
Then exactly one POST /v1/language/setup is sent with all three rows
And the Languages page of every listed site no longer shows the setup banner
Scenario: Nothing is pending
Given the owner opens the setup page and no site is pending, including after "Check again" resolves
Then the Done success alert "Languages assigned" is shown without an article count
And this state has no frame, so the assumption is recorded in the QA notes
Scenario: Unknown or already complete :key
Given the page is opened with a :key that is unknown or whose default is already confirmed
Then the page still lists the globally pending sites
And the back link and breadcrumb go to the Languages page of :key, or to Settings, Sites when the :key is unknown
And this case has no frame, so the assumption is recorded in the QA notes
Scenario: Substituted seed
Given a site's suggestion has "substituted" true (Locale "fr-BE" gives "fr")
Then the row shows the seeded tag "fr"
And its help text reuses the Languages "broader" wording, the closest drawn copy
And this state has no frame, so the assumption is recorded in the QA notes