Wrap the eight invitation-key downloads
`GOALS.md` listed these as "eight routes that take no auth token, so they do
not fit the authenticated request path and need a different entry point."
That was wrong on every count, and wrong in the way goal 6 exists to catch:
the premise came from the route names rather than the route file.
All eight sit inside `routes/api.php`'s `api/v1` group, under its
`token_auth` middleware, and the controllers' own OpenAPI blocks declare
`X-API-TOKEN`. So they needed no new entry point — just a macro, alongside
`entity_action_methods!` and for the same four entities. What the item had
actually described is the client-portal family in `routes/client.php`, which
is genuinely token-less, is not under `/api/v1`, and stays unwrapped for
that reason; it is now the gap entry in `COVERAGE.md`.
`download_{invoice,quote,credit,purchase_order}_by_invitation_key` returns
the PDF and `download_e_*_by_invitation_key` the XML e-document. These take
a contact's invitation key rather than a record id, so the server renders in
that contact's locale — a client with contacts in two languages gets a
different document per key, which the id-based `download_*` cannot express.
Two things here are easy to get wrong, so both are pinned by tests. The
entity segment is singular (`invoice/{key}/download`), the only route family
in the API that departs from the plural CRUD path. And an unknown key is a
400 carrying `no record found`: the controllers return that directly instead
of raising `ModelNotFoundException`, so the status matches the rule for a
missing record but the message does not.
The smoke test's read tier now downloads an invoice by invitation key,
taking the key from the `invitations` array the list route already returns.
It is there to re-check the token claim against a real server rather than
against a second reading of the source. The `download_e_*` four are left
out: they render from e-invoice settings a test instance need not have.
Also in here, unrelated: a rustfmt fix to a `match` arm in
`examples/smoke_test.rs` that was already off-format on main, so
`cargo fmt --check` — and with it CI's `test` job — was failing before this
change. It could not be split out cleanly, being in a file this change also
touches.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -157,6 +157,10 @@ Beyond "does it decode", the read tier fetches the same rows raw and fails
|
||||
if a typed row lost a key (`keys kept: <resource>`, names only, never
|
||||
values), and the write tier creates a purchase order, location, webhook and
|
||||
recurring invoice from bare `Default` models, since those are what broke.
|
||||
The read tier also downloads an invoice by invitation key, which is there to
|
||||
re-check the claim that those routes are token-authenticated; the matching
|
||||
`download_e_*` is left out, since it renders from e-invoice settings a test
|
||||
instance need not have.
|
||||
With `IN_API_PASSWORD` set as well, a password tier merges two throwaway
|
||||
clients and purges the survivor (a real, permanent delete); it was run
|
||||
against a disposable 5.13.50 instance on 2026-10-24 and all four checks
|
||||
@@ -241,6 +245,17 @@ changes, doc tweaks) does not need an entry.
|
||||
elsewhere without checking `routes/api.php`. In particular the official PHP
|
||||
SDK puts bulk `download` on *every* resource, which is wrong; only
|
||||
PDF-rendering entities support it.
|
||||
- `invitation_download_methods!` generates `download_{x}_by_invitation_key`
|
||||
and `download_e_{x}_by_invitation_key` for the same four resources. Two
|
||||
traps, both pinned by tests: the entity segment is **singular**
|
||||
(`invoice/{key}/download`, the only route family in the API that is), and
|
||||
the routes are **authenticated** despite being the link a client follows —
|
||||
they sit in `routes/api.php`'s `api/v1` group under `token_auth`. The
|
||||
genuinely token-less invitation downloads are the separate client-portal
|
||||
routes in `routes/client.php`, which are not under `/api/v1` and are not
|
||||
wrapped. `COVERAGE.md` used to list the `/api/v1` eight as unwrapped for
|
||||
that reason, having conflated the two families; don't reintroduce the
|
||||
claim.
|
||||
- `upload_methods!` generates `upload_{x}` for the 16 resources with a
|
||||
`/{id}/upload` route; documents can only be created this way, because
|
||||
`DocumentController::store` is an empty stub upstream (hence no
|
||||
@@ -322,18 +337,20 @@ changes, doc tweaks) does not need an entry.
|
||||
## Coverage
|
||||
|
||||
**All 34 of the API's `Route::resource` groups are wrapped**, plus `users`
|
||||
(which isn't one). 325 methods: 30 resources with CRUD + bulk, 3 with CRUD
|
||||
only, 4 with entity actions, 16 with document uploads, and 69 hand-written.
|
||||
(which isn't one). 333 methods: 30 resources with CRUD + bulk, 3 with CRUD
|
||||
only, 4 with entity actions, 4 with invitation-key downloads, 16 with
|
||||
document uploads, and 69 hand-written.
|
||||
|
||||
Plus the non-CRUD surface: documents, entity actions and PDF downloads for
|
||||
the four action-capable resources, `report`, `chart`, `search`, `statics`,
|
||||
`send_email`, `refund_payment`, `ping`, and `health_check`.
|
||||
the four action-capable resources, their invitation-key PDF and e-document
|
||||
downloads, `report`, `chart`, `search`, `statics`, `send_email`,
|
||||
`refund_payment`, `ping`, and `health_check`.
|
||||
|
||||
**`COVERAGE.md` is the detailed inventory** — every wrapped endpoint, every
|
||||
gap, and the reason for each gap. The main remaining gaps are the
|
||||
integration and auth routes, import/export, the public invitation-key
|
||||
downloads (which take no auth token, so they don't fit the client's
|
||||
request path), and a few company and user extras.
|
||||
integration and auth routes, import/export, the client-portal routes (which
|
||||
are not under `/api/v1`, so they don't fit the client's `base_url`), and a
|
||||
few company and user extras.
|
||||
|
||||
Keep `COVERAGE.md` current when adding or removing an endpoint: it names
|
||||
exact counts and route groups, so it goes stale quietly. The three coverage
|
||||
|
||||
Reference in New Issue
Block a user