Repository navigation
test: build faked API error answers with a contract-checked helper - #3612
Conversation
apiErrorResponse() in tests/helpers/api-error.mjs writes the API's error envelope and checks it against the ErrorResponse contract schema, so a faked refusal cannot carry a code, detail or request ID the API never sends. 26 hand-built envelopes in 13 browser files that no open PR touches now use it. Codes the API does not have become the real ones (INTERNAL -> INTERNAL_ERROR, PERMISSION_DENIED -> FORBIDDEN, VALIDATION_FAILED -> INVALID_REQUEST, UNAVAILABLE -> DEPENDENCY_UNAVAILABLE, TEST -> the status's code), the stale-generation detail gains its INVALID_VALUE code, and envelopes without meta get a valid request ID.
…refusal The faked 403 had no meta, so the console showed no Request ID; a real API refusal always carries one and the console prints it.
|
🦞👀 Pull request received. I will update this pull request when review starts. ClawSweeper review completeClawSweeper finished reviewing this revision. The review result is being finalized. |
|
Codex review: needs maintainer review before merge. What this changesThis PR validates simulated API error responses against the existing contract and migrates 26 fixtures across 13 browser-test files. Example: Browser tests simulate API refusals with hand-built error envelopes, including values the API contract does not allow.
Review scores
ProductKind: Test only · Worth it: Yes · Fix scope: Complete Merge readiness✅ Ready for maintainer review Keep this PR open for normal landing: it repairs invalid browser-test fixtures with a focused shared helper, and no concrete introduced defect was found. Priority: P3 Before mergeNone. FindingsNone. Agent review detailsHow this fits togetherBrowser tests inject HTTP refusals into the Console’s real API client; the existing error helper now builds those responses using the contracts package’s ErrorResponse schema. flowchart LR
A[Browser test scenario] --> B[API error helper]
C[ErrorResponse contract] --> B
B --> D[Playwright route response]
D --> E[Console API client]
E --> F[Existing UI assertions]
Technical reviewBest possible solution: Land the focused helper and fixture corrections while preserving independent UI expectations and deliberately malformed proxy-response scenarios. Do we have a high-confidence way to reproduce the issue? The original fixtures visibly contain codes absent from ErrorResponse, missing required metadata, and an incomplete detail object; no target code was executed during this review. Is this the best way to solve the issue? Extending the existing test helper with the authoritative schema is proportionate and preserves independent behavior assertions without copying validation rules. AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning medium; reviewed against c000db1d561d. Provenance checked
TestingProof path: in-process harness. SecurityNone. EvidenceWhat I checked:
Likely related people:
Review metrics
LabelsLabel changes:
Label justifications:
Rating scale6/6 🦀 challenger crab · 5/6 🦞 diamond lobster · 4/6 🐚 platinum hermit · 3/6 🦐 gold shrimp · 2/6 🦪 silver shellfish · 1/6 🧂 unranked krab. Overall follows the weaker of proof and patch quality; ✨ marks media proof (a screenshot, video, or linked artifact) that directly shows the changed behavior. WorkflowClawSweeper edits this one comment on every review. Comment Reviewed October 25, 2026, 6:23 AM ET / 10:23 UTC. |
Problem
Browser tests fake API refusals by writing the error envelope by hand in
route.fulfill(about 146 copies in 33 files). Nothing checks those copies against the API contract, and many have drifted from what the API can send:INTERNAL,PERMISSION_DENIED,VALIDATION_FAILED,UNAVAILABLE,TEST.code. The API sendsINVALID_VALUE.req_test_…), or nometaat all, or nomessage.The repository's test rules say a test must use the API's actual response envelopes. A test that fakes an answer the API never sends proves less than it appears to.
Change
tests/helpers/api-error.mjsgetsapiErrorResponse(status, code, message, { details, requestId }). It returns aroute.fulfill()answer with the API's envelope,{ error: { code, message, details? }, meta: { requestId } }, and checks that envelope against the contract'sErrorResponseschema (TypeBoxCheck).requestId, it generates a valid one.INTERNALbecomesINTERNAL_ERROR,PERMISSION_DENIEDbecomesFORBIDDEN,VALIDATION_FAILEDbecomesINVALID_REQUEST, andUNAVAILABLEbecomesDEPENDENCY_UNAVAILABLE.TESToutcomes becomeRESOURCE_CONFLICT(409) andINTERNAL_ERROR(500).code: "INVALID_VALUE".metaget one.console-status-block: the Share Agent refusal's faked 403 had nometa, so the console showed no request ID. A real 403 always carries one, and the console prints it. The exact-text check now includesRequest ID: ….console-runtime-credentials(4),console-status-block(3),console-agents-list-return(3),console-create-agent-member(3),console-form-fields-2(3),console-agent-deploy-again(2),console-json-error-feedback(2),console-form-fields(1),console-agent-saves(1),console-namespace-not-ready(1),console-namespace-recheck(1),console-refocus-focus(1),console-retained-revalidation(1).Left as they are, on purpose:
BAD_GATEWAY502 inconsole-status-block.RATE_LIMITED429 cases inconsole-secretsandconsole-form-fields. The Secret API never sends this code, so these need their own fix.Verification
On a remote test VM:
checks-browser(24 files) andchecks-browser-2(25 files) pass with this change.The validator refuses each kind of drift above. Called directly, it refuses:
500 INTERNALrequestId: "req_test"/expectedGenerationdetail without a codeMutant:
apiErrorResponseanswers with status 200. 9 of the 13 caller files fail at a migrated site. The other 4 pass:console-runtime-credentials,console-namespace-recheck,console-refocus-focusandconsole-retained-revalidation. There the console reads the error body whatever the status is, or the check covers only the reread.Locally (CPUs 20-27):
assertApiErrorfrom the same module pass: 96/96.tsc --build tsconfig.json tests/tsconfig.json, ESLint and Prettier are clean.Open PRs:
git merge-treewith current main is clean.Risks
Test-only. A faked refusal now goes through the schema check when the module loads or the test runs, so a later contract change that drops a code fails these tests at the helper, with the schema problem named. That is the purpose of the check.