|
1 | 1 | import { getCurrentHub, Hub } from '@sentry/core';
|
2 |
| -import { Event, Scope } from '@sentry/types'; |
| 2 | +import { Event, Scope, TransportMakeRequestResponse } from '@sentry/types'; |
3 | 3 | import { EventType } from 'rrweb';
|
4 | 4 |
|
5 | 5 | import {
|
@@ -874,6 +874,96 @@ describe('Replay', () => {
|
874 | 874 | await advanceTimers(DEFAULT_FLUSH_MIN_DELAY);
|
875 | 875 | expect(replay.flush).toHaveBeenCalledTimes(1);
|
876 | 876 | });
|
| 877 | + |
| 878 | + 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); |
| 903 | + |
| 904 | + expect(mockRecord.takeFullSnapshot).not.toHaveBeenCalled(); |
| 905 | + expect(mockTransportSend).toHaveBeenCalledTimes(1); |
| 906 | + expect(replay).toHaveLastSentReplay({ events: JSON.stringify([TEST_EVENT]) }); |
| 907 | + |
| 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); |
| 912 | + |
| 913 | + // No user activity to trigger an update |
| 914 | + expect(replay.session?.lastActivity).toBe(BASE_TIMESTAMP); |
| 915 | + expect(replay.session?.segmentId).toBe(1); |
| 916 | + |
| 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); |
| 925 | + await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); |
| 926 | + expect(replay.isPaused()).toBe(true); |
| 927 | + expect(replay.sendReplay).toHaveBeenCalledTimes(1); |
| 928 | + expect(mockTransportSend).toHaveBeenCalledTimes(1); |
| 929 | + } |
| 930 | + |
| 931 | + // T = base + 35 |
| 932 | + await advanceTimers(DEFAULT_FLUSH_MIN_DELAY); |
| 933 | + |
| 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); |
| 938 | + |
| 939 | + expect(replay.sendReplay).toHaveBeenCalledTimes(2); |
| 940 | + expect(replay).toHaveLastSentReplay({ |
| 941 | + events: '[{"data":{"isCheckout":true},"timestamp":1580598035000,"type":2}]', |
| 942 | + }); |
| 943 | + |
| 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 | + }); |
| 966 | + }); |
877 | 967 | });
|
878 | 968 |
|
879 | 969 | describe('eventProcessors', () => {
|
|
0 commit comments