Skip to content

test: replace artificial with built-in fetch for integration tests#269

Merged
arb merged 1 commit into
masterfrom
remove-artificial-devdep
May 1, 2026
Merged

test: replace artificial with built-in fetch for integration tests#269
arb merged 1 commit into
masterfrom
remove-artificial-devdep

Conversation

@arb

@arb arb commented May 1, 2026

Copy link
Copy Markdown
Owner

Why

Closes #266.

artificial is the source of 7 of the 11 npm warn deprecated notices on a fresh npm install. It pulls in the deprecated @hapi/joi@16 and @hapi/shot@4 lines, which transitively bring @hapi/hoek@8, @hapi/address@2, @hapi/formula@1, @hapi/pinpoint@1, and @hapi/topo@3. artificial itself has not been updated in years, and we use it in exactly one place: test/integration.test.js, where it attaches an in-process inject(...) method on the Express app via @hapi/shot's simulated Request/Response.

What's different now

test/integration.test.js drives the Express app over real HTTP via app.listen(0) and Node's built-in fetch. A small inject shim inside the Server() factory:

  • binds the app to an ephemeral loopback port,
  • issues one fetch per call with a default user-agent: 'shot' (kept so existing assertions stay byte-for-byte) and Connection: close so the server tears down promptly,
  • awaits the listen, the fetch, the optional callback, and the full httpServer.close before resolving.

Because the shim is async and every server.inject(...) call site now awaits it, node:test sees a complete unit of work and exits cleanly. No reliance on --test-force-exit, no global undici dispatcher hook, and no new dependency.

Two side-effects of going from Shot's fake response to real HTTP:

  • The two update req values > req.headers and multiple-runs > continues to set default values tests previously asserted on the entire req.headers bag. With real HTTP that bag includes whatever the transport happens to inject (e.g. accept-language, sec-fetch-mode). Those tests' actual intent is "the Joi default 'secret-header': '@@@@@@' got applied to req.headers," so the assertions are narrowed to that single contract. We're testing celebrate's behavior, not Express + the transport's.
  • Four route handlers in the update req values block previously got away without sending a response because Shot faked the response cycle. Real fetch hangs forever waiting for one, so each of those handlers now calls res.send() after team.attend(req).

Tradeoffs

Real HTTP is slightly slower than in-process injection, but the full integration suite still finishes in ~330ms on Node 20. In exchange, the tests now exercise the actual production code path: Node's HTTP parser, Express.json() reading bytes off a real stream, cookie-parser reading a real Cookie: header, and Express 4 vs Express 5 routing/body parsing differences. That's exactly the regime where future regressions would otherwise ship undetected.

Teamwork is preserved. The optional inject(options, callback) shape is preserved. No public API changes; no production dependency changes; no CI matrix changes; no lib/ changes.

QA Spec

  • npm install emits zero @hapi/* deprecation warnings.
  • npm ls --all shows no @hapi/joi@16, @hapi/shot, @hapi/hoek@8, @hapi/address@2, @hapi/formula@1, @hapi/pinpoint@1, or @hapi/topo@3.
  • npx eslint . is clean.
  • npm run test:ci passes locally with c8 --100 green and the process exits cleanly (no hang).
  • CI matrix passes on every Node x Express combination ([20.x, 22.x, 24.x, 25.x] x [latest-4, latest]).
  • No regression in the 48 unit tests in test/celebrate.test.js.
  • No package-lock.json accidentally committed.

Made with Cursor

artificial was the source of 7 of 11 npm install deprecation
warnings. It transitively pulled in @hapi/joi@16, @hapi/shot@4,
and the rest of the deprecated v8/v16 @Hapi line (hoek, address,
formula, pinpoint, topo). The package itself has not been updated
in years.

Drives the Express app over real HTTP via app.listen(0) and Node's
built-in fetch instead. The async inject shim awaits listen, fetch,
optional callback, and full httpServer.close before resolving, so
node:test sees a complete unit of work and exits cleanly without
relying on --test-force-exit or a global undici dispatcher hook.

Tightens two over-broad header assertions to check what celebrate
actually contributes (a Joi-applied default) instead of the full
header bag the transport happens to set, and adds res.send() to
four route handlers that previously relied on @hapi/shot's fake
response semantics.

No production deps change; no public API change. Verified on
Node 20.19 with both Express 4.22 and Express 5.2: 62/62 tests
pass, 100% coverage holds, no deprecated @hapi/* in npm ls --all.

Closes #266

Co-authored-by: Cursor <cursoragent@cursor.com>
@codecov-commenter

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (670567e) to head (1bc1d72).
❗ Your organization needs to install the Codecov GitHub app to enable full functionality.

Additional details and impacted files
@@            Coverage Diff            @@
##            master      #269   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files            5         5           
  Lines          263       263           
=========================================
  Hits           263       263           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@arb arb merged commit 7f38988 into master May 1, 2026
9 checks passed
@arb arb deleted the remove-artificial-devdep branch May 1, 2026 21:28
@arb arb added this to the 16.0.0 milestone May 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Remove artificial devDependency

2 participants