Skip to content

Commit 8ea1580

Browse files
committed
add tests for multiple rate limit headers
1 parent b13e86d commit 8ea1580

File tree

1 file changed

+103
-77
lines changed

1 file changed

+103
-77
lines changed

packages/replay/test/unit/index.test.ts

Lines changed: 103 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -876,93 +876,119 @@ describe('Replay', () => {
876876
});
877877

878878
describe('rate-limiting behaviour', () => {
879-
it('pauses recording and flushing a rate limit is hit and resumes both after the rate limit duration is over', async () => {
880-
expect(replay.session?.segmentId).toBe(0);
881-
jest.spyOn(replay, 'sendReplay');
882-
jest.spyOn(replay, 'pause');
883-
jest.spyOn(replay, 'resume');
884-
// @ts-ignore private API
885-
jest.spyOn(replay, '_handleRateLimit');
886-
887-
const TEST_EVENT = { data: {}, timestamp: BASE_TIMESTAMP, type: 2 };
888-
889-
mockTransportSend.mockImplementationOnce(() => {
890-
return Promise.resolve({
891-
statusCode: 429,
892-
headers: {
893-
'x-sentry-rate-limits': null,
894-
'retry-after': `30`,
895-
},
896-
} as TransportMakeRequestResponse);
897-
});
898-
899-
mockRecord._emitter(TEST_EVENT);
900-
901-
// T = base + 5
902-
await advanceTimers(DEFAULT_FLUSH_MIN_DELAY);
879+
it.each([
880+
{
881+
statusCode: 429,
882+
headers: {
883+
'x-sentry-rate-limits': '30',
884+
'retry-after': null,
885+
},
886+
},
887+
{
888+
statusCode: 429,
889+
headers: {
890+
'x-sentry-rate-limits': '30:replay_event',
891+
'retry-after': null,
892+
},
893+
},
894+
{
895+
statusCode: 429,
896+
headers: {
897+
'x-sentry-rate-limits': '30:replay_recording',
898+
'retry-after': null,
899+
},
900+
},
901+
{
902+
statusCode: 429,
903+
headers: {
904+
'x-sentry-rate-limits': null,
905+
'retry-after': '30',
906+
},
907+
},
908+
] as TransportMakeRequestResponse[])(
909+
'pauses recording and flushing a rate limit is hit and resumes both after the rate limit duration is over',
910+
async ratelimitResponse => {
911+
expect(replay.session?.segmentId).toBe(0);
912+
jest.spyOn(replay, 'sendReplay');
913+
jest.spyOn(replay, 'pause');
914+
jest.spyOn(replay, 'resume');
915+
// @ts-ignore private API
916+
jest.spyOn(replay, '_handleRateLimit');
903917

904-
expect(mockRecord.takeFullSnapshot).not.toHaveBeenCalled();
905-
expect(mockTransportSend).toHaveBeenCalledTimes(1);
906-
expect(replay).toHaveLastSentReplay({ events: JSON.stringify([TEST_EVENT]) });
918+
const TEST_EVENT = { data: {}, timestamp: BASE_TIMESTAMP, type: 2 };
907919

908-
expect(replay['_handleRateLimit']).toHaveBeenCalledTimes(1);
909-
// resume() was called once before we even started
910-
expect(replay.resume).not.toHaveBeenCalled();
911-
expect(replay.pause).toHaveBeenCalledTimes(1);
920+
mockTransportSend.mockImplementationOnce(() => {
921+
return Promise.resolve(ratelimitResponse);
922+
});
912923

913-
// No user activity to trigger an update
914-
expect(replay.session?.lastActivity).toBe(BASE_TIMESTAMP);
915-
expect(replay.session?.segmentId).toBe(1);
924+
mockRecord._emitter(TEST_EVENT);
916925

917-
// let's simulate the rate-limit time of inactivity (30secs) and check that we don't do anything in the meantime
918-
const TEST_EVENT2 = { data: {}, timestamp: BASE_TIMESTAMP + DEFAULT_FLUSH_MIN_DELAY, type: 3 };
919-
for (let i = 0; i < 5; i++) {
920-
const ev = {
921-
...TEST_EVENT2,
922-
timestamp: BASE_TIMESTAMP + DEFAULT_FLUSH_MIN_DELAY * (i + 1),
923-
};
924-
mockRecord._emitter(ev);
926+
// T = base + 5
925927
await advanceTimers(DEFAULT_FLUSH_MIN_DELAY);
926-
expect(replay.isPaused()).toBe(true);
927-
expect(replay.sendReplay).toHaveBeenCalledTimes(1);
928+
929+
expect(mockRecord.takeFullSnapshot).not.toHaveBeenCalled();
928930
expect(mockTransportSend).toHaveBeenCalledTimes(1);
929-
}
931+
expect(replay).toHaveLastSentReplay({ events: JSON.stringify([TEST_EVENT]) });
932+
933+
expect(replay['_handleRateLimit']).toHaveBeenCalledTimes(1);
934+
// resume() was called once before we even started
935+
expect(replay.resume).not.toHaveBeenCalled();
936+
expect(replay.pause).toHaveBeenCalledTimes(1);
937+
938+
// No user activity to trigger an update
939+
expect(replay.session?.lastActivity).toBe(BASE_TIMESTAMP);
940+
expect(replay.session?.segmentId).toBe(1);
941+
942+
// let's simulate the rate-limit time of inactivity (30secs) and check that we don't do anything in the meantime
943+
const TEST_EVENT2 = { data: {}, timestamp: BASE_TIMESTAMP + DEFAULT_FLUSH_MIN_DELAY, type: 3 };
944+
for (let i = 0; i < 5; i++) {
945+
const ev = {
946+
...TEST_EVENT2,
947+
timestamp: BASE_TIMESTAMP + DEFAULT_FLUSH_MIN_DELAY * (i + 1),
948+
};
949+
mockRecord._emitter(ev);
950+
await advanceTimers(DEFAULT_FLUSH_MIN_DELAY);
951+
expect(replay.isPaused()).toBe(true);
952+
expect(replay.sendReplay).toHaveBeenCalledTimes(1);
953+
expect(mockTransportSend).toHaveBeenCalledTimes(1);
954+
}
955+
956+
// T = base + 35
957+
await advanceTimers(DEFAULT_FLUSH_MIN_DELAY);
930958

931-
// T = base + 35
932-
await advanceTimers(DEFAULT_FLUSH_MIN_DELAY);
959+
// now, recording should resume and first, we expect a checkout event to be sent, as resume()
960+
// should trigger a full snapshot
961+
expect(replay.resume).toHaveBeenCalledTimes(1);
962+
expect(replay.isPaused()).toBe(false);
963+
964+
expect(replay.sendReplay).toHaveBeenCalledTimes(2);
965+
expect(replay).toHaveLastSentReplay({
966+
events: '[{"data":{"isCheckout":true},"timestamp":1580598035000,"type":2}]',
967+
});
968+
969+
// and let's also emit a new event and check that it is recorded
970+
const TEST_EVENT3 = {
971+
data: {},
972+
timestamp: BASE_TIMESTAMP + 7 * DEFAULT_FLUSH_MIN_DELAY,
973+
type: 3,
974+
};
975+
mockRecord._emitter(TEST_EVENT3);
933976

934-
// now, recording should resume and first, we expect a checkout event to be sent, as resume()
935-
// should trigger a full snapshot
936-
expect(replay.resume).toHaveBeenCalledTimes(1);
937-
expect(replay.isPaused()).toBe(false);
977+
// T = base + 40
978+
await advanceTimers(DEFAULT_FLUSH_MIN_DELAY);
979+
expect(replay.sendReplay).toHaveBeenCalledTimes(3);
980+
expect(replay).toHaveLastSentReplay({ events: JSON.stringify([TEST_EVENT3]) });
938981

939-
expect(replay.sendReplay).toHaveBeenCalledTimes(2);
940-
expect(replay).toHaveLastSentReplay({
941-
events: '[{"data":{"isCheckout":true},"timestamp":1580598035000,"type":2}]',
942-
});
982+
// nothing should happen afterwards
983+
// T = base + 60
984+
await advanceTimers(20_000);
985+
expect(replay.sendReplay).toHaveBeenCalledTimes(3);
986+
expect(replay).toHaveLastSentReplay({ events: JSON.stringify([TEST_EVENT3]) });
943987

944-
// and let's also emit a new event and check that it is recorded
945-
const TEST_EVENT3 = {
946-
data: {},
947-
timestamp: BASE_TIMESTAMP + 7 * DEFAULT_FLUSH_MIN_DELAY,
948-
type: 3,
949-
};
950-
mockRecord._emitter(TEST_EVENT3);
951-
952-
// T = base + 40
953-
await advanceTimers(DEFAULT_FLUSH_MIN_DELAY);
954-
expect(replay.sendReplay).toHaveBeenCalledTimes(3);
955-
expect(replay).toHaveLastSentReplay({ events: JSON.stringify([TEST_EVENT3]) });
956-
957-
// nothing should happen afterwards
958-
// T = base + 60
959-
await advanceTimers(20_000);
960-
expect(replay.sendReplay).toHaveBeenCalledTimes(3);
961-
expect(replay).toHaveLastSentReplay({ events: JSON.stringify([TEST_EVENT3]) });
962-
963-
// events array should be empty
964-
expect(replay.eventBuffer?.length).toBe(0);
965-
});
988+
// events array should be empty
989+
expect(replay.eventBuffer?.length).toBe(0);
990+
},
991+
);
966992
});
967993
});
968994

0 commit comments

Comments
 (0)