Skip to content

Conversation

@aryamohanan
Copy link
Contributor

@aryamohanan aryamohanan commented Sep 22, 2025

Migrate test setup files from node-fetch-v2 to the native fetch API available in Node.js 18+. This change removes the dependency on node-fetch-v2 in the test code and uses the built-in fetch implementation instead.

Note:
This update replaces all usages of node-fetch-v2 in the test setup.

Background:

@aryamohanan aryamohanan self-assigned this Sep 25, 2025
@aryamohanan aryamohanan force-pushed the test-node-fetch branch 2 times, most recently from 8ae3d4d to 9b3dec7 Compare September 30, 2025 11:25
@kirrg001
Copy link
Contributor

kirrg001 commented Oct 2, 2025

https://jsw.ibm.com/browse/INSTA-15850

'request to http://127.0.0.1:65212/ failed, reason: connect ' +
'ECONNREFUSED 127.0.0.1:65212 -- console.error - should be traced'
));
runAndTrace('exit-span', true, 'fetch failed -- console.error - should be traced'));
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Native fetch and node-fetch represent the same failure; they are just surfaced differently.

Native fetch:

  • Throws a TypeError: fetch failed
  • The real cause is nested under error.cause

node-fetch:

  • Throws a FetchError
  • The connection error is exposed directly

@aryamohanan aryamohanan marked this pull request as ready for review January 2, 2026 09:18
@aryamohanan aryamohanan requested a review from a team as a code owner January 2, 2026 09:18
@aryamohanan aryamohanan removed the WIP label Jan 2, 2026
if (process.env.DETACHED_REQUEST) {
setTimeout(async () => {
await fetch(downstreamDummyUrl, { headers: { 'X-Downstream-Header': 'yes' } });
const fetchResponse = await fetch(downstreamDummyUrl, { headers: { 'X-Downstream-Header': 'yes' } });
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This changes because native fetch and node-fetch v2 complete the request at different points in the lifecycle.

Native fetch resolves when response headers arrive, not when the body finishes. The response body is streamed, and if it isn’t consumed, the tracing span is finalized too early and no trace is emitted.

node-fetch v2 tracks the full HTTP lifecycle, so traces are generated even without reading the body.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Native fetch resolves when response headers arrive, not when the body finishes. The response body is streamed, and if it isn’t consumed, the tracing span is finalized too early and no trace is emitted.

Don't we have to add await response.json(); to all apps?

We usually only do

fetch(`http://127.0.0.1:${agentPort}/ping`)
                .then(() => {
                  httpSuccess(res, data);
                })

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No. I only applied this to the cases where tests were breaking (when fetch is called inside a setTimeout, etc.). In the other cases, using await fetch() or .then() allows the promise to resolve and the instrumentation’s .finally() to run, so the span is transmitted.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Discussed: this needs more investigation.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a bug/misbehavior in nativeFetch.

finally is triggered with a delay when using without .text() or .json(). This is not right IMO.

We already receive the response.headers in the then method.
I am quiet sure that the one who added finally intend to handle success or error in one place.

Similar to httpClient (please compare):

  • we wait for response headers
  • span.d + transmit is executed after the headers (not after the body)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exactly, I’ll check this and see whether we can fix it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah we should fix this issue as a PR before this PR 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

created a pr #2265

@abhilash-sivan
Copy link
Contributor

I believe this also needs to be removed!

@aryamohanan
Copy link
Contributor Author

aryamohanan commented Jan 7, 2026

I believe this also needs to be removed!

@abhilash-sivan IMO, this is not part of the test setup code. It is a minimal test application (metrics-test-app) that exists solely to be test the metrics collection.

The purpose of the test is to verify that when this application runs with node-fetch as a dependency, the metrics system reports it correctly. Therefore, this dependency is not related to test setup and does not need to be replaced or removed the test explicitly requires an external dependency to validate the behavior.

Copy link
Contributor

@abhilash-sivan abhilash-sivan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Copy link
Contributor

@kirrg001 kirrg001 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See comments 👍

// To use it in a CommonJS environment, we load it via a dynamic import and
// forward all arguments to the default export.
// eslint-disable-next-line no-shadow
const fetch = (...args) => import('node-fetch').then(({ default: fetch }) => fetch(...args));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this part of this PR? Looks like a separate PR to me.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’ve created a separate PR to clarify and extend the tests: #2264.

if (process.env.DETACHED_REQUEST) {
setTimeout(async () => {
await fetch(downstreamDummyUrl, { headers: { 'X-Downstream-Header': 'yes' } });
const fetchResponse = await fetch(downstreamDummyUrl, { headers: { 'X-Downstream-Header': 'yes' } });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a bug/misbehavior in nativeFetch.

finally is triggered with a delay when using without .text() or .json(). This is not right IMO.

We already receive the response.headers in the then method.
I am quiet sure that the one who added finally intend to handle success or error in one place.

Similar to httpClient (please compare):

  • we wait for response headers
  • span.d + transmit is executed after the headers (not after the body)

@sonarqubecloud
Copy link

sonarqubecloud bot commented Jan 8, 2026

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants