Skip to content

Commit da218b3

Browse files
committed
add flushing tests
1 parent 87efd29 commit da218b3

File tree

1 file changed

+92
-0
lines changed

1 file changed

+92
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import * as Sentry from '@sentry/node';
2+
import * as utils from '@sentry/utils';
3+
import { NextApiHandler, NextApiRequest, NextApiResponse } from 'next';
4+
5+
import { AugmentedNextApiResponse, withSentry, WrappedNextApiHandler } from '../../src/utils/withSentry';
6+
7+
const FLUSH_DURATION = 200;
8+
9+
async function sleep(ms: number): Promise<void> {
10+
await new Promise(resolve => setTimeout(resolve, ms));
11+
}
12+
13+
async function callWrappedHandler(wrappedHandler: WrappedNextApiHandler, req: NextApiRequest, res: NextApiResponse) {
14+
await wrappedHandler(req, res);
15+
16+
// Within the wrapped handler, we await `flush()` inside `res.end()`, but nothing awaits `res.end()`, because in its
17+
// original version, it's sync (unlike the function we wrap around it). The original does actually *act* like an async
18+
// function being awaited - subsequent steps in the request/response lifecycle don't happen until it emits a
19+
// `prefinished` event (which is why it's safe for us to make the switch). But here in tests, there's nothing past
20+
// `res.end()`, so we have to manually wait for it to be done.
21+
await sleep(FLUSH_DURATION);
22+
while (!res.finished) {
23+
await sleep(100);
24+
}
25+
}
26+
27+
// We mock `captureException` as a no-op because under normal circumstances it is an un-awaited effectively-async
28+
// function which might or might not finish before any given test ends, potentially leading jest to error out.
29+
const captureExceptionSpy = jest.spyOn(Sentry, 'captureException').mockImplementation(jest.fn());
30+
const loggerSpy = jest.spyOn(utils.logger, 'log');
31+
const flushSpy = jest.spyOn(Sentry, 'flush').mockImplementation(async () => {
32+
// simulate the time it takes time to flush all events
33+
await sleep(FLUSH_DURATION);
34+
return true;
35+
});
36+
37+
describe('withSentry', () => {
38+
let req: NextApiRequest, res: NextApiResponse;
39+
40+
const error = new Error('Oh, no! Charlie ate the flip-flops! :-(');
41+
42+
const origHandlerNoError: NextApiHandler = async (_req, res) => {
43+
res.send('Good dog, Maisey!');
44+
};
45+
const origHandlerWithError: NextApiHandler = async (_req, _res) => {
46+
throw error;
47+
};
48+
49+
const wrappedHandlerNoError = withSentry(origHandlerNoError);
50+
const wrappedHandlerWithError = withSentry(origHandlerWithError);
51+
52+
beforeEach(() => {
53+
req = { url: 'http://dogs.are.great' } as NextApiRequest;
54+
res = ({
55+
send: function(this: AugmentedNextApiResponse) {
56+
this.end();
57+
},
58+
end: function(this: AugmentedNextApiResponse) {
59+
this.finished = true;
60+
},
61+
} as unknown) as AugmentedNextApiResponse;
62+
});
63+
64+
afterEach(() => {
65+
jest.clearAllMocks();
66+
});
67+
68+
describe('flushing', () => {
69+
it('flushes events before rethrowing error', async () => {
70+
try {
71+
await callWrappedHandler(wrappedHandlerWithError, req, res);
72+
} catch (err) {
73+
expect(err).toBe(error);
74+
}
75+
76+
expect(captureExceptionSpy).toHaveBeenCalledWith(error);
77+
expect(flushSpy).toHaveBeenCalled();
78+
expect(loggerSpy).toHaveBeenCalledWith('Done flushing events');
79+
80+
// This ensures the expect inside the `catch` block actually ran, i.e., that in the end the wrapped handler
81+
// errored out the same way it would without sentry, meaning the error was indeed rethrown
82+
expect.assertions(4);
83+
});
84+
85+
it('flushes events before finishing non-erroring response', async () => {
86+
await callWrappedHandler(wrappedHandlerNoError, req, res);
87+
88+
expect(flushSpy).toHaveBeenCalled();
89+
expect(loggerSpy).toHaveBeenCalledWith('Done flushing events');
90+
});
91+
});
92+
});

0 commit comments

Comments
 (0)