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,13 @@ 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 ( EVENT_ATTACHMENT_ENVELOPE , new TextEncoder ( ) ) ;
83+
7284const defaultOptions = {
7385 url : TEST_SERVER_URL ,
7486 recordDroppedEvent : ( ) => undefined ,
@@ -155,6 +167,40 @@ describe('makeNewHttpTransport()', () => {
155167 } ) ;
156168 } ) ;
157169
170+ describe ( 'compression' , ( ) => {
171+ it ( 'small envelopes should not be compressed' , async ( ) => {
172+ await setupTestServer (
173+ {
174+ statusCode : SUCCESS ,
175+ responseHeaders : { } ,
176+ } ,
177+ ( req , body ) => {
178+ expect ( req . headers [ 'content-encoding' ] ) . toBeUndefined ( ) ;
179+ expect ( body ) . toBe ( SERIALIZED_EVENT_ENVELOPE ) ;
180+ } ,
181+ ) ;
182+
183+ const transport = makeNodeTransport ( defaultOptions ) ;
184+ await transport . send ( EVENT_ENVELOPE ) ;
185+ } ) ;
186+
187+ it ( 'large envelopes should be compressed' , async ( ) => {
188+ await setupTestServer (
189+ {
190+ statusCode : SUCCESS ,
191+ responseHeaders : { } ,
192+ } ,
193+ ( req , _ , raw ) => {
194+ expect ( req . headers [ 'content-encoding' ] ) . toEqual ( 'gzip' ) ;
195+ expect ( raw . buffer ) . toStrictEqual ( SERIALIZED_EVENT_ATTACHMENT_ENVELOPE . buffer ) ;
196+ } ,
197+ ) ;
198+
199+ const transport = makeNodeTransport ( defaultOptions ) ;
200+ await transport . send ( EVENT_ATTACHMENT_ENVELOPE ) ;
201+ } ) ;
202+ } ) ;
203+
158204 describe ( 'proxy' , ( ) => {
159205 it ( 'can be configured through option' , ( ) => {
160206 makeNodeTransport ( {
@@ -236,104 +282,106 @@ describe('makeNewHttpTransport()', () => {
236282 } ) ;
237283 } ) ;
238284
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 ( {
285+ describe ( 'should register TransportRequestExecutor that returns the correct object from server response' , ( ) => {
286+ it ( 'rate limit' , async ( ) => {
287+ await setupTestServer ( {
255288 statusCode : RATE_LIMIT ,
256- } ) ,
257- ) ;
258- } ) ;
289+ responseHeaders : { } ,
290+ } ) ;
259291
260- it ( 'should register TransportRequestExecutor that returns the correct object from server response (OK)' , async ( ) => {
261- await setupTestServer ( {
262- statusCode : SUCCESS ,
263- } ) ;
292+ makeNodeTransport ( defaultOptions ) ;
293+ const registeredRequestExecutor = ( createTransport as jest . Mock ) . mock . calls [ 0 ] [ 1 ] ;
264294
265- makeNodeTransport ( defaultOptions ) ;
266- const registeredRequestExecutor = ( createTransport as jest . Mock ) . mock . calls [ 0 ] [ 1 ] ;
295+ const executorResult = registeredRequestExecutor ( {
296+ body : serializeEnvelope ( EVENT_ENVELOPE , new TextEncoder ( ) ) ,
297+ category : 'error' ,
298+ } ) ;
267299
268- const executorResult = registeredRequestExecutor ( {
269- body : serializeEnvelope ( EVENT_ENVELOPE , new TextEncoder ( ) ) ,
270- category : 'error' ,
300+ await expect ( executorResult ) . resolves . toEqual (
301+ expect . objectContaining ( {
302+ statusCode : RATE_LIMIT ,
303+ } ) ,
304+ ) ;
271305 } ) ;
272306
273- await expect ( executorResult ) . resolves . toEqual (
274- expect . objectContaining ( {
307+ it ( 'OK' , async ( ) => {
308+ await setupTestServer ( {
275309 statusCode : SUCCESS ,
276- headers : {
277- 'retry-after' : null ,
278- 'x-sentry-rate-limits' : null ,
279- } ,
280- } ) ,
281- ) ;
282- } ) ;
310+ } ) ;
283311
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- } ) ;
312+ makeNodeTransport ( defaultOptions ) ;
313+ const registeredRequestExecutor = ( createTransport as jest . Mock ) . mock . calls [ 0 ] [ 1 ] ;
292314
293- makeNodeTransport ( defaultOptions ) ;
294- const registeredRequestExecutor = ( createTransport as jest . Mock ) . mock . calls [ 0 ] [ 1 ] ;
315+ const executorResult = registeredRequestExecutor ( {
316+ body : serializeEnvelope ( EVENT_ENVELOPE , new TextEncoder ( ) ) ,
317+ category : 'error' ,
318+ } ) ;
295319
296- const executorResult = registeredRequestExecutor ( {
297- body : serializeEnvelope ( EVENT_ENVELOPE , new TextEncoder ( ) ) ,
298- category : 'error' ,
320+ await expect ( executorResult ) . resolves . toEqual (
321+ expect . objectContaining ( {
322+ statusCode : SUCCESS ,
323+ headers : {
324+ 'retry-after' : null ,
325+ 'x-sentry-rate-limits' : null ,
326+ } ,
327+ } ) ,
328+ ) ;
299329 } ) ;
300330
301- await expect ( executorResult ) . resolves . toEqual (
302- expect . objectContaining ( {
331+ it ( 'OK with rate-limit headers' , async ( ) => {
332+ await setupTestServer ( {
303333 statusCode : SUCCESS ,
304- headers : {
305- 'retry-after ' : '2700' ,
306- 'x-sentry-rate-limits ' : '60::organization, 2700::organization' ,
334+ responseHeaders : {
335+ 'Retry-After ' : '2700' ,
336+ 'X-Sentry-Rate-Limits ' : '60::organization, 2700::organization' ,
307337 } ,
308- } ) ,
309- ) ;
310- } ) ;
338+ } ) ;
311339
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- } ) ;
340+ makeNodeTransport ( defaultOptions ) ;
341+ const registeredRequestExecutor = ( createTransport as jest . Mock ) . mock . calls [ 0 ] [ 1 ] ;
320342
321- makeNodeTransport ( defaultOptions ) ;
322- const registeredRequestExecutor = ( createTransport as jest . Mock ) . mock . calls [ 0 ] [ 1 ] ;
343+ const executorResult = registeredRequestExecutor ( {
344+ body : serializeEnvelope ( EVENT_ENVELOPE , new TextEncoder ( ) ) ,
345+ category : 'error' ,
346+ } ) ;
323347
324- const executorResult = registeredRequestExecutor ( {
325- body : serializeEnvelope ( EVENT_ENVELOPE , new TextEncoder ( ) ) ,
326- category : 'error' ,
348+ await expect ( executorResult ) . resolves . toEqual (
349+ expect . objectContaining ( {
350+ statusCode : SUCCESS ,
351+ headers : {
352+ 'retry-after' : '2700' ,
353+ 'x-sentry-rate-limits' : '60::organization, 2700::organization' ,
354+ } ,
355+ } ) ,
356+ ) ;
327357 } ) ;
328358
329- await expect ( executorResult ) . resolves . toEqual (
330- expect . objectContaining ( {
359+ it ( 'NOK with rate-limit headers' , async ( ) => {
360+ await setupTestServer ( {
331361 statusCode : RATE_LIMIT ,
332- headers : {
333- 'retry-after ' : '2700' ,
334- 'x-sentry-rate-limits ' : '60::organization, 2700::organization' ,
362+ responseHeaders : {
363+ 'Retry-After ' : '2700' ,
364+ 'X-Sentry-Rate-Limits ' : '60::organization, 2700::organization' ,
335365 } ,
336- } ) ,
337- ) ;
366+ } ) ;
367+
368+ makeNodeTransport ( defaultOptions ) ;
369+ const registeredRequestExecutor = ( createTransport as jest . Mock ) . mock . calls [ 0 ] [ 1 ] ;
370+
371+ const executorResult = registeredRequestExecutor ( {
372+ body : serializeEnvelope ( EVENT_ENVELOPE , new TextEncoder ( ) ) ,
373+ category : 'error' ,
374+ } ) ;
375+
376+ await expect ( executorResult ) . resolves . toEqual (
377+ expect . objectContaining ( {
378+ statusCode : RATE_LIMIT ,
379+ headers : {
380+ 'retry-after' : '2700' ,
381+ 'x-sentry-rate-limits' : '60::organization, 2700::organization' ,
382+ } ,
383+ } ) ,
384+ ) ;
385+ } ) ;
338386 } ) ;
339387} ) ;
0 commit comments