153 lines
6.9 KiB
Gherkin
153 lines
6.9 KiB
Gherkin
Feature: Article language, story and per-site language storage
|
|
Articles gain a full BCP 47 language tag, a story they belong to and a
|
|
content revision. Each site gets a language table with one default. Existing
|
|
articles keep their ids, bodies, publication and public API. They are
|
|
migrated without a guessed language: each gets its own story and an
|
|
unassigned language until the owner confirms the site default.
|
|
|
|
Background:
|
|
Given the supported languages are "en", "en-GB", "en-US", "fr", "fr-CA", "pt", "pt-BR", "es", "de", "ar"
|
|
And a language tag keeps its region, "pt-BR" is never reduced to "pt"
|
|
And the domain package internal/domain/language imports only the stdlib
|
|
And persistence is portable across SQLite, MySQL and PostgreSQL
|
|
|
|
Scenario Outline: Language tag normalization
|
|
When the tag "<input>" is normalized
|
|
Then the result is "<result>"
|
|
|
|
Examples:
|
|
| input | result |
|
|
| en | en |
|
|
| EN | en |
|
|
| pt-br | pt-BR |
|
|
| PT_BR | ErrInvalidLanguage |
|
|
| fr-ca | fr-CA |
|
|
| fr | fr |
|
|
| e | ErrInvalidLanguage |
|
|
| english | ErrInvalidLanguage |
|
|
| en-ZZZZ | ErrInvalidLanguage |
|
|
| | ErrInvalidLanguage |
|
|
| xx | ErrUnsupportedLanguage |
|
|
|
|
Scenario Outline: Suggestion for a site's default language from Site.Locale
|
|
Given a site whose Locale is "<locale>"
|
|
When the default language suggestion is computed
|
|
Then the suggestion is "<suggested>" and the substitution flag is <substituted>
|
|
|
|
Examples:
|
|
| locale | suggested | substituted |
|
|
| pt-BR | pt-BR | false |
|
|
| en-GB | en-GB | false |
|
|
| fr-BE | fr | true |
|
|
| fr | fr | false |
|
|
| xx-YY | none | false |
|
|
| null | none | false |
|
|
|
|
Scenario: Migration of an existing site and its articles
|
|
Given a database created before this change with site "default" (Locale "fr-CA") and articles 1, 2 and 3 in it, article 3 online
|
|
When database.Migrate runs
|
|
Then every article keeps its id, title, body, slug, is_online and timestamps
|
|
And each article has lang "" and its own distinct story_id
|
|
And each article has content_revision 1
|
|
And site "default" has one site_languages row "fr-CA" with is_default true, enabled true and confirmed false
|
|
And the migration marker for the article story backfill is recorded
|
|
|
|
Scenario: Site without a usable locale gets no suggestion row
|
|
Given a site with Locale null
|
|
When database.Migrate runs
|
|
Then the site has no site_languages row
|
|
|
|
Scenario: Migration runs twice
|
|
Given a migrated database
|
|
When database.Migrate runs again
|
|
Then no story is created or changed, no article changes and no site_languages row changes
|
|
And the schema is identical
|
|
|
|
Scenario: Migration is atomic
|
|
Given a database with 3 existing articles
|
|
And the backfill write fails while handling the second article
|
|
When database.Migrate runs
|
|
Then no article has a story_id and the marker is not recorded
|
|
And the next Migrate completes the whole backfill
|
|
|
|
Scenario: Soft-deleted articles are migrated
|
|
Given an article that is soft-deleted before the change
|
|
When database.Migrate runs
|
|
Then it has its own story_id and its tombstone slug is unchanged
|
|
|
|
Scenario: Slug is unique per site and language
|
|
Given site 1 holds an article with slug "bonjour" in language "fr"
|
|
When another article is created in site 1, language "fr", slug "bonjour"
|
|
Then the write fails with a unique violation mapped to ErrSlugTaken
|
|
When an article with slug "bonjour" is created in site 1, language "en"
|
|
Then it is stored
|
|
When an article with slug "bonjour" is created in site 2, language "fr"
|
|
Then it is stored
|
|
|
|
Scenario: Existing unassigned articles keep the current per-site slug rule
|
|
Given two unassigned articles in site 1
|
|
When the second is written with the first one's slug
|
|
Then ErrSlugTaken is returned
|
|
|
|
Scenario: One version per language in a story
|
|
Given story 7 holds an article in language "fr"
|
|
When a second article is stored in story 7, language "fr"
|
|
Then the write fails with a unique violation
|
|
When an article is stored in story 7, language "en"
|
|
Then it is stored
|
|
|
|
Scenario: Unassigned articles never collide on language
|
|
Given 3 articles each with lang "" and their own story
|
|
Then all 3 are stored
|
|
|
|
Scenario: Creating an article before the default is confirmed
|
|
Given site "default" has an unconfirmed suggestion "fr-CA"
|
|
When a user creates an article with title "Bonjour le monde"
|
|
Then the article has lang ""
|
|
And it has a new story_id and content_revision 1
|
|
|
|
Scenario: Creating an article after the default is confirmed
|
|
Given site "default" has a confirmed default "fr-CA"
|
|
When a user creates an article with title "Bonjour le monde"
|
|
Then the article has lang "fr-CA"
|
|
And it has a new story_id distinct from every other article
|
|
|
|
Scenario: Create racing a confirmation never strands an article
|
|
Given site "default" has unassigned articles
|
|
When the default is confirmed and assigned to unassigned articles in one transaction while articles are created concurrently
|
|
Then every article of the site ends with the confirmed default
|
|
And no article has a different language than the confirmed default
|
|
|
|
Scenario: Assigning a language is idempotent
|
|
Given a site whose unassigned articles were assigned "fr-CA"
|
|
When the assignment runs again
|
|
Then no article changes
|
|
|
|
Scenario Outline: Article reads expose lang and story_id
|
|
Given article 12 with lang "<lang>" in story 5
|
|
When it is read through "<read>"
|
|
Then the JSON contains "lang": "<lang>" and "story_id": 5
|
|
And every key present before this change is present with an unchanged value and type
|
|
|
|
Examples:
|
|
| lang | read |
|
|
| fr-CA | GET /v1/article/12 with a valid jwt |
|
|
| | GET /v1/article/12 with a valid jwt |
|
|
| fr-CA | GET /v1/article/ list item |
|
|
| fr-CA | public article read of an online article |
|
|
|
|
Scenario: PATCH ignores lang and story_id
|
|
When PATCH /v1/article/{id} carries "lang" or "story_id"
|
|
Then the fields are ignored and the stored values are unchanged
|
|
And POST /v1/article/ honours lang, story_id, source_article_id and copy only as defined in features/article_translation_create.feature
|
|
And a POST without them behaves as before
|
|
|
|
Scenario: Existing behaviour is unchanged
|
|
Then the existing article, pagination, publishing and SEO tests pass without edits
|
|
And offline articles still read as not found publicly
|
|
|
|
Scenario: Persistence portability
|
|
Given the repository contract cases above
|
|
Then they pass on SQLite, MySQL 8.4, MariaDB 11 and PostgreSQL
|
|
And no raw SQL uses a dialect-only construct
|