Skip to content

Commit c78e960

Browse files
committed
fixup: remove unpatch method
1 parent f44352c commit c78e960

File tree

5 files changed

+96
-138
lines changed

5 files changed

+96
-138
lines changed

packages/core/src/index.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -116,12 +116,7 @@ export { linkedErrorsIntegration } from './integrations/linkederrors';
116116
export { moduleMetadataIntegration } from './integrations/moduleMetadata';
117117
export { requestDataIntegration } from './integrations/requestdata';
118118
export { captureConsoleIntegration } from './integrations/captureconsole';
119-
export {
120-
unpatchExpressModule,
121-
patchExpressModule,
122-
setupExpressErrorHandler,
123-
expressErrorHandler,
124-
} from './integrations/express/index';
119+
export { patchExpressModule, setupExpressErrorHandler, expressErrorHandler } from './integrations/express/index';
125120
export type {
126121
ExpressIntegrationOptions,
127122
ExpressHandlerOptions,

packages/core/src/integrations/express/index.ts

Lines changed: 1 addition & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ import {
5656
isExpressWithoutRouterPrototype,
5757
isExpressWithRouterPrototype,
5858
} from './utils';
59-
import { unwrapMethod, wrapMethod } from '../../utils/object';
59+
import { wrapMethod } from '../../utils/object';
6060
import { patchLayer } from './patch-layer';
6161

6262
const getExpressExport = (express: ExpressModuleExport): ExpressExport =>
@@ -156,40 +156,6 @@ export const patchExpressModule = (options: ExpressIntegrationOptions) => {
156156
return express;
157157
};
158158

159-
export const unpatchExpressModule = (options: ExpressIntegrationOptions) => {
160-
const express = getExpressExport(options.express);
161-
const routerProto: ExpressRouterv5 | ExpressRouterv4 | undefined = isExpressWithRouterPrototype(express)
162-
? express.Router.prototype // Express v5
163-
: isExpressWithoutRouterPrototype(express)
164-
? express.Router // Express v4
165-
: undefined;
166-
167-
if (!routerProto) {
168-
throw new TypeError('no valid Express route function to deinstrument');
169-
}
170-
171-
try {
172-
unwrapMethod(routerProto, 'route');
173-
} catch (e) {
174-
DEBUG_BUILD && debug.error('Failed to unpatch express route method:', e);
175-
}
176-
177-
try {
178-
unwrapMethod(routerProto, 'use');
179-
} catch (e) {
180-
DEBUG_BUILD && debug.error('Failed to unpatch express use method:', e);
181-
}
182-
183-
const { application } = express;
184-
try {
185-
unwrapMethod(application, 'use');
186-
} catch (e) {
187-
DEBUG_BUILD && debug.error('Failed to unpatch express application.use method:', e);
188-
}
189-
190-
return express;
191-
};
192-
193159
/**
194160
* An Express-compatible error handler, used by setupExpressErrorHandler
195161
*/

packages/core/src/integrations/express/utils.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import type {
66
ExpressLayer,
77
ExpressLayerType,
88
ExpressRequest,
9-
IgnoreMatcher,
109
LayerPathSegment,
1110
MiddlewareError,
1211
ExpressRouterv4,

packages/core/test/lib/integrations/express/index.test.ts

Lines changed: 87 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import {
22
patchExpressModule,
3-
unpatchExpressModule,
43
expressErrorHandler,
54
setupExpressErrorHandler,
65
} from '../../../../src/integrations/express/index';
76

8-
import { describe, it, expect, vi } from 'vitest';
7+
import { describe, it, expect, vi, beforeEach } from 'vitest';
8+
import type { Mock } from 'vitest';
99
import type {
1010
ExpressIntegrationOptions,
1111
ExpressExportv5,
@@ -22,7 +22,6 @@ import type {
2222
ExpressHandlerOptions,
2323
} from '../../../../src/integrations/express/types';
2424
import type { WrappedFunction } from '../../../../src/types-hoist/wrappedfunction';
25-
import type { IncomingMessage, ServerResponse } from 'http';
2625

2726
const sdkProcessingMetadata: unknown[] = [];
2827
const isolationScope = {
@@ -57,6 +56,7 @@ vi.mock('../../../../src/utils/debug-logger', () => ({
5756
},
5857
}));
5958

59+
beforeEach(() => (patchLayerCalls.length = 0));
6060
const patchLayerCalls: [options: ExpressIntegrationOptions, layer: ExpressLayer, layerPath?: string][] = [];
6161

6262
vi.mock('../../../../src/integrations/express/patch-layer', () => ({
@@ -67,53 +67,78 @@ vi.mock('../../../../src/integrations/express/patch-layer', () => ({
6767
},
6868
}));
6969

70-
describe('(un)patchExpressModule', () => {
71-
it('throws trying to patch/unpatch the wrong thing', () => {
72-
expect(() => {
73-
patchExpressModule({
74-
express: {} as unknown as ExpressModuleExport,
75-
} as unknown as ExpressIntegrationOptions);
76-
}).toThrowError('no valid Express route function to instrument');
77-
expect(() => {
78-
unpatchExpressModule({
79-
express: {},
80-
} as unknown as ExpressIntegrationOptions);
81-
}).toThrowError('no valid Express route function to deinstrument');
82-
});
70+
type ExpressSpies = Record<'routerUse' | 'routerRoute' | 'appUse', Mock<() => void>>;
8371

84-
// these are called in the unit tests below, so set spies
85-
const routerv4route = vi.fn();
86-
const routerv4use = vi.fn();
87-
const appv4use = vi.fn();
88-
const expressv4 = Object.assign(function express() {}, {
89-
application: { use: appv4use },
72+
// get a fresh copy of a mock Express version 4 export
73+
function getExpress4(): ExpressExportv4 & { spies: ExpressSpies } {
74+
const routerRoute = vi.fn();
75+
const routerUse = vi.fn();
76+
const appUse = vi.fn();
77+
const spies = {
78+
routerRoute,
79+
routerUse,
80+
appUse,
81+
} as const;
82+
const express = Object.assign(function express() {}, {
83+
spies,
84+
application: { use: appUse },
9085
Router: Object.assign(function Router() {}, {
91-
route: routerv4route,
92-
use: routerv4use,
86+
route: routerRoute,
87+
use: routerUse,
9388
stack: [{ name: 'layer0' }, { name: 'layer1' }, { name: 'layerFinal' }],
9489
}),
95-
}) as unknown as ExpressExportv4;
96-
Object.assign(expressv4.application, { _router: expressv4.Router });
90+
}) as unknown as ExpressExportv4 & { spies: ExpressSpies };
91+
Object.assign(express.application, { _router: express.Router });
92+
93+
return express;
94+
}
9795

98-
const appv5use = vi.fn();
96+
// get a fresh copy of a mock Express version 5 export
97+
function getExpress5(): ExpressExportv5 & { spies: ExpressSpies } {
98+
const routerRoute = vi.fn();
99+
const routerUse = vi.fn();
100+
const appUse = vi.fn();
101+
const spies = {
102+
routerRoute,
103+
routerUse,
104+
appUse,
105+
} as const;
99106
const expressv5 = Object.assign(function express() {}, {
100-
application: { use: appv5use },
107+
spies,
108+
application: { use: appUse },
101109
Router: class Router {
102110
stack: ExpressLayer[] = [];
103-
route() {}
104-
use() {}
111+
route(...args: unknown[]) {
112+
return routerRoute(...args);
113+
}
114+
use(...args: unknown[]) {
115+
return routerUse(...args);
116+
}
105117
},
106-
}) as unknown as ExpressExportv5;
118+
}) as unknown as ExpressExportv5 & { spies: ExpressSpies };
119+
const stack = [{ name: 'layer0' }, { name: 'layer1' }, { name: 'layerFinal' }];
107120
Object.assign(expressv5.application, {
108-
router: {
109-
stack: [{ name: 'layer0' }, { name: 'layer1' }, { name: 'layerFinal' }],
110-
},
121+
router: { stack },
122+
});
123+
124+
return expressv5;
125+
}
126+
127+
describe('patchExpressModule', () => {
128+
it('throws trying to patch/unpatch the wrong thing', () => {
129+
expect(() => {
130+
patchExpressModule({
131+
express: {} as unknown as ExpressModuleExport,
132+
} as unknown as ExpressIntegrationOptions);
133+
}).toThrowError('no valid Express route function to instrument');
111134
});
112135

113136
it('can patch and restore expressv4 style module', () => {
114-
const r = expressv4.Router as ExpressRouterv4;
115-
const a = expressv4.application;
116-
for (const module of [expressv4, { default: expressv4 }]) {
137+
for (const useDefault of [false, true]) {
138+
const express = getExpress4();
139+
const module = useDefault ? { default: express } : express;
140+
const r = express.Router as ExpressRouterv4;
141+
const a = express.application;
117142
const options = { express: module } as unknown as ExpressIntegrationOptions;
118143
expect((r.use as WrappedFunction).__sentry_original__).toBe(undefined);
119144
expect((r.route as WrappedFunction).__sentry_original__).toBe(undefined);
@@ -124,19 +149,15 @@ describe('(un)patchExpressModule', () => {
124149
expect(typeof (r.use as WrappedFunction).__sentry_original__).toBe('function');
125150
expect(typeof (r.route as WrappedFunction).__sentry_original__).toBe('function');
126151
expect(typeof (a.use as WrappedFunction).__sentry_original__).toBe('function');
127-
128-
unpatchExpressModule(options);
129-
130-
expect((r.use as WrappedFunction).__sentry_original__).toBe(undefined);
131-
expect((r.route as WrappedFunction).__sentry_original__).toBe(undefined);
132-
expect((a.use as WrappedFunction).__sentry_original__).toBe(undefined);
133152
}
134153
});
135154

136155
it('can patch and restore expressv5 style module', () => {
137-
const r = expressv5.Router as ExpressRouterv5;
138-
const a = expressv5.application;
139-
for (const module of [expressv5, { default: expressv5 }]) {
156+
for (const useDefault of [false, true]) {
157+
const express = getExpress5();
158+
const r = express.Router as ExpressRouterv5;
159+
const a = express.application;
160+
const module = useDefault ? { default: express } : express;
140161
const options = { express: module } as unknown as ExpressIntegrationOptions;
141162
expect((r.prototype.use as WrappedFunction).__sentry_original__).toBe(undefined);
142163
expect((r.prototype.route as WrappedFunction).__sentry_original__).toBe(undefined);
@@ -147,74 +168,64 @@ describe('(un)patchExpressModule', () => {
147168
expect(typeof (r.prototype.use as WrappedFunction).__sentry_original__).toBe('function');
148169
expect(typeof (r.prototype.route as WrappedFunction).__sentry_original__).toBe('function');
149170
expect(typeof (a.use as WrappedFunction).__sentry_original__).toBe('function');
150-
151-
unpatchExpressModule(options);
152-
153-
expect((r.prototype.use as WrappedFunction).__sentry_original__).toBe(undefined);
154-
expect((r.prototype.route as WrappedFunction).__sentry_original__).toBe(undefined);
155-
expect((a.use as WrappedFunction).__sentry_original__).toBe(undefined);
156171
}
157172
});
158173

159174
it('calls patched and original Router.route', () => {
175+
const expressv4 = getExpress4();
176+
const { spies } = expressv4;
160177
const options = { express: expressv4 };
161178
patchExpressModule(options);
162179
expressv4.Router.route('a');
163-
unpatchExpressModule(options);
164-
expect(patchLayerCalls).toStrictEqual([[options, { name: 'layerFinal' }, 'a']]);
165-
patchLayerCalls.length = 0;
166-
expect(routerv4route).toHaveBeenCalledExactlyOnceWith('a');
167-
routerv4route.mockReset();
180+
expect(spies.routerRoute).toHaveBeenCalledExactlyOnceWith('a');
168181
});
169182

170183
it('calls patched and original Router.use', () => {
184+
const expressv4 = getExpress4();
185+
const { spies } = expressv4;
171186
const options = { express: expressv4 };
172187
patchExpressModule(options);
173188
expressv4.Router.use('a');
174-
unpatchExpressModule(options);
175189
expect(patchLayerCalls).toStrictEqual([[options, { name: 'layerFinal' }, 'a']]);
176-
patchLayerCalls.length = 0;
177-
expect(routerv4use).toHaveBeenCalledExactlyOnceWith('a');
178-
routerv4use.mockReset();
190+
expect(spies.routerUse).toHaveBeenCalledExactlyOnceWith('a');
179191
});
180192

181193
it('skips patchLayer call in Router.use if no layer in the stack', () => {
194+
const expressv4 = getExpress4();
195+
const { spies } = expressv4;
182196
const options = { express: expressv4 };
183197
patchExpressModule(options);
184198
const { stack } = expressv4.Router;
185199
expressv4.Router.stack = [];
186200
expressv4.Router.use('a');
187201
expressv4.Router.stack = stack;
188-
unpatchExpressModule(options);
189202
expect(patchLayerCalls).toStrictEqual([]);
190-
patchLayerCalls.length = 0;
191-
expect(routerv4use).toHaveBeenCalledExactlyOnceWith('a');
192-
routerv4use.mockReset();
203+
expect(spies.routerUse).toHaveBeenCalledExactlyOnceWith('a');
193204
});
194205

195206
it('calls patched and original application.use', () => {
207+
const expressv4 = getExpress4();
208+
const { spies } = expressv4;
196209
const options = { express: expressv4 };
197210
patchExpressModule(options);
198211
expressv4.application.use('a');
199-
unpatchExpressModule(options);
200212
expect(patchLayerCalls).toStrictEqual([[options, { name: 'layerFinal' }, 'a']]);
201-
patchLayerCalls.length = 0;
202-
expect(appv4use).toHaveBeenCalledExactlyOnceWith('a');
203-
appv4use.mockReset();
213+
expect(spies.appUse).toHaveBeenCalledExactlyOnceWith('a');
204214
});
205215

206216
it('calls patched and original application.use on express v5', () => {
217+
const expressv5 = getExpress5();
218+
const { spies } = expressv5;
207219
const options = { express: expressv5 };
208220
patchExpressModule(options);
209221
expressv5.application.use('a');
210-
unpatchExpressModule(options);
211222
expect(patchLayerCalls).toStrictEqual([[options, { name: 'layerFinal' }, 'a']]);
212-
patchLayerCalls.length = 0;
213-
expect(appv5use).toHaveBeenCalledExactlyOnceWith('a');
214-
appv5use.mockReset();
223+
expect(spies.appUse).toHaveBeenCalledExactlyOnceWith('a');
215224
});
216225

217226
it('skips patchLayer on application.use if no router found', () => {
227+
const expressv4 = getExpress4();
228+
const { spies } = expressv4;
218229
const options = { express: expressv4 };
219230
patchExpressModule(options);
220231
const app = expressv4.application as {
@@ -223,29 +234,14 @@ describe('(un)patchExpressModule', () => {
223234
const { _router } = app;
224235
delete app._router;
225236
expressv4.application.use('a');
226-
unpatchExpressModule(options);
227237
app._router = _router;
228238
// no router, so no layers to patch!
229239
expect(patchLayerCalls).toStrictEqual([]);
230-
patchLayerCalls.length = 0;
231-
expect(appv4use).toHaveBeenCalledExactlyOnceWith('a');
232-
appv4use.mockReset();
233-
});
234-
235-
it('debug error when unpatching fails', () => {
236-
unpatchExpressModule({ express: expressv5 });
237-
expect(debugErrors).toStrictEqual([
238-
['Failed to unpatch express route method:', new Error('Method route is not wrapped, and cannot be unwrapped')],
239-
['Failed to unpatch express use method:', new Error('Method use is not wrapped, and cannot be unwrapped')],
240-
[
241-
'Failed to unpatch express application.use method:',
242-
new Error('Method use is not wrapped, and cannot be unwrapped'),
243-
],
244-
]);
245-
debugErrors.length = 0;
240+
expect(spies.appUse).toHaveBeenCalledExactlyOnceWith('a');
246241
});
247242

248243
it('debug error when patching fails', () => {
244+
const expressv5 = getExpress5();
249245
patchExpressModule({ express: expressv5 });
250246
patchExpressModule({ express: expressv5 });
251247
expect(debugErrors).toStrictEqual([
@@ -333,7 +329,7 @@ describe('setupExpressErrorHandler', () => {
333329
const reqHandler = appUseCalls[0];
334330
expect(typeof reqHandler).toBe('function');
335331
const next = vi.fn();
336-
(reqHandler as (request: IncomingMessage, _res: ServerResponse, next: () => void) => void)(
332+
(reqHandler as (request: ExpressRequest, _res: ExpressResponse, next: () => void) => void)(
337333
{
338334
method: 'GET',
339335
headers: { request: 'headers' },

0 commit comments

Comments
 (0)