[NO-REF] Handle non-JSON HTTP error bodies, vendor test fixtures, run tests serially - #266
[NO-REF] Handle non-JSON HTTP error bodies, vendor test fixtures, run tests serially#266pedro-lb wants to merge 5 commits into
Conversation
The catalog and tasks specs downloaded their fixture files from raw.githubusercontent.com in a `before` hook on every run. When the request is rate limited, the response body is plain text rather than a file, which surfaces as `SyntaxError: Unexpected token 'T', "Too many requests" is not valid JSON` and a cascade of 5s timeouts unrelated to the code under test. Commit the four fixtures (1.9 KB total) under `spec/fixtures/` and read them from disk via a `readCatalogFixture` helper, removing the network dependency from test setup. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
This PR vendors four catalog fixture files into the repo and replaces network fetches with synchronous disk reads, eliminating a flaky test failure caused by GitHub rate-limiting on raw content URLs.
Inline comments: 2 discussions added
Overall Assessment: ✅ Pass
There was a problem hiding this comment.
Pull request overview
This PR removes a test-suite network dependency by vendoring small catalog fixture files into the repository and updating specs to read them from disk instead of fetching them from raw.githubusercontent.com at runtime.
Changes:
- Added catalog fixture files under
spec/fixtures/. - Introduced
readCatalogFixtureinspec/mocha.helpers.jsto load fixtures from disk as buffers. - Updated affected specs to use the local fixtures and removed the prior network-fetch setup.
Reviewed changes
Copilot reviewed 6 out of 7 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| spec/src/modules/tasks.js | Replaces remote fixture fetch with helpers.readCatalogFixture('items.csv') for catalog upload setup. |
| spec/src/modules/catalog/catalog-files.js | Loads all catalog-related fixtures from disk and removes the before hook that fetched them over the network. |
| spec/mocha.helpers.js | Adds readCatalogFixture helper to synchronously read vendored fixtures as buffers. |
| spec/fixtures/variations.csv | Adds vendored variations catalog fixture. |
| spec/fixtures/items.csv | Adds vendored items catalog fixture. |
| spec/fixtures/item_groups.csv | Adds vendored item groups fixture. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
`throwHttpErrorFromResponse` parsed every non-2XX body as JSON. Error
responses are not always JSON: rate limit and gateway responses are
commonly plain text or HTML. For those, `response.json()` rejected and
the SDK surfaced the parse failure instead of the request failure:
SyntaxError: Unexpected token 'T', "Too many requests" is not valid JSON
at Response.json (node_modules/node-fetch/src/body.js:149:15)
The real status, statusText, url and headers were lost with it, which are
the details the function exists to attach.
Read the body as text and attempt to parse it, falling back to the raw
text as the error message, and to `HTTP <status>` when the body is empty.
`error.message` is now always a string, where previously it was
`undefined` for a JSON body without a `message` field.
All modules route their non-2XX responses through this helper, so this
covers every endpoint. `tracker.js` already handled non-JSON bodies this
way when emitting error events.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The suite makes real API calls, and the global 5s timeout is not enough for the slower endpoints when the run is being rate limited. Requests that complete normally take 50-350ms, so anything approaching 5s is already backing off rather than working. Across four runs on this branch the timeouts landed in seven different spec files, so this raises the shared timeout rather than patching each `describe` that happened to lose the race. Hook timeouts matter most here: `--retries 3` does not apply to hooks, so one slow `before` takes out its entire block on the first attempt. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Reverts the 15s timeout from the previous commit: it made things worse (987 passing / 7 failing, against 1049 / 6 at 5s) because a rate limited request holding a slot for 15s prolongs the pressure instead of easing it. Every spec throttles itself with a 300ms `afterEach` delay, commented as being there to avoid rate limiting. One request per 300ms is ~200/min, which sits just under the `x-ratelimit-limit: 201` the API reports — the throttle is calibrated to the limit. `--parallel` gives each worker its own 300ms throttle, so N workers make N times the calibrated rate and the run exceeds the quota. That is why the failures move between spec files run to run, and why they surface as `Too many requests` and 5s-plus response times rather than as assertion failures. Running serially restores the rate the throttle was written for. This trades wall clock time for a run that can actually pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The test's promise chain had no rejection handler, so a failing assertion never reached `done()` and the failure was reported as a timeout instead. That is why it exceeded both the 5s and the 15s timeout identically: the duration was never the problem, `done()` simply was never called. Adding `.catch(done)` reports the actual assertion error. This is diagnostic — the underlying expectation still needs fixing once CI shows what the endpoint returns. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Tried to apply a quick fix here by versioning fixtures instead of fetching them, but found out tests are currently firing requests so the fix will be a bit longer. @Mudaafi would love to align on this! |
Five commits. The first is a real SDK fix; the rest are about making this repo's test runs legible. One test still fails — details at the bottom, it needs someone with context on the test index.
1. Preserve HTTP error details when the response body is not JSON
helpers.throwHttpErrorFromResponsecalledresponse.json()on every non-2XX body. Error responses are not always JSON — rate limit and gateway responses are commonly plain text. For those the parse rejects and the SDK surfaces the parse failure rather than the request failure:The
status,statusText,urlandheadersgo with it — the details the function exists to attach. A caller cannot tell a rate limit from a bad gateway from a malformed payload. Now the body is read as text and parsed if possible, falling back to the raw text, and toHTTP <status>when empty. All modules route non-2XX responses through this one helper.tracker.jsalready handled non-JSON bodies this way when emitting error events.Behavior change:
error.messageis now always a string. Previously a JSON body without amessagefield left itundefined, which makeserror.message.toLowerCase()throw —spec/src/modules/catalog/catalog-groups-v2.js:223does exactly that.2. Vendor the catalog test fixtures
tasks.jsandcatalog-files.jsdownloaded fixtures fromraw.githubusercontent.comon every run, before any assertion. Now committed underspec/fixtures/(1.9 KB, byte-identical to the remote copies) and read from disk.spec/is outsidefilesinpackage.json, so nothing extra ships.3-4. Run the suite serially
Every spec throttles itself with a 300ms
afterEach, commented as being there to avoid rate limiting. One request per 300ms is ~200/min, just under thex-ratelimit-limit: 201the API reports — the throttle is calibrated to the limit.--parallelgives each worker its own 300ms throttle, so N workers make N times that rate and the run exceeds the quota. That is why failures moved between spec files run to run.Measured on this branch:
--parallel, 5s timeout--parallel, 5s (re-run)--parallel, 5s + commit 1--parallel, 15sCommit 4 also reverts a 15s timeout I tried in commit 3: it made things worse, since a rate limited request holding a slot for 15s prolongs the pressure.
.mocharc.jsonis back at 5s.Trade-off: serial costs wall clock time. If that is not acceptable, the alternative is raising the rate limit on the test API key so
--parallelfits within it.5. Surface the real failure in one search test
spec/src/modules/search.js:465had no rejection handler on its promise chain, so a failing assertion never reacheddone()and was reported as a timeout. It exceeded the 5s and the 15s timeout identically — the duration was never the problem. With.catch(done)the real error appears.Worth noting the file has 53
.then((res) => {and 2.catch(done), so other tests there can mask failures the same way. Left alone here.The remaining failure — needs your context
The
preFilterExpressionis(group_id=BrandXY AND Color=red) OR (Color=blue AND Brand=XYZ). Initems.csv,10005(BrandXY, red) matches the first branch and10008(blue, XYZ) the second, so the expected 2 looks right against the fixture. The index returns 1, which suggests the test index does not currently hold what the fixture describes — several specs callreplaceCatalogagainst the shared test key, so its state is not fixture-determined.I did not guess at the expected number. This one needs someone who knows the intended state of the test index.
🤖 Generated with Claude Code