Skip to content

Commit 1e198b2

Browse files
committed
add function to rewrap http methods if necessary
1 parent 447aa32 commit 1e198b2

File tree

1 file changed

+66
-2
lines changed
  • packages/nextjs/test/integration/test/utils

1 file changed

+66
-2
lines changed

packages/nextjs/test/integration/test/utils/server.js

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,22 @@
11
const { get } = require('http');
22
const nock = require('nock');
3+
const nodeSDK = require('@sentry/node');
34
const { logIf, parseEnvelope } = require('./common');
45

56
Error.stackTraceLimit = Infinity;
67

7-
const getAsync = url => {
8+
const getAsync = (url, rewrap = false) => {
9+
// Depending on what version of Nextjs we're testing, the wrapping which happens in the `Http` integration may have
10+
// happened too early and gotten overwritten by `nock`. This redoes the wrapping if so.
11+
//
12+
// TODO: This works but is pretty hacky in that it has the potential to wrap things multiple times, more even than the
13+
// double-wrapping which is discussed at length in the comment in `ensureWrappedGet` below, which is why we need
14+
// `rewrap`. Once we fix `fill` to not wrap things twice, we should be able to take this out and just always call
15+
// `ensureWrappedGet`.
16+
const wrappedGet = rewrap ? ensureWrappedGet(get, url) : get;
17+
818
return new Promise((resolve, reject) => {
9-
get(url, res => {
19+
wrappedGet(url, res => {
1020
res.setEncoding('utf8');
1121
let rawData = '';
1222
res.on('data', chunk => {
@@ -99,6 +109,60 @@ const objectMatches = (actual, expected) => {
99109
return true;
100110
};
101111

112+
/**
113+
* Rewrap `http.get` if the wrapped version has been overridden by `nock`.
114+
*
115+
* This is only relevant for Nextjs >= 12.1, which changed when `_app` is initialized, which in turn changed the order
116+
* in which our SDK and `nock` wrap `http.get`. See https://github.com/getsentry/sentry-javascript/pull/4619.
117+
*
118+
* TODO: We'll have to do this for `ClientRequest` also if we decide to start wrapping that.
119+
* TODO: Can we fix the wrapping-things-twice problem discussed in the comment below?
120+
*/
121+
function ensureWrappedGet(importedGet, url) {
122+
// we always test against the latest minor for any given Nextjs major version, so if we're testing Next 12, it's
123+
// definitely at least 12.1, making this check against the major version sufficient
124+
if (Number(process.env.NEXTJS_VERSION) < 12) {
125+
return importedGet;
126+
}
127+
128+
// As of Next 12.1, creating a `NextServer` instance (which we do immediately upon starting this test runner) loads
129+
// `_app`, which has the effect of initializing the SDK. So, unless something's gone wrong, we should always be able
130+
// to find the integration
131+
let httpIntegration;
132+
try {
133+
httpIntegration = nodeSDK.getCurrentHub().getClient().getIntegration(nodeSDK.Integrations.Http);
134+
} catch (err) {
135+
console.warn(`Warning: Sentry SDK not set up at \`NextServer\` initialization. Request URL: ${url}`);
136+
return importedGet;
137+
}
138+
139+
// This rewraps `http.get` and `http.request`, which, at this point, look like `nockWrapper(sentryWrapper(get))` and
140+
// `nockWrapper(sentryWrapper(request))`. By the time we're done with this function, they'll look like
141+
// `sentryWrapper(nockWrapper(sentryWrapper(get)))` and `sentryWrapper(nockWrapper(sentryWrapper(request)))`,
142+
// respectively. Though this seems less than ideal, we don't have to worry about our instrumentation being
143+
// (meaningfully) called twice because:
144+
//
145+
// 1) As long as we set up a `nock` interceptor for any outgoing http request, `nock`'s wrapper will call a replacement
146+
// function for that request rather than call the function it's wrapping (in other words, it will look more like
147+
// `sentryWrapper(nockWrapper(getSubstitute))` than `sentryWrapper(nockWrapper(sentryWrapper(get)))`), which means
148+
// our code is only called once.
149+
// 2) In cases where we don't set up an interceptor (such as for the `wrappedGet` call in `getAsync` above), it's true
150+
// that we can end up with `sentryWrapper(nockWrapper(sentryWrapper(get)))`, meaning our wrapper code will run
151+
// twice. For now that's okay because in those cases we're not in the middle of a transactoin and therefore
152+
// the two wrappers' respective attempts to start spans will both no-op.
153+
//
154+
// TL; DR - if the double-wrapping means you're seeing two spans where you really only want one, set up a nock
155+
// interceptor for the request.
156+
//
157+
// TODO: add in a "don't do this twice" check (in `fill`, maybe moved from `wrap`), so that we don't wrap the outer
158+
// wrapper with a third wrapper
159+
httpIntegration.setupOnce();
160+
161+
// now that we've rewrapped it, grab the correct version of `get` for use in our tests
162+
const httpModule = require('http');
163+
return httpModule.get;
164+
}
165+
102166
module.exports = {
103167
getAsync,
104168
interceptEventRequest,

0 commit comments

Comments
 (0)