fix: technical debts left by the Sites, Authors and SEO slices, plus backend support for the new Sites and Authors pagination

This commit is contained in:
Florian Sylvain committed 2026-10-19 00:25:31 +02:00
1 parent 9f4ce426a8
commit daf3594316
86 files changed
+4223 -1485

No files matched your search

+144
View File
@@ -632,9 +632,153 @@ Feature: Sites backend, the owner manages sites and every article belongs to exa
When a verified account sends GET /v1/activity/?category=sites
Then the site events are returned with category "sites"
Rule: Pagination and search of the site list
Background:
Given GET /v1/site/ accepts q, page and size, keeps the body a JSON array, and sends the number of matching sites in the X-Total-Count header on every 200
And when neither page nor size is sent, every matching site is returned
And when page or size is sent, size is one of 5, 25, 50 or 100 (default 25 if only page is sent) and page is a positive integer (default 1 if only size is sent)
And q is at most 200 characters and matches name, key or base_url as a case- and accent-insensitive substring
And the order is unchanged and applied before paging: the default site first, then by name, then by id
And X-Total-Count is already in the CORS ExposedHeaders, so the router needs no change
And an invalid page, size or q answers 400 with apperr.BodyErrorMessage and sends no X-Total-Count, like GET /v1/article/
And access is unchanged: a verified session or an API key reads, an unverified account gets 403, no credentials get 401
And the body is [] and never null when nothing matches
Scenario: With no paging parameter every site is returned
Given 61 sites of which "Renew" is the default
When a verified account sends GET /v1/site/
Then the status is 200
And the body is an array of 61 sites starting with "Renew"
And X-Total-Count is "61"
Scenario: q alone returns every match
Given 61 sites of which 40 match "shop"
When a verified account sends GET /v1/site/?q=shop
Then the body holds 40 sites and X-Total-Count is "40"
Scenario Outline: Paging and sizes
Given 61 sites
When a verified account sends GET /v1/site/?<params>
Then the body holds <count> sites
And X-Total-Count is "61"
Examples:
| params | count |
| page=1 | 25 |
| page=2 | 25 |
| page=3 | 11 |
| size=5 | 5 |
| size=25 | 25 |
| size=50&page=2 | 11 |
| size=100 | 61 |
Scenario: Pages never overlap and keep the order
Given 61 sites
When pages 1 to 3 are read with the default size
Then the ids are all distinct, the default site is first on page 1, and the concatenation equals the unpaged list
Scenario: A page past the end is an empty array with the total
Given 61 sites
When a verified account sends GET /v1/site/?page=99
Then the status is 200 and the body is []
And X-Total-Count is "61"
Scenario: No match is an empty array
When a verified account sends GET /v1/site/?q=nomatch
Then the status is 200 and the body is [] and not null
And X-Total-Count is "0"
Scenario Outline: Search matches name, key and base URL
Given the sites "Renew" (key "renew", https://renew.example.com), "Journal" (key "blog", https://journal.example.org) and "Archive" (key "archive", https://old.example.net)
When a verified account sends GET /v1/site/?q=<q>
Then the names are <names>
And X-Total-Count is "<total>"
Examples:
| q | names | total |
| renew | "Renew" | 1 |
| JOURNAL | "Journal" | 1 |
| blog | "Journal" | 1 |
| example.org | "Journal" | 1 |
| example | "Renew", "Journal", "Archive" | 3 |
| nobody | none | 0 |
Scenario: Search ignores accents
Given the site "Café" with key "cafe"
When a verified account sends GET /v1/site/?q=cafe and GET /v1/site/?q=CAFÉ
Then both answer "Café" with X-Total-Count "1"
Scenario: Search and paging compose and the total follows the search
Given 61 sites of which 27 match "shop"
When a verified account sends GET /v1/site/?q=shop&page=2
Then the body holds 2 sites and X-Total-Count is "27"
Scenario: The default site is not forced into the results
Given the default site "Renew" and the site "Journal"
When a verified account sends GET /v1/site/?q=journal
Then the body is "Journal" only
Scenario: A wildcard in q is literal
Given the site "100% Shop" and the site "Main"
When a verified account sends GET /v1/site/?q=%25
Then the body is "100% Shop" only
Scenario Outline: Invalid page, size or q
When a verified account sends GET /v1/site/?<params>
Then the status is 400
And the body is apperr.BodyErrorMessage
And no X-Total-Count header is sent
Examples:
| params |
| page=0 |
| page=-1 |
| page=abc |
| page= |
| size=0 |
| size=30 |
| size=101 |
| size=abc |
| size= |
| q=(201 characters) |
Scenario: q at the limit is accepted
When a verified account sends GET /v1/site/?q=(200 characters)
Then the status is 200 and the body is [] and X-Total-Count is "0"
Scenario: The other fields are unchanged
When a verified account sends GET /v1/site/?size=5
Then every site carries the same fields as before, article_count included, and the owner-only writes are unchanged
Scenario Outline: Every reader gets the header
When GET /v1/site/?size=5 is sent with <credentials>
Then the status is 200 and X-Total-Count is present
Examples:
| credentials |
| the owner's jwt cookie |
| a verified admin's jwt cookie |
| a valid API key |
Scenario Outline: Refused readers get no header
When GET /v1/site/?size=5 is sent with <credentials>
Then the status is <status> and no X-Total-Count is sent
Examples:
| credentials | status |
| an unverified account's jwt cookie | 403 |
| no credentials | 401 |
Scenario: CORS exposes the header
Given CORS_ALLOWED_ORIGINS includes "https://app.example.com"
When a preflighted GET /v1/site/?page=2 comes from that origin
Then Access-Control-Expose-Headers lists X-Total-Count and Link
Rule: Documentation
Scenario: The docs describe the new surface
Then docs/api.md documents the /v1/site routes, their errors, article site_id, the ?site= filter and the media sites_updated field
And docs/api.md documents q, page, size, the all-sites default without paging parameters, the 400, the past-the-end page and X-Total-Count on GET /v1/site/
And docs/configuration.md states that PUBLIC_URL seeds the first site once at migration
And docs/architecture.md mentions the site aggregate, its repository and its use cases