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:
1 parent
9f4ce426a8
commit
daf3594316
86 files changed
+4223
-1485
No files matched your search
@@ -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
|
||||
Reference in new issue
Block a user