11import { getCurrentHub } from '@sentry/core' ;
2- import type { Transport , TransportMakeRequestResponse } from '@sentry/types' ;
2+ import type { Transport } from '@sentry/types' ;
33
44import { DEFAULT_FLUSH_MIN_DELAY , SESSION_IDLE_DURATION } from '../../src/constants' ;
55import type { ReplayContainer } from '../../src/replay' ;
@@ -64,120 +64,9 @@ describe('Integration | rate-limiting behaviour', () => {
6464 replay && replay . stop ( ) ;
6565 } ) ;
6666
67- it . each ( [
68- {
69- statusCode : 429 ,
70- headers : {
71- 'x-sentry-rate-limits' : '30' ,
72- 'retry-after' : null ,
73- } ,
74- } ,
75- {
76- statusCode : 429 ,
77- headers : {
78- 'x-sentry-rate-limits' : '30:replay' ,
79- 'retry-after' : null ,
80- } ,
81- } ,
82- {
83- statusCode : 429 ,
84- headers : {
85- 'x-sentry-rate-limits' : null ,
86- 'retry-after' : '30' ,
87- } ,
88- } ,
89- ] as TransportMakeRequestResponse [ ] ) (
90- 'pauses recording and flushing a rate limit is hit and resumes both after the rate limit duration is over %j' ,
91- async rateLimitResponse => {
92- expect ( replay . session ?. segmentId ) . toBe ( 0 ) ;
93- jest . spyOn ( replay , 'pause' ) ;
94- jest . spyOn ( replay , 'resume' ) ;
95- // @ts -ignore private API
96- jest . spyOn ( replay , '_handleRateLimit' ) ;
97-
98- const TEST_EVENT = { data : { } , timestamp : BASE_TIMESTAMP , type : 2 } ;
99-
100- mockTransportSend . mockImplementationOnce ( ( ) => {
101- return Promise . resolve ( rateLimitResponse ) ;
102- } ) ;
103-
104- mockRecord . _emitter ( TEST_EVENT ) ;
105-
106- // T = base + 5
107- await advanceTimers ( DEFAULT_FLUSH_MIN_DELAY ) ;
108-
109- expect ( mockRecord . takeFullSnapshot ) . not . toHaveBeenCalled ( ) ;
110- expect ( mockTransportSend ) . toHaveBeenCalledTimes ( 1 ) ;
111- expect ( replay ) . toHaveLastSentReplay ( { recordingData : JSON . stringify ( [ TEST_EVENT ] ) } ) ;
112-
113- expect ( replay [ '_handleRateLimit' ] ) . toHaveBeenCalledTimes ( 1 ) ;
114- // resume() was called once before we even started
115- expect ( replay . resume ) . not . toHaveBeenCalled ( ) ;
116- expect ( replay . pause ) . toHaveBeenCalledTimes ( 1 ) ;
117-
118- // No user activity to trigger an update
119- expect ( replay . session ?. lastActivity ) . toBe ( BASE_TIMESTAMP ) ;
120- expect ( replay . session ?. segmentId ) . toBe ( 1 ) ;
121-
122- // let's simulate the rate-limit time of inactivity (30secs) and check that we don't do anything in the meantime
123- const TEST_EVENT2 = { data : { } , timestamp : BASE_TIMESTAMP + DEFAULT_FLUSH_MIN_DELAY , type : 3 } ;
124- for ( let i = 0 ; i < 5 ; i ++ ) {
125- const ev = {
126- ...TEST_EVENT2 ,
127- timestamp : BASE_TIMESTAMP + DEFAULT_FLUSH_MIN_DELAY * ( i + 1 ) ,
128- } ;
129- mockRecord . _emitter ( ev ) ;
130- await advanceTimers ( DEFAULT_FLUSH_MIN_DELAY ) ;
131- expect ( replay . isPaused ( ) ) . toBe ( true ) ;
132- expect ( mockSendReplayRequest ) . toHaveBeenCalledTimes ( 1 ) ;
133- expect ( mockTransportSend ) . toHaveBeenCalledTimes ( 1 ) ;
134- }
135-
136- // T = base + 35
137- await advanceTimers ( DEFAULT_FLUSH_MIN_DELAY ) ;
138-
139- // now, recording should resume and first, we expect a checkout event to be sent, as resume()
140- // should trigger a full snapshot
141- expect ( replay . resume ) . toHaveBeenCalledTimes ( 1 ) ;
142- expect ( replay . isPaused ( ) ) . toBe ( false ) ;
143-
144- expect ( mockSendReplayRequest ) . toHaveBeenCalledTimes ( 2 ) ;
145- expect ( replay ) . toHaveLastSentReplay ( {
146- recordingData : JSON . stringify ( [
147- { data : { isCheckout : true } , timestamp : BASE_TIMESTAMP + DEFAULT_FLUSH_MIN_DELAY * 7 , type : 2 } ,
148- ] ) ,
149- } ) ;
150-
151- // and let's also emit a new event and check that it is recorded
152- const TEST_EVENT3 = {
153- data : { } ,
154- timestamp : BASE_TIMESTAMP + 7 * DEFAULT_FLUSH_MIN_DELAY ,
155- type : 3 ,
156- } ;
157- mockRecord . _emitter ( TEST_EVENT3 ) ;
158-
159- // T = base + 40
160- await advanceTimers ( DEFAULT_FLUSH_MIN_DELAY ) ;
161- expect ( mockSendReplayRequest ) . toHaveBeenCalledTimes ( 3 ) ;
162- expect ( replay ) . toHaveLastSentReplay ( { recordingData : JSON . stringify ( [ TEST_EVENT3 ] ) } ) ;
163-
164- // nothing should happen afterwards
165- // T = base + 60
166- await advanceTimers ( 20_000 ) ;
167- expect ( mockSendReplayRequest ) . toHaveBeenCalledTimes ( 3 ) ;
168- expect ( replay ) . toHaveLastSentReplay ( { recordingData : JSON . stringify ( [ TEST_EVENT3 ] ) } ) ;
169-
170- // events array should be empty
171- expect ( replay . eventBuffer ?. hasEvents ) . toBe ( false ) ;
172- } ,
173- ) ;
174-
175- it ( 'handles rate-limits from a plain 429 response without any retry time' , async ( ) => {
67+ it ( 'handles rate-limit 429 responses by stopping the replay' , async ( ) => {
17668 expect ( replay . session ?. segmentId ) . toBe ( 0 ) ;
177- jest . spyOn ( replay , 'pause' ) ;
178- jest . spyOn ( replay , 'resume' ) ;
179- // @ts -ignore private API
180- jest . spyOn ( replay , '_handleRateLimit' ) ;
69+ jest . spyOn ( replay , 'stop' ) ;
18170
18271 const TEST_EVENT = { data : { } , timestamp : BASE_TIMESTAMP , type : 2 } ;
18372
@@ -194,99 +83,33 @@ describe('Integration | rate-limiting behaviour', () => {
19483 expect ( mockTransportSend ) . toHaveBeenCalledTimes ( 1 ) ;
19584 expect ( replay ) . toHaveLastSentReplay ( { recordingData : JSON . stringify ( [ TEST_EVENT ] ) } ) ;
19685
197- expect ( replay [ '_handleRateLimit' ] ) . toHaveBeenCalledTimes ( 1 ) ;
198- // resume() was called once before we even started
199- expect ( replay . resume ) . not . toHaveBeenCalled ( ) ;
200- expect ( replay . pause ) . toHaveBeenCalledTimes ( 1 ) ;
86+ expect ( replay . stop ) . toHaveBeenCalledTimes ( 1 ) ;
20187
20288 // No user activity to trigger an update
20389 expect ( replay . session ?. lastActivity ) . toBe ( BASE_TIMESTAMP ) ;
20490 expect ( replay . session ?. segmentId ) . toBe ( 1 ) ;
20591
206- // let's simulate the rate-limit time of inactivity (60secs) and check that we don't do anything in the meantime
92+ // let's simulate the default rate-limit time of inactivity (60secs) and check that we
93+ // don't do anything in the meantime or after the time has passed
20794 // 60secs are the default we fall back to in the plain 429 case in updateRateLimits()
208- const TEST_EVENT2 = { data : { } , timestamp : BASE_TIMESTAMP + DEFAULT_FLUSH_MIN_DELAY , type : 3 } ;
209- for ( let i = 0 ; i < 11 ; i ++ ) {
210- const ev = {
211- ...TEST_EVENT2 ,
212- timestamp : BASE_TIMESTAMP + DEFAULT_FLUSH_MIN_DELAY * ( i + 1 ) ,
213- } ;
214- mockRecord . _emitter ( ev ) ;
215- await advanceTimers ( DEFAULT_FLUSH_MIN_DELAY ) ;
216- expect ( replay . isPaused ( ) ) . toBe ( true ) ;
217- expect ( mockSendReplayRequest ) . toHaveBeenCalledTimes ( 1 ) ;
218- expect ( mockTransportSend ) . toHaveBeenCalledTimes ( 1 ) ;
219- }
22095
22196 // T = base + 60
222- await advanceTimers ( DEFAULT_FLUSH_MIN_DELAY ) ;
223-
224- // now, recording should resume and first, we expect a checkout event to be sent, as resume()
225- // should trigger a full snapshot
226- expect ( replay . resume ) . toHaveBeenCalledTimes ( 1 ) ;
227- expect ( replay . isPaused ( ) ) . toBe ( false ) ;
97+ await advanceTimers ( DEFAULT_FLUSH_MIN_DELAY * 12 ) ;
22898
229- expect ( mockSendReplayRequest ) . toHaveBeenCalledTimes ( 2 ) ;
230- expect ( replay ) . toHaveLastSentReplay ( {
231- recordingData : JSON . stringify ( [
232- { data : { isCheckout : true } , timestamp : BASE_TIMESTAMP + DEFAULT_FLUSH_MIN_DELAY * 13 , type : 2 } ,
233- ] ) ,
234- } ) ;
99+ expect ( mockSendReplayRequest ) . toHaveBeenCalledTimes ( 1 ) ;
100+ expect ( mockTransportSend ) . toHaveBeenCalledTimes ( 1 ) ;
235101
236- // and let's also emit a new event and check that it is recorded
102+ // and let's also emit a new event and check that it is not recorded
237103 const TEST_EVENT3 = {
238104 data : { } ,
239105 timestamp : BASE_TIMESTAMP + 7 * DEFAULT_FLUSH_MIN_DELAY ,
240106 type : 3 ,
241107 } ;
242108 mockRecord . _emitter ( TEST_EVENT3 ) ;
243109
244- // T = base + 65
245- await advanceTimers ( DEFAULT_FLUSH_MIN_DELAY ) ;
246- expect ( mockSendReplayRequest ) . toHaveBeenCalledTimes ( 3 ) ;
247- expect ( replay ) . toHaveLastSentReplay ( { recordingData : JSON . stringify ( [ TEST_EVENT3 ] ) } ) ;
248-
249- // nothing should happen afterwards
250- // T = base + 85
110+ // T = base + 80
251111 await advanceTimers ( 20_000 ) ;
252- expect ( mockSendReplayRequest ) . toHaveBeenCalledTimes ( 3 ) ;
253- expect ( replay ) . toHaveLastSentReplay ( { recordingData : JSON . stringify ( [ TEST_EVENT3 ] ) } ) ;
254-
255- // events array should be empty
256- expect ( replay . eventBuffer ?. hasEvents ) . toBe ( false ) ;
257- } ) ;
258-
259- it ( "doesn't do anything, if a rate limit is hit and recording is already paused" , async ( ) => {
260- let paused = false ;
261- expect ( replay . session ?. segmentId ) . toBe ( 0 ) ;
262- jest . spyOn ( replay , 'isPaused' ) . mockImplementation ( ( ) => {
263- return paused ;
264- } ) ;
265- jest . spyOn ( replay , 'pause' ) ;
266- jest . spyOn ( replay , 'resume' ) ;
267- // @ts -ignore private API
268- jest . spyOn ( replay , '_handleRateLimit' ) ;
269-
270- const TEST_EVENT = { data : { } , timestamp : BASE_TIMESTAMP , type : 2 } ;
271-
272- mockTransportSend . mockImplementationOnce ( ( ) => {
273- return Promise . resolve ( { statusCode : 429 } ) ;
274- } ) ;
275-
276- mockRecord . _emitter ( TEST_EVENT ) ;
277- paused = true ;
278-
279- // T = base + 5
280- await advanceTimers ( DEFAULT_FLUSH_MIN_DELAY ) ;
281-
282- expect ( mockRecord . takeFullSnapshot ) . not . toHaveBeenCalled ( ) ;
112+ expect ( mockSendReplayRequest ) . toHaveBeenCalledTimes ( 1 ) ;
283113 expect ( mockTransportSend ) . toHaveBeenCalledTimes ( 1 ) ;
284-
285- expect ( replay ) . toHaveLastSentReplay ( { recordingData : JSON . stringify ( [ TEST_EVENT ] ) } ) ;
286-
287- expect ( replay [ '_handleRateLimit' ] ) . toHaveBeenCalledTimes ( 1 ) ;
288- expect ( replay . resume ) . not . toHaveBeenCalled ( ) ;
289- expect ( replay . isPaused ) . toHaveBeenCalledTimes ( 2 ) ;
290- expect ( replay . pause ) . not . toHaveBeenCalled ( ) ;
291114 } ) ;
292115} ) ;
0 commit comments