@@ -2,14 +2,19 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
22import { getEnvelopeEndpointWithUrlEncodedAuth } from '../../../src/api' ;
33import { makeDsn } from '../../../src/utils/dsn' ;
44import { createEnvelope , serializeEnvelope } from '../../../src/utils/envelope' ;
5- import { handleTunnelRequest } from '../../../src/utils/tunnel' ;
5+ import { handleTunnelRequest , MAX_TUNNEL_BODY_SIZE } from '../../../src/utils/tunnel' ;
66
77const TEST_DSN = 'https://public@dsn.ingest.sentry.io/1337' ;
88
9- function makeEnvelopeRequest ( envelopeHeader : Record < string , unknown > ) : Request {
9+ function makeEnvelopeRequest ( envelopeHeader : Record < string , unknown > , contentLength ?: number ) : Request {
1010 const envelope = createEnvelope ( envelopeHeader , [ ] ) ;
1111 const body = serializeEnvelope ( envelope ) ;
12- return new Request ( 'http://localhost/tunnel' , { method : 'POST' , body } ) ;
12+ const byteLength = typeof body === 'string' ? new TextEncoder ( ) . encode ( body ) . byteLength : body . byteLength ;
13+ return new Request ( 'http://localhost/tunnel' , {
14+ method : 'POST' ,
15+ body,
16+ headers : { 'content-length' : String ( contentLength ?? byteLength ) } ,
17+ } ) ;
1318}
1419
1520describe ( 'handleTunnelRequest' , ( ) => {
@@ -69,7 +74,11 @@ describe('handleTunnelRequest', () => {
6974
7075 it ( 'returns 400 when the envelope body contains malformed JSON' , async ( ) => {
7176 const result = await handleTunnelRequest ( {
72- request : new Request ( 'http://localhost/tunnel' , { method : 'POST' , body : 'not valid envelope data{{{' } ) ,
77+ request : new Request ( 'http://localhost/tunnel' , {
78+ method : 'POST' ,
79+ body : 'not valid envelope data{{{' ,
80+ headers : { 'content-length' : '26' } ,
81+ } ) ,
7382 allowedDsns : [ TEST_DSN ] ,
7483 } ) ;
7584
@@ -133,4 +142,48 @@ describe('handleTunnelRequest', () => {
133142 expect ( result . status ) . toBe ( 500 ) ;
134143 expect ( await result . text ( ) ) . toBe ( 'Failed to forward envelope to Sentry' ) ;
135144 } ) ;
145+
146+ describe ( 'body size limit' , ( ) => {
147+ it ( 'forwards the envelope when Content-Length equals the limit' , async ( ) => {
148+ fetchMock . mockResolvedValueOnce ( new Response ( 'ok' , { status : 200 } ) ) ;
149+
150+ const result = await handleTunnelRequest ( {
151+ request : makeEnvelopeRequest ( { dsn : TEST_DSN } , MAX_TUNNEL_BODY_SIZE ) ,
152+ allowedDsns : [ TEST_DSN ] ,
153+ } ) ;
154+
155+ expect ( result . status ) . toBe ( 200 ) ;
156+ } ) ;
157+
158+ it ( 'returns 413 when Content-Length is above the limit' , async ( ) => {
159+ const result = await handleTunnelRequest ( {
160+ request : makeEnvelopeRequest ( { dsn : TEST_DSN } , MAX_TUNNEL_BODY_SIZE + 1 ) ,
161+ allowedDsns : [ TEST_DSN ] ,
162+ } ) ;
163+
164+ expect ( result . status ) . toBe ( 413 ) ;
165+ expect ( await result . text ( ) ) . toBe ( 'Envelope too large' ) ;
166+ expect ( fetchMock ) . not . toHaveBeenCalled ( ) ;
167+ } ) ;
168+
169+ it ( 'returns 411 when the request has no Content-Length' , async ( ) => {
170+ const body = new ReadableStream < Uint8Array > ( {
171+ start ( controller ) {
172+ controller . enqueue ( new TextEncoder ( ) . encode ( `${ JSON . stringify ( { dsn : TEST_DSN } ) } \n` ) ) ;
173+ controller . close ( ) ;
174+ } ,
175+ } ) ;
176+ // Node.js requires `duplex` for a stream body, but the DOM types do not have it yet.
177+ const init : RequestInit & { duplex : 'half' } = { method : 'POST' , body, duplex : 'half' } ;
178+
179+ const result = await handleTunnelRequest ( {
180+ request : new Request ( 'http://localhost/tunnel' , init ) ,
181+ allowedDsns : [ TEST_DSN ] ,
182+ } ) ;
183+
184+ expect ( result . status ) . toBe ( 411 ) ;
185+ expect ( await result . text ( ) ) . toBe ( 'Length required' ) ;
186+ expect ( fetchMock ) . not . toHaveBeenCalled ( ) ;
187+ } ) ;
188+ } ) ;
136189} ) ;
0 commit comments