Skip to content

feat: add the client contract, fixtures and contract scenarios - #3

Merged
MobileMage merged 13 commits into
mainfrom
feat/client-contract
Oct 3, 2026
Merged

MobileMage merged 13 commits into
mainfrom
feat/client-contract

Conversation

@MobileMage

Copy link
Copy Markdown
Collaborator

Spec 0.2.0 adds what an API client needs besides the grammar. client.md
is the contract that every Gatepost client follows: the calls, the
results, the error codes and the map from each HTTP status to one of
them, retries, the queue, the cache and the input rules. The completed
OpenAPI file describes the NIPOST gateway, and each response carries its
evidence: observed in our own calls, documented by NIPOST, or assumed.

The fixtures and mock keys are synthetic and hold no real postcode or
key. The contract scenarios script each case that a client must pass,
and the mock server plays them by their X-Scenario-Id header. Checks tie
each scenario to its rule in client.md and to the responses of the
OpenAPI file, so a deleted or false scenario fails.

This change is larger than the 400 lines that GIT-5 suggests, because
most of it is fixtures and scenarios, which are data.

Signed-off-by: MobileMage aidelojejoshua@gmail.com

Every client must give the same results and errors for the same
responses of the gateway. client.md fixes them, with the map from each
status to one error code that ERR-1 asks for. The calls of 2 Oct 2026
showed facts that the first interface missed: lookup has a status,
suggestions have no label, an empty autocomplete query gets no answer,
and a 429 carries no Retry-After. The spec moves to 0.2.0, because it
adds the contract.

Signed-off-by: MobileMage <aidelojejoshua@gmail.com>
A review found three gaps that would let the clients differ: an
unknown lookup status became null, no rule covered a bad level,
coordinate or distance, and the contract did not say that a client
ignores unknown fields in a response.

Signed-off-by: MobileMage <aidelojejoshua@gmail.com>
NIPOST's OpenAPI file gives no error responses, no nearby body and no
rate-limit headers. The completed file adds them, and marks each
response as observed, documented or assumed, so a reader knows what a
call has shown. The checks reject a path that NIPOST does not document
and a response with no mark.

Signed-off-by: MobileMage <aidelojejoshua@gmail.com>
The mock server needs one body for each response that a client must
handle, and NIPOST's terms allow only synthetic values. Every postcode
here has the district Z99 and the area ZZ, and a test fails on any other
full postcode. A test also checks each body against its schema in the
OpenAPI file, so the two cannot drift apart without a red build.

Signed-off-by: MobileMage <aidelojejoshua@gmail.com>
Every client must pass the same scenarios against the mock server.
Each scenario names the calls, the responses and the outcome, with the
attempts and the waits, so a TypeScript and a PHP client that pass them
behave the same. The scenarios also cover the input rules, the plain
form of a number, unknown fields and an unknown lookup status. A check
fails when a scenario names a missing fixture or an error code that
client.md does not list, and when an error code or a required rule has
no scenario.

Signed-off-by: MobileMage <aidelojejoshua@gmail.com>
A review found that the contract left room for clients to differ.
The unknown-fields rule was too wide, the form of a number in a query
was not fixed, and the contract gave no rule for a reply with a broken
part, for a Retry-After on a 5xx reply, for a total deadline, for a
shared request that one caller cancels, or for the wait of autocomplete.
Each rule now says what a client does, and the tests check that
meaning. A reply with a broken part gets the new error code
unexpected_response, so that no client returns a guessed value.

Signed-off-by: MobileMage <aidelojejoshua@gmail.com>
Each rule that a scenario can test now has one: the total deadline, a
Retry-After on a 503, an invalid Retry-After, two spellings of one
postcode, a whole number in a query, and the four replies with a
missing or broken part. A test checks that every scenario that
client.md names exists, and the required list covers the new ones.

Signed-off-by: MobileMage <aidelojejoshua@gmail.com>
One mark on a shared response claimed more than the calls showed. The
calls without a key reached lookup and autocomplete only, a 403 came
back from lookup only, and no call sent an unknown key. Each operation
now has its own mark for each status, and each error code has its own
mark, so a reader can tell the observed code from the assumed ones. A
check fails when a code claims more than its response, and when an
error response or a header has no mark. The file now also describes
the 5xx replies and the answer to an unknown path, both assumed, so
that every reply the mock server can send has a mark.

Signed-off-by: MobileMage <aidelojejoshua@gmail.com>
A fixture with a wrong status or a wrong error code passed the checks,
and the mock server would then send a wrong reply. Each fixture must
now have a status that a response with its schema declares, an error
code of that response, and the code that its file name states. Its
evidence can claim no more than the mark of that response or code, so
the key that the gateway does not know and the origin error are now
assumed. The scan for real postcodes now also reads the docs beside
the fixtures and the scenarios, accepts only the state FC, and knows
more separators. A new scan fails on text in the form of a real key.
Two error messages now use Gatepost's own words.

Signed-off-by: MobileMage <aidelojejoshua@gmail.com>
The checks let a deleted scenario or a false outcome pass. client.md
now names the scenarios of each rule, and its error table names them
for each row. The check fails when a named scenario has no file, when
a scenario has no rule, and when a scenario of a row ends with another
error code. With one call, the reply to the last attempt must give the
outcome: the error with its status, API code and Retry-After, or the
result with its level, validity and unit. A fixture of another method
and too many attempts fail too. New scenarios cover the rules that had
none: levels 4 and 5, a lower level, the default level, the limits of
the radius, text over 11 characters, the limits of Retry-After, a
shared error, the echoed postcode, and a reverse postcode that does
not parse.

Signed-off-by: MobileMage <aidelojejoshua@gmail.com>
The check of a scenario compared only the last outcome, so a scenario
could keep a wrong count of attempts, a wrong wait, a wrong query or an
error for a good reply. The check now plays the calls as client.md
says: the retries and their waits, the deadline, shared requests, kept
results and the limit of four requests in flight. It compares the
attempts, the waits, the first request and each outcome with the
scenario, and a good reply must give a result. The play lives in its
own module, so the file of format checks stays short. Each rule of
client.md now names its scenario or is left to each client, and a body
above lookup level 1 cannot claim to be observed. A new scenario
covers a reply with no data field.

Signed-off-by: MobileMage <aidelojejoshua@gmail.com>
The truth check compared the code and the label of a suggestion, and not
its postcode. A scenario with a wrong postcode passed, and every correct
client then failed it. The check now builds the postcode from the typed
segments before the active one, plus the code, and a test plants the fault.

Signed-off-by: MobileMage <aidelojejoshua@gmail.com>
The deadline scenario now uses a 2 s attempt and a 7 s wait, so a slow
runner cannot make it flaky while it still tests the deadline and not
the 10 s limit. Five reverse and autocomplete fields get an evidence
mark. Two sentences now credit NIPOST's OpenAPI file for the radius
clamp, because no call of ours tested it. The contract names the null
cases of radiusM, area and district, lists the negative zero and the
wait between attempts as rules that no scenario tests, and gives the
radius rule of the reverse fixture. The TypeScript standard says that
the mock server is outside CS-2. A test uses a postcode that cannot be
real.

Signed-off-by: MobileMage <aidelojejoshua@gmail.com>
@MobileMage
MobileMage marked this pull request as ready for review October 3, 2026 01:39
@MobileMage
MobileMage merged commit 359db01 into main Oct 3, 2026
5 checks passed
@MobileMage
MobileMage deleted the feat/client-contract branch October 3, 2026 10:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant