11import { createTransport } from '@sentry/core' ;
22import { EventEnvelope , EventItem } from '@sentry/types' ;
3- import { createEnvelope , serializeEnvelope } from '@sentry/utils' ;
3+ import { addItemToEnvelope , createAttachmentEnvelopeItem , createEnvelope , serializeEnvelope } from '@sentry/utils' ;
44import * as http from 'http' ;
55import { TextEncoder } from 'util' ;
6+ import { createGunzip } from 'zlib' ;
67
78import { makeNodeTransport } from '../../src/transports' ;
89
@@ -34,17 +35,19 @@ let testServer: http.Server | undefined;
3435
3536function setupTestServer (
3637 options : TestServerOptions ,
37- requestInspector ?: ( req : http . IncomingMessage , body : string ) => void ,
38+ requestInspector ?: ( req : http . IncomingMessage , body : string , raw : Uint8Array ) => void ,
3839) {
3940 testServer = http . createServer ( ( req , res ) => {
40- let body = '' ;
41+ const chunks : Buffer [ ] = [ ] ;
4142
42- req . on ( 'data' , data => {
43- body += data ;
43+ const stream = req . headers [ 'content-encoding' ] === 'gzip' ? req . pipe ( createGunzip ( { } ) ) : req ;
44+
45+ stream . on ( 'data' , data => {
46+ chunks . push ( data ) ;
4447 } ) ;
4548
46- req . on ( 'end' , ( ) => {
47- requestInspector ?.( req , body ) ;
49+ stream . on ( 'end' , ( ) => {
50+ requestInspector ?.( req , chunks . join ( ) , Buffer . concat ( chunks ) ) ;
4851 } ) ;
4952
5053 res . writeHead ( options . statusCode , options . responseHeaders ) ;
@@ -69,6 +72,16 @@ const EVENT_ENVELOPE = createEnvelope<EventEnvelope>({ event_id: 'aa3ff046696b4b
6972
7073const SERIALIZED_EVENT_ENVELOPE = serializeEnvelope ( EVENT_ENVELOPE , new TextEncoder ( ) ) ;
7174
75+ const ATTACHMENT_ITEM = createAttachmentEnvelopeItem (
76+ { filename : 'empty-file.bin' , data : new Uint8Array ( 50_000 ) } ,
77+ new TextEncoder ( ) ,
78+ ) ;
79+ const EVENT_ATTACHMENT_ENVELOPE = addItemToEnvelope ( EVENT_ENVELOPE , ATTACHMENT_ITEM ) ;
80+ const SERIALIZED_EVENT_ATTACHMENT_ENVELOPE = serializeEnvelope (
81+ EVENT_ATTACHMENT_ENVELOPE ,
82+ new TextEncoder ( ) ,
83+ ) as Uint8Array ;
84+
7285const defaultOptions = {
7386 url : TEST_SERVER_URL ,
7487 recordDroppedEvent : ( ) => undefined ,
@@ -155,6 +168,40 @@ describe('makeNewHttpTransport()', () => {
155168 } ) ;
156169 } ) ;
157170
171+ describe ( 'compression' , ( ) => {
172+ it ( 'small envelopes should not be compressed' , async ( ) => {
173+ await setupTestServer (
174+ {
175+ statusCode : SUCCESS ,
176+ responseHeaders : { } ,
177+ } ,
178+ ( req , body ) => {
179+ expect ( req . headers [ 'content-encoding' ] ) . toBeUndefined ( ) ;
180+ expect ( body ) . toBe ( SERIALIZED_EVENT_ENVELOPE ) ;
181+ } ,
182+ ) ;
183+
184+ const transport = makeNodeTransport ( defaultOptions ) ;
185+ await transport . send ( EVENT_ENVELOPE ) ;
186+ } ) ;
187+
188+ it ( 'large envelopes should be compressed' , async ( ) => {
189+ await setupTestServer (
190+ {
191+ statusCode : SUCCESS ,
192+ responseHeaders : { } ,
193+ } ,
194+ ( req , _ , raw ) => {
195+ expect ( req . headers [ 'content-encoding' ] ) . toEqual ( 'gzip' ) ;
196+ expect ( raw . buffer ) . toStrictEqual ( SERIALIZED_EVENT_ATTACHMENT_ENVELOPE . buffer ) ;
197+ } ,
198+ ) ;
199+
200+ const transport = makeNodeTransport ( defaultOptions ) ;
201+ await transport . send ( EVENT_ATTACHMENT_ENVELOPE ) ;
202+ } ) ;
203+ } ) ;
204+
158205 describe ( 'proxy' , ( ) => {
159206 it ( 'can be configured through option' , ( ) => {
160207 makeNodeTransport ( {
@@ -236,104 +283,106 @@ describe('makeNewHttpTransport()', () => {
236283 } ) ;
237284 } ) ;
238285
239- it ( 'should register TransportRequestExecutor that returns the correct object from server response (rate limit)' , async ( ) => {
240- await setupTestServer ( {
241- statusCode : RATE_LIMIT ,
242- responseHeaders : { } ,
243- } ) ;
244-
245- makeNodeTransport ( defaultOptions ) ;
246- const registeredRequestExecutor = ( createTransport as jest . Mock ) . mock . calls [ 0 ] [ 1 ] ;
247-
248- const executorResult = registeredRequestExecutor ( {
249- body : serializeEnvelope ( EVENT_ENVELOPE , new TextEncoder ( ) ) ,
250- category : 'error' ,
251- } ) ;
252-
253- await expect ( executorResult ) . resolves . toEqual (
254- expect . objectContaining ( {
286+ describe ( 'should register TransportRequestExecutor that returns the correct object from server response' , ( ) => {
287+ it ( 'rate limit' , async ( ) => {
288+ await setupTestServer ( {
255289 statusCode : RATE_LIMIT ,
256- } ) ,
257- ) ;
258- } ) ;
290+ responseHeaders : { } ,
291+ } ) ;
259292
260- it ( 'should register TransportRequestExecutor that returns the correct object from server response (OK)' , async ( ) => {
261- await setupTestServer ( {
262- statusCode : SUCCESS ,
263- } ) ;
293+ makeNodeTransport ( defaultOptions ) ;
294+ const registeredRequestExecutor = ( createTransport as jest . Mock ) . mock . calls [ 0 ] [ 1 ] ;
264295
265- makeNodeTransport ( defaultOptions ) ;
266- const registeredRequestExecutor = ( createTransport as jest . Mock ) . mock . calls [ 0 ] [ 1 ] ;
296+ const executorResult = registeredRequestExecutor ( {
297+ body : serializeEnvelope ( EVENT_ENVELOPE , new TextEncoder ( ) ) ,
298+ category : 'error' ,
299+ } ) ;
267300
268- const executorResult = registeredRequestExecutor ( {
269- body : serializeEnvelope ( EVENT_ENVELOPE , new TextEncoder ( ) ) ,
270- category : 'error' ,
301+ await expect ( executorResult ) . resolves . toEqual (
302+ expect . objectContaining ( {
303+ statusCode : RATE_LIMIT ,
304+ } ) ,
305+ ) ;
271306 } ) ;
272307
273- await expect ( executorResult ) . resolves . toEqual (
274- expect . objectContaining ( {
308+ it ( 'OK' , async ( ) => {
309+ await setupTestServer ( {
275310 statusCode : SUCCESS ,
276- headers : {
277- 'retry-after' : null ,
278- 'x-sentry-rate-limits' : null ,
279- } ,
280- } ) ,
281- ) ;
282- } ) ;
311+ } ) ;
283312
284- it ( 'should register TransportRequestExecutor that returns the correct object from server response (OK with rate-limit headers)' , async ( ) => {
285- await setupTestServer ( {
286- statusCode : SUCCESS ,
287- responseHeaders : {
288- 'Retry-After' : '2700' ,
289- 'X-Sentry-Rate-Limits' : '60::organization, 2700::organization' ,
290- } ,
291- } ) ;
313+ makeNodeTransport ( defaultOptions ) ;
314+ const registeredRequestExecutor = ( createTransport as jest . Mock ) . mock . calls [ 0 ] [ 1 ] ;
292315
293- makeNodeTransport ( defaultOptions ) ;
294- const registeredRequestExecutor = ( createTransport as jest . Mock ) . mock . calls [ 0 ] [ 1 ] ;
316+ const executorResult = registeredRequestExecutor ( {
317+ body : serializeEnvelope ( EVENT_ENVELOPE , new TextEncoder ( ) ) ,
318+ category : 'error' ,
319+ } ) ;
295320
296- const executorResult = registeredRequestExecutor ( {
297- body : serializeEnvelope ( EVENT_ENVELOPE , new TextEncoder ( ) ) ,
298- category : 'error' ,
321+ await expect ( executorResult ) . resolves . toEqual (
322+ expect . objectContaining ( {
323+ statusCode : SUCCESS ,
324+ headers : {
325+ 'retry-after' : null ,
326+ 'x-sentry-rate-limits' : null ,
327+ } ,
328+ } ) ,
329+ ) ;
299330 } ) ;
300331
301- await expect ( executorResult ) . resolves . toEqual (
302- expect . objectContaining ( {
332+ it ( 'OK with rate-limit headers' , async ( ) => {
333+ await setupTestServer ( {
303334 statusCode : SUCCESS ,
304- headers : {
305- 'retry-after ' : '2700' ,
306- 'x-sentry-rate-limits ' : '60::organization, 2700::organization' ,
335+ responseHeaders : {
336+ 'Retry-After ' : '2700' ,
337+ 'X-Sentry-Rate-Limits ' : '60::organization, 2700::organization' ,
307338 } ,
308- } ) ,
309- ) ;
310- } ) ;
339+ } ) ;
311340
312- it ( 'should register TransportRequestExecutor that returns the correct object from server response (NOK with rate-limit headers)' , async ( ) => {
313- await setupTestServer ( {
314- statusCode : RATE_LIMIT ,
315- responseHeaders : {
316- 'Retry-After' : '2700' ,
317- 'X-Sentry-Rate-Limits' : '60::organization, 2700::organization' ,
318- } ,
319- } ) ;
341+ makeNodeTransport ( defaultOptions ) ;
342+ const registeredRequestExecutor = ( createTransport as jest . Mock ) . mock . calls [ 0 ] [ 1 ] ;
320343
321- makeNodeTransport ( defaultOptions ) ;
322- const registeredRequestExecutor = ( createTransport as jest . Mock ) . mock . calls [ 0 ] [ 1 ] ;
344+ const executorResult = registeredRequestExecutor ( {
345+ body : serializeEnvelope ( EVENT_ENVELOPE , new TextEncoder ( ) ) ,
346+ category : 'error' ,
347+ } ) ;
323348
324- const executorResult = registeredRequestExecutor ( {
325- body : serializeEnvelope ( EVENT_ENVELOPE , new TextEncoder ( ) ) ,
326- category : 'error' ,
349+ await expect ( executorResult ) . resolves . toEqual (
350+ expect . objectContaining ( {
351+ statusCode : SUCCESS ,
352+ headers : {
353+ 'retry-after' : '2700' ,
354+ 'x-sentry-rate-limits' : '60::organization, 2700::organization' ,
355+ } ,
356+ } ) ,
357+ ) ;
327358 } ) ;
328359
329- await expect ( executorResult ) . resolves . toEqual (
330- expect . objectContaining ( {
360+ it ( 'NOK with rate-limit headers' , async ( ) => {
361+ await setupTestServer ( {
331362 statusCode : RATE_LIMIT ,
332- headers : {
333- 'retry-after ' : '2700' ,
334- 'x-sentry-rate-limits ' : '60::organization, 2700::organization' ,
363+ responseHeaders : {
364+ 'Retry-After ' : '2700' ,
365+ 'X-Sentry-Rate-Limits ' : '60::organization, 2700::organization' ,
335366 } ,
336- } ) ,
337- ) ;
367+ } ) ;
368+
369+ makeNodeTransport ( defaultOptions ) ;
370+ const registeredRequestExecutor = ( createTransport as jest . Mock ) . mock . calls [ 0 ] [ 1 ] ;
371+
372+ const executorResult = registeredRequestExecutor ( {
373+ body : serializeEnvelope ( EVENT_ENVELOPE , new TextEncoder ( ) ) ,
374+ category : 'error' ,
375+ } ) ;
376+
377+ await expect ( executorResult ) . resolves . toEqual (
378+ expect . objectContaining ( {
379+ statusCode : RATE_LIMIT ,
380+ headers : {
381+ 'retry-after' : '2700' ,
382+ 'x-sentry-rate-limits' : '60::organization, 2700::organization' ,
383+ } ,
384+ } ) ,
385+ ) ;
386+ } ) ;
338387 } ) ;
339388} ) ;
0 commit comments