Skip to content

Commit 44b8ed8

Browse files
committed
add tests for multiple rate limit headers
1 parent 72306e2 commit 44b8ed8

File tree

1 file changed

+112
-85
lines changed

1 file changed

+112
-85
lines changed

packages/replay/test/integration/rateLimiting.test.ts

Lines changed: 112 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -57,98 +57,125 @@ describe('Integration | rate-limiting behaviour', () => {
5757
await new Promise(process.nextTick);
5858
jest.setSystemTime(new Date(BASE_TIMESTAMP));
5959
clearSession(replay);
60+
jest.clearAllMocks();
6061
replay.loadSession({ expiry: SESSION_IDLE_DURATION });
6162
});
6263

6364
afterAll(() => {
6465
replay && replay.stop();
6566
});
6667

67-
it('pauses recording and flushing a rate limit is hit and resumes both after the rate limit duration is over', async () => {
68-
expect(replay.session?.segmentId).toBe(0);
69-
jest.spyOn(replay, 'sendReplay');
70-
jest.spyOn(replay, 'pause');
71-
jest.spyOn(replay, 'resume');
72-
// @ts-ignore private API
73-
jest.spyOn(replay, '_handleRateLimit');
74-
75-
const TEST_EVENT = { data: {}, timestamp: BASE_TIMESTAMP, type: 2 };
76-
77-
mockTransportSend.mockImplementationOnce(() => {
78-
return Promise.resolve({
79-
statusCode: 429,
80-
headers: {
81-
'x-sentry-rate-limits': null,
82-
'retry-after': `30`,
83-
},
84-
} as TransportMakeRequestResponse);
85-
});
86-
87-
mockRecord._emitter(TEST_EVENT);
88-
89-
// T = base + 5
90-
await advanceTimers(DEFAULT_FLUSH_MIN_DELAY);
91-
92-
expect(mockRecord.takeFullSnapshot).not.toHaveBeenCalled();
93-
expect(mockTransportSend).toHaveBeenCalledTimes(1);
94-
expect(replay).toHaveLastSentReplay({ events: JSON.stringify([TEST_EVENT]) });
95-
96-
expect(replay['_handleRateLimit']).toHaveBeenCalledTimes(1);
97-
// resume() was called once before we even started
98-
expect(replay.resume).not.toHaveBeenCalled();
99-
expect(replay.pause).toHaveBeenCalledTimes(1);
100-
101-
// No user activity to trigger an update
102-
expect(replay.session?.lastActivity).toBe(BASE_TIMESTAMP);
103-
expect(replay.session?.segmentId).toBe(1);
104-
105-
// let's simulate the rate-limit time of inactivity (30secs) and check that we don't do anything in the meantime
106-
const TEST_EVENT2 = { data: {}, timestamp: BASE_TIMESTAMP + DEFAULT_FLUSH_MIN_DELAY, type: 3 };
107-
for (let i = 0; i < 5; i++) {
108-
const ev = {
109-
...TEST_EVENT2,
110-
timestamp: BASE_TIMESTAMP + DEFAULT_FLUSH_MIN_DELAY * (i + 1),
111-
};
112-
mockRecord._emitter(ev);
68+
it.each([
69+
{
70+
statusCode: 429,
71+
headers: {
72+
'x-sentry-rate-limits': '30',
73+
'retry-after': null,
74+
},
75+
},
76+
{
77+
statusCode: 429,
78+
headers: {
79+
'x-sentry-rate-limits': '30:replay_event',
80+
'retry-after': null,
81+
},
82+
},
83+
{
84+
statusCode: 429,
85+
headers: {
86+
'x-sentry-rate-limits': '30:replay_recording',
87+
'retry-after': null,
88+
},
89+
},
90+
{
91+
statusCode: 429,
92+
headers: {
93+
'x-sentry-rate-limits': null,
94+
'retry-after': '30',
95+
},
96+
},
97+
] as TransportMakeRequestResponse[])(
98+
'pauses recording and flushing a rate limit is hit and resumes both after the rate limit duration is over',
99+
async rateLimitResponse => {
100+
expect(replay.session?.segmentId).toBe(0);
101+
jest.spyOn(replay, 'sendReplay');
102+
jest.spyOn(replay, 'pause');
103+
jest.spyOn(replay, 'resume');
104+
// @ts-ignore private API
105+
jest.spyOn(replay, '_handleRateLimit');
106+
107+
const TEST_EVENT = { data: {}, timestamp: BASE_TIMESTAMP, type: 2 };
108+
109+
mockTransportSend.mockImplementationOnce(() => {
110+
return Promise.resolve(rateLimitResponse);
111+
});
112+
113+
mockRecord._emitter(TEST_EVENT);
114+
115+
// T = base + 5
113116
await advanceTimers(DEFAULT_FLUSH_MIN_DELAY);
114-
expect(replay.isPaused()).toBe(true);
115-
expect(replay.sendReplay).toHaveBeenCalledTimes(1);
117+
118+
expect(mockRecord.takeFullSnapshot).not.toHaveBeenCalled();
116119
expect(mockTransportSend).toHaveBeenCalledTimes(1);
117-
}
118-
119-
// T = base + 35
120-
await advanceTimers(DEFAULT_FLUSH_MIN_DELAY);
121-
122-
// now, recording should resume and first, we expect a checkout event to be sent, as resume()
123-
// should trigger a full snapshot
124-
expect(replay.resume).toHaveBeenCalledTimes(1);
125-
expect(replay.isPaused()).toBe(false);
126-
127-
expect(replay.sendReplay).toHaveBeenCalledTimes(2);
128-
expect(replay).toHaveLastSentReplay({
129-
events: '[{"data":{"isCheckout":true},"timestamp":1580598035000,"type":2}]',
130-
});
131-
132-
// and let's also emit a new event and check that it is recorded
133-
const TEST_EVENT3 = {
134-
data: {},
135-
timestamp: BASE_TIMESTAMP + 7 * DEFAULT_FLUSH_MIN_DELAY,
136-
type: 3,
137-
};
138-
mockRecord._emitter(TEST_EVENT3);
139-
140-
// T = base + 40
141-
await advanceTimers(DEFAULT_FLUSH_MIN_DELAY);
142-
expect(replay.sendReplay).toHaveBeenCalledTimes(3);
143-
expect(replay).toHaveLastSentReplay({ events: JSON.stringify([TEST_EVENT3]) });
144-
145-
// nothing should happen afterwards
146-
// T = base + 60
147-
await advanceTimers(20_000);
148-
expect(replay.sendReplay).toHaveBeenCalledTimes(3);
149-
expect(replay).toHaveLastSentReplay({ events: JSON.stringify([TEST_EVENT3]) });
150-
151-
// events array should be empty
152-
expect(replay.eventBuffer?.length).toBe(0);
153-
});
120+
expect(replay).toHaveLastSentReplay({ events: JSON.stringify([TEST_EVENT]) });
121+
122+
expect(replay['_handleRateLimit']).toHaveBeenCalledTimes(1);
123+
// resume() was called once before we even started
124+
expect(replay.resume).not.toHaveBeenCalled();
125+
expect(replay.pause).toHaveBeenCalledTimes(1);
126+
127+
// No user activity to trigger an update
128+
expect(replay.session?.lastActivity).toBe(BASE_TIMESTAMP);
129+
expect(replay.session?.segmentId).toBe(1);
130+
131+
// let's simulate the rate-limit time of inactivity (30secs) and check that we don't do anything in the meantime
132+
const TEST_EVENT2 = { data: {}, timestamp: BASE_TIMESTAMP + DEFAULT_FLUSH_MIN_DELAY, type: 3 };
133+
for (let i = 0; i < 5; i++) {
134+
const ev = {
135+
...TEST_EVENT2,
136+
timestamp: BASE_TIMESTAMP + DEFAULT_FLUSH_MIN_DELAY * (i + 1),
137+
};
138+
mockRecord._emitter(ev);
139+
await advanceTimers(DEFAULT_FLUSH_MIN_DELAY);
140+
expect(replay.isPaused()).toBe(true);
141+
expect(replay.sendReplay).toHaveBeenCalledTimes(1);
142+
expect(mockTransportSend).toHaveBeenCalledTimes(1);
143+
}
144+
145+
// T = base + 35
146+
await advanceTimers(DEFAULT_FLUSH_MIN_DELAY);
147+
148+
// now, recording should resume and first, we expect a checkout event to be sent, as resume()
149+
// should trigger a full snapshot
150+
expect(replay.resume).toHaveBeenCalledTimes(1);
151+
expect(replay.isPaused()).toBe(false);
152+
153+
expect(replay.sendReplay).toHaveBeenCalledTimes(2);
154+
expect(replay).toHaveLastSentReplay({
155+
events: '[{"data":{"isCheckout":true},"timestamp":1580598035000,"type":2}]',
156+
});
157+
158+
// and let's also emit a new event and check that it is recorded
159+
const TEST_EVENT3 = {
160+
data: {},
161+
timestamp: BASE_TIMESTAMP + 7 * DEFAULT_FLUSH_MIN_DELAY,
162+
type: 3,
163+
};
164+
mockRecord._emitter(TEST_EVENT3);
165+
166+
// T = base + 40
167+
await advanceTimers(DEFAULT_FLUSH_MIN_DELAY);
168+
expect(replay.sendReplay).toHaveBeenCalledTimes(3);
169+
expect(replay).toHaveLastSentReplay({ events: JSON.stringify([TEST_EVENT3]) });
170+
171+
// nothing should happen afterwards
172+
// T = base + 60
173+
await advanceTimers(20_000);
174+
expect(replay.sendReplay).toHaveBeenCalledTimes(3);
175+
expect(replay).toHaveLastSentReplay({ events: JSON.stringify([TEST_EVENT3]) });
176+
177+
// events array should be empty
178+
expect(replay.eventBuffer?.length).toBe(0);
179+
},
180+
);
154181
});

0 commit comments

Comments
 (0)