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,21 @@ 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 ( 'error' , ( ) => { } ) ;
46+
47+ stream . on ( 'data' , data => {
48+ chunks . push ( data ) ;
4449 } ) ;
4550
46- req . on ( 'end' , ( ) => {
47- requestInspector ?.( req , body ) ;
51+ stream . on ( 'end' , ( ) => {
52+ requestInspector ?.( req , chunks . join ( ) , Buffer . concat ( chunks ) ) ;
4853 } ) ;
4954
5055 res . writeHead ( options . statusCode , options . responseHeaders ) ;
@@ -69,6 +74,16 @@ const EVENT_ENVELOPE = createEnvelope<EventEnvelope>({ event_id: 'aa3ff046696b4b
6974
7075const SERIALIZED_EVENT_ENVELOPE = serializeEnvelope ( EVENT_ENVELOPE , new TextEncoder ( ) ) ;
7176
77+ const ATTACHMENT_ITEM = createAttachmentEnvelopeItem (
78+ { filename : 'empty-file.bin' , data : new Uint8Array ( 50_000 ) } ,
79+ new TextEncoder ( ) ,
80+ ) ;
81+ const EVENT_ATTACHMENT_ENVELOPE = addItemToEnvelope ( EVENT_ENVELOPE , ATTACHMENT_ITEM ) ;
82+ const SERIALIZED_EVENT_ATTACHMENT_ENVELOPE = serializeEnvelope (
83+ EVENT_ATTACHMENT_ENVELOPE ,
84+ new TextEncoder ( ) ,
85+ ) as Uint8Array ;
86+
7287const defaultOptions = {
7388 url : TEST_SERVER_URL ,
7489 recordDroppedEvent : ( ) => undefined ,
@@ -155,6 +170,40 @@ describe('makeNewHttpTransport()', () => {
155170 } ) ;
156171 } ) ;
157172
173+ describe ( 'compression' , ( ) => {
174+ it ( 'small envelopes should not be compressed' , async ( ) => {
175+ await setupTestServer (
176+ {
177+ statusCode : SUCCESS ,
178+ responseHeaders : { } ,
179+ } ,
180+ ( req , body ) => {
181+ expect ( req . headers [ 'content-encoding' ] ) . toBeUndefined ( ) ;
182+ expect ( body ) . toBe ( SERIALIZED_EVENT_ENVELOPE ) ;
183+ } ,
184+ ) ;
185+
186+ const transport = makeNodeTransport ( defaultOptions ) ;
187+ await transport . send ( EVENT_ENVELOPE ) ;
188+ } ) ;
189+
190+ it ( 'large envelopes should be compressed' , async ( ) => {
191+ await setupTestServer (
192+ {
193+ statusCode : SUCCESS ,
194+ responseHeaders : { } ,
195+ } ,
196+ ( req , _ , raw ) => {
197+ expect ( req . headers [ 'content-encoding' ] ) . toEqual ( 'gzip' ) ;
198+ expect ( raw . buffer ) . toStrictEqual ( SERIALIZED_EVENT_ATTACHMENT_ENVELOPE . buffer ) ;
199+ } ,
200+ ) ;
201+
202+ const transport = makeNodeTransport ( defaultOptions ) ;
203+ await transport . send ( EVENT_ATTACHMENT_ENVELOPE ) ;
204+ } ) ;
205+ } ) ;
206+
158207 describe ( 'proxy' , ( ) => {
159208 it ( 'can be configured through option' , ( ) => {
160209 makeNodeTransport ( {
@@ -236,104 +285,106 @@ describe('makeNewHttpTransport()', () => {
236285 } ) ;
237286 } ) ;
238287
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 ( {
288+ describe ( 'should register TransportRequestExecutor that returns the correct object from server response' , ( ) => {
289+ it ( 'rate limit' , async ( ) => {
290+ await setupTestServer ( {
255291 statusCode : RATE_LIMIT ,
256- } ) ,
257- ) ;
258- } ) ;
292+ responseHeaders : { } ,
293+ } ) ;
259294
260- it ( 'should register TransportRequestExecutor that returns the correct object from server response (OK)' , async ( ) => {
261- await setupTestServer ( {
262- statusCode : SUCCESS ,
263- } ) ;
295+ makeNodeTransport ( defaultOptions ) ;
296+ const registeredRequestExecutor = ( createTransport as jest . Mock ) . mock . calls [ 0 ] [ 1 ] ;
264297
265- makeNodeTransport ( defaultOptions ) ;
266- const registeredRequestExecutor = ( createTransport as jest . Mock ) . mock . calls [ 0 ] [ 1 ] ;
298+ const executorResult = registeredRequestExecutor ( {
299+ body : serializeEnvelope ( EVENT_ENVELOPE , new TextEncoder ( ) ) ,
300+ category : 'error' ,
301+ } ) ;
267302
268- const executorResult = registeredRequestExecutor ( {
269- body : serializeEnvelope ( EVENT_ENVELOPE , new TextEncoder ( ) ) ,
270- category : 'error' ,
303+ await expect ( executorResult ) . resolves . toEqual (
304+ expect . objectContaining ( {
305+ statusCode : RATE_LIMIT ,
306+ } ) ,
307+ ) ;
271308 } ) ;
272309
273- await expect ( executorResult ) . resolves . toEqual (
274- expect . objectContaining ( {
310+ it ( 'OK' , async ( ) => {
311+ await setupTestServer ( {
275312 statusCode : SUCCESS ,
276- headers : {
277- 'retry-after' : null ,
278- 'x-sentry-rate-limits' : null ,
279- } ,
280- } ) ,
281- ) ;
282- } ) ;
313+ } ) ;
283314
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- } ) ;
315+ makeNodeTransport ( defaultOptions ) ;
316+ const registeredRequestExecutor = ( createTransport as jest . Mock ) . mock . calls [ 0 ] [ 1 ] ;
292317
293- makeNodeTransport ( defaultOptions ) ;
294- const registeredRequestExecutor = ( createTransport as jest . Mock ) . mock . calls [ 0 ] [ 1 ] ;
318+ const executorResult = registeredRequestExecutor ( {
319+ body : serializeEnvelope ( EVENT_ENVELOPE , new TextEncoder ( ) ) ,
320+ category : 'error' ,
321+ } ) ;
295322
296- const executorResult = registeredRequestExecutor ( {
297- body : serializeEnvelope ( EVENT_ENVELOPE , new TextEncoder ( ) ) ,
298- category : 'error' ,
323+ await expect ( executorResult ) . resolves . toEqual (
324+ expect . objectContaining ( {
325+ statusCode : SUCCESS ,
326+ headers : {
327+ 'retry-after' : null ,
328+ 'x-sentry-rate-limits' : null ,
329+ } ,
330+ } ) ,
331+ ) ;
299332 } ) ;
300333
301- await expect ( executorResult ) . resolves . toEqual (
302- expect . objectContaining ( {
334+ it ( 'OK with rate-limit headers' , async ( ) => {
335+ await setupTestServer ( {
303336 statusCode : SUCCESS ,
304- headers : {
305- 'retry-after ' : '2700' ,
306- 'x-sentry-rate-limits ' : '60::organization, 2700::organization' ,
337+ responseHeaders : {
338+ 'Retry-After ' : '2700' ,
339+ 'X-Sentry-Rate-Limits ' : '60::organization, 2700::organization' ,
307340 } ,
308- } ) ,
309- ) ;
310- } ) ;
341+ } ) ;
311342
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- } ) ;
343+ makeNodeTransport ( defaultOptions ) ;
344+ const registeredRequestExecutor = ( createTransport as jest . Mock ) . mock . calls [ 0 ] [ 1 ] ;
320345
321- makeNodeTransport ( defaultOptions ) ;
322- const registeredRequestExecutor = ( createTransport as jest . Mock ) . mock . calls [ 0 ] [ 1 ] ;
346+ const executorResult = registeredRequestExecutor ( {
347+ body : serializeEnvelope ( EVENT_ENVELOPE , new TextEncoder ( ) ) ,
348+ category : 'error' ,
349+ } ) ;
323350
324- const executorResult = registeredRequestExecutor ( {
325- body : serializeEnvelope ( EVENT_ENVELOPE , new TextEncoder ( ) ) ,
326- category : 'error' ,
351+ await expect ( executorResult ) . resolves . toEqual (
352+ expect . objectContaining ( {
353+ statusCode : SUCCESS ,
354+ headers : {
355+ 'retry-after' : '2700' ,
356+ 'x-sentry-rate-limits' : '60::organization, 2700::organization' ,
357+ } ,
358+ } ) ,
359+ ) ;
327360 } ) ;
328361
329- await expect ( executorResult ) . resolves . toEqual (
330- expect . objectContaining ( {
362+ it ( 'NOK with rate-limit headers' , async ( ) => {
363+ await setupTestServer ( {
331364 statusCode : RATE_LIMIT ,
332- headers : {
333- 'retry-after ' : '2700' ,
334- 'x-sentry-rate-limits ' : '60::organization, 2700::organization' ,
365+ responseHeaders : {
366+ 'Retry-After ' : '2700' ,
367+ 'X-Sentry-Rate-Limits ' : '60::organization, 2700::organization' ,
335368 } ,
336- } ) ,
337- ) ;
369+ } ) ;
370+
371+ makeNodeTransport ( defaultOptions ) ;
372+ const registeredRequestExecutor = ( createTransport as jest . Mock ) . mock . calls [ 0 ] [ 1 ] ;
373+
374+ const executorResult = registeredRequestExecutor ( {
375+ body : serializeEnvelope ( EVENT_ENVELOPE , new TextEncoder ( ) ) ,
376+ category : 'error' ,
377+ } ) ;
378+
379+ await expect ( executorResult ) . resolves . toEqual (
380+ expect . objectContaining ( {
381+ statusCode : RATE_LIMIT ,
382+ headers : {
383+ 'retry-after' : '2700' ,
384+ 'x-sentry-rate-limits' : '60::organization, 2700::organization' ,
385+ } ,
386+ } ) ,
387+ ) ;
388+ } ) ;
338389 } ) ;
339390} ) ;
0 commit comments