Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 39 additions & 7 deletions packages/rest/src/http-response-test-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,13 +76,45 @@
* a mirror keeps only the last status where `mock.calls` keeps every one, and
* `mockClear()` empties one and not the other. One record, not two.
*
* ⚠️ Three other files in this package — `analytics-dataset-dimension-gate`,
* `meta-public-book-grant`, `rest-batch-size-cap` — assert on a mirrored
* `res.statusCode` / `res.body` built by a local `makeRes()` typed `any`. They
* are green for the forbidden reason rather than conforming, but they cannot
* adopt this builder by substitution: converting those reads into spy reads
* CHANGES the assertion, so it is a decision of its own rather than a mechanical
* edit. Recorded here so the omission is not read as an oversight.
* ⚠️ Most of this package's test files — not the three this paragraph used
* to name — assert on a mirrored `res.statusCode` / `res.body` built by a local
* `any`-typed fixture instead of reaching for this builder. They are green for
* the forbidden reason rather than conforming, but they cannot adopt it by
* substitution: converting those reads into spy reads CHANGES the assertion, so
* it is a decision of its own rather than a mechanical edit. Recorded here so
* the omission is not read as an oversight.
*
* ⛔ **Do not put the file list back.** The three names this paragraph carried
* were wrong in both directions at once. The count was wrong — the real
* population is 76, not 3 — and the stated property was wrong for one of the
* three it did name: `analytics-dataset-dimension-gate` contains no `makeRes`
* at all, because its fixture is spelled `mockRes`. Both errors have one cause,
* which is that the population was enumerated by HELPER NAME and the helper name
* is the one thing about this shape that varies. So what replaces the list is
* the property and the command that derives it, and a reader re-derives instead
* of trusting this sentence:
*
* ```
* # builds a mirror AND asserts on it — 76 of 187 test files at a5eccf925
* grep -lE 'expect\([A-Za-z_$][A-Za-z0-9_$]*\.(statusCode|body)\b' \
* packages/rest/src/*.test.ts | xargs grep -l 'statusCode = ' | wc -l
* ```
*
* Only **30** of those 76 spell the fixture `makeRes`; the rest spell it
* `mockRes` or inline it, which is precisely how a name-keyed hand-list came to
* miss 73 of them. The control that shows the filter discriminates rather than
* matching everything it sees: **18** further files DO define a `makeRes` and
* are correctly excluded, because theirs is a spy-only or closure-capture double
* with no mirror to read.
*
* `src/rest.test.ts` holds one fixture of each kind, which is the reason to
* state a property here rather than a filename: its `makeRes` under
* `describe('RestServer — object API exposure')` IS a mirror and IS in the 76,
* while its `makeRes` under `describe('export handler')` captures status in a
* closure and is correctly outside. The mirrored one also feeds that file's only
* `route.handler` call whose enclosing binding is untyped, so neither argument
* is checked at it — typing it would red the response for the reason above, and
* so belongs to the conversion decision rather than to this paragraph.
*
* **It takes no parameters.** `httpRequestForRoute` needs them because a
* request carries fixture DATA that differs per test. A response double carries
Expand Down
10 changes: 10 additions & 0 deletions packages/rest/src/rest.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4109,6 +4109,16 @@ describe('filterDashboardForUser — ADR-0057 D10 widget requiresService gate',
// ---------------------------------------------------------------------------

describe('RestServer — object API exposure (apiEnabled / apiMethods)', () => {
// ⚠️ A MIRROR fixture, NOT this package's response double: it keeps
// `statusCode` / `body` off `status` / `json`, and the assertions below read
// that mirror rather than the spies. It is in the census stated in
// `src/http-response-test-builder.ts` — read that header for why it cannot
// adopt the builder by substitution. ⛔ Do not assume this file is done with
// mirrors because most of it is: `invoke()` below carries the only
// `route.handler` call here whose enclosing binding is untyped, so neither
// argument is checked at it. (The `makeRes` under `describe('export
// handler')` above is a different shape again — it captures status in a
// closure and is outside the census.)
function makeRes() {
const res: any = { statusCode: 200, body: undefined };
res.status = vi.fn((c: number) => { res.statusCode = c; return res; });
Expand Down
2 changes: 1 addition & 1 deletion packages/rest/test-typecheck-debt.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"_comment": "Per-file tsc error debt of the @objectstack/rest TEST layer (#5286). `tsconfig.test.json` compiles `src/**/*.test.ts` — which `tsconfig.json` excludes and therefore no gate ever read — and every file below still carries errors from before that gate existed. THIS FIELD IS GENERATED: every regeneration rewrites it from scripts/check-test-typecheck.mts, and the EXACT ratchet below requires a regeneration on every repair — so an edit made here is gone by the next one. Anything true of THIS package goes in the sibling `_note` field, which is authored, is preserved verbatim, and is never written by the generator (#12624). This comment states NO cause for the errors, deliberately: the classes differ per package and per file, they move as the debt is paid down, and a cause written here is rewritten verbatim into every ledger by every regeneration — so it outlives its own repair and cannot be corrected in the file where it is read. Measure instead, before repairing anything: `tsc --noEmit --pretty false -p tsconfig.test.json` in the package prints the real classes with their TS codes. Each entry maps a file to its per-SIGNATURE error counts, never to a bare total (#13470): a signature is the TS code plus the diagnostic message with structural type blobs collapsed, and it carries NO line or column — so the pin survives edits that move code around, and only stops matching when the error itself becomes a different error. EXACT ratchet, judged by re-running tsc: a file that gains errors is red, a file that loses them is red until its number is re-recorded, a file that reaches zero is red until its entry is deleted, a signature that ARRIVES or VANISHES is red even when the file total is unchanged, and a file NOT listed here may have no errors at all. Regenerate with: pnpm --filter @objectstack/rest gen:test-typecheck-debt",
"_note": "This package's test layer is at ZERO: `entries` is empty, and that is the end state the EXACT ratchet was built for rather than a gap in it. Nothing in this layer is exempt any more, so an error arriving in any src/**/*.test.ts of this package is red on the PR that introduces it, with no entry to widen and nothing to re-record. #13454 closed the last one. The history below is kept because the mechanism is not rest-specific and has not gone away. tsc reports at most ONE argument-assignability error per call expression, so a repair can uncover a different error at the very same site, and a per-file COUNT can hold while the errors underneath it are replaced wholesale: this file was recorded at 2 before #13377 and measured 2 after, and neither of the two was the same error - the request literals that card removed had been masking the response literals beside them. That blindness is closed (#13470): an entry is a map of normalized error SIGNATURE to count, so such a substitution reds with the signature that ARRIVED and the one that VANISHED both named. The count was never the pin; the signature keys were. None of the three classes recorded here was paid down by an annotation, and each ended as one statement a reader can find: the five members IHttpRequest requires are stated in src/http-request-test-builder.ts (#13377), where a request takes its method and its path from the route under test so the two cannot disagree; the four members IHttpResponse requires are stated in src/http-response-test-builder.ts (#13454), whose required-member set is computed from the contract so a new required member fails there rather than in a fixture; and exceljs's module-local Buffer, which shadows Node's inside every exceljs signature, is asserted around once in src/xlsx-test-loader.ts (#13378). What an empty ledger here does NOT say: that every fixture in this package is well-typed. Most `.handler(` call sites still cast their arguments `as any`, and three files (analytics-dataset-dimension-gate, meta-public-book-grant, rest-batch-size-cap) build their response through a local makeRes() typed `any` and assert on a mirrored res.statusCode/res.body. Those are green because nothing is checked there, not because they conform. This ledger can only ever speak for the CHECKED layer.",
"_note": "This package's test layer is at ZERO: `entries` is empty, and that is the end state the EXACT ratchet was built for rather than a gap in it. Nothing in this layer is exempt any more, so an error arriving in any src/**/*.test.ts of this package is red on the PR that introduces it, with no entry to widen and nothing to re-record. #13454 closed the last one. The history below is kept because the mechanism is not rest-specific and has not gone away. tsc reports at most ONE argument-assignability error per call expression, so a repair can uncover a different error at the very same site, and a per-file COUNT can hold while the errors underneath it are replaced wholesale: this file was recorded at 2 before #13377 and measured 2 after, and neither of the two was the same error - the request literals that card removed had been masking the response literals beside them. That blindness is closed (#13470): an entry is a map of normalized error SIGNATURE to count, so such a substitution reds with the signature that ARRIVED and the one that VANISHED both named. The count was never the pin; the signature keys were. None of the three classes recorded here was paid down by an annotation, and each ended as one statement a reader can find: the five members IHttpRequest requires are stated in src/http-request-test-builder.ts (#13377), where a request takes its method and its path from the route under test so the two cannot disagree; the four members IHttpResponse requires are stated in src/http-response-test-builder.ts (#13454), whose required-member set is computed from the contract so a new required member fails there rather than in a fixture; and exceljs's module-local Buffer, which shadows Node's inside every exceljs signature, is asserted around once in src/xlsx-test-loader.ts (#13378). What an empty ledger here does NOT say: that every fixture in this package is well-typed. Most `.handler(` call sites still cast their arguments `as any`, and 76 of this package's 187 test files build their response through a local `any`-typed fixture and assert on a mirrored res.statusCode/res.body. That number replaces a hand-list of three names, which sat here and in src/http-response-test-builder.ts until #15584 and was wrong in both directions: short by 73, and stating a property (`a local makeRes()`) that one of its own three names did not satisfy, since analytics-dataset-dimension-gate spells its fixture mockRes. The cause of both errors is that the population was enumerated by HELPER NAME, which is the one thing about this shape that varies - only 30 of the 76 spell it makeRes. Re-derive it rather than re-listing it: grep -lE 'expect\\([A-Za-z_$][A-Za-z0-9_$]*\\.(statusCode|body)\\b' packages/rest/src/*.test.ts | xargs grep -l 'statusCode = '. Those are green because nothing is checked there, not because they conform. This ledger can only ever speak for the CHECKED layer.",
"entries": {}
}
Loading