diff --git a/packages/rest/src/http-response-test-builder.ts b/packages/rest/src/http-response-test-builder.ts index 73a87015f4..f89c65fe83 100644 --- a/packages/rest/src/http-response-test-builder.ts +++ b/packages/rest/src/http-response-test-builder.ts @@ -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 diff --git a/packages/rest/src/rest.test.ts b/packages/rest/src/rest.test.ts index b9d45045b4..5e6487b087 100644 --- a/packages/rest/src/rest.test.ts +++ b/packages/rest/src/rest.test.ts @@ -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; }); diff --git a/packages/rest/test-typecheck-debt.json b/packages/rest/test-typecheck-debt.json index c3fe259271..46d3f49d38 100644 --- a/packages/rest/test-typecheck-debt.json +++ b/packages/rest/test-typecheck-debt.json @@ -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": {} }