@@ -14,6 +14,7 @@ type SseTestEvent = {
1414const server : Server = { } ;
1515
1616describe ( 'HttpRouter' , ( ) => {
17+ let port = 7500 ;
1718 describe ( 'handlers' , ( ) => {
1819 let app : HttpRouter ;
1920 let oldEnv : string | undefined ;
@@ -295,28 +296,131 @@ describe('HttpRouter', () => {
295296 expect ( await resp2 . text ( ) ) . toBe ( 'Hi' ) ;
296297 } ) ;
297298 } ) ;
298- describe ( 'server' , ( ) => {
299- let app : HttpRouter ;
299+ describe ( 'listening' , ( ) => {
300300 let server : Server ;
301- beforeEach ( ( ) => {
302- app = new HttpRouter ( ) ;
303- } ) ;
304301 afterEach ( ( ) => {
305302 server . stop ( true ) ;
306303 } ) ;
307304 it ( 'should assign random port' , async ( ) => {
305+ const app = new HttpRouter ( ) ;
306+ server = app . listen ( ) ;
308307 app . get ( '/' , ( ) => new Response ( 'Hi' ) ) ;
309- server = app . listen ( { port : 7769 } ) ;
310- const resp = await fetch ( `http://localhost:${ server . port } /` ) ;
308+ const resp = await fetch ( server . url ) ;
311309 expect ( typeof server . port ) . toBe ( 'number' ) ;
312310 expect ( server . port ) . toBeGreaterThan ( 0 ) ;
313311 expect ( resp . status ) . toBe ( 200 ) ;
314312 expect ( await resp . text ( ) ) . toBe ( 'Hi' ) ;
315313 } ) ;
314+ } ) ;
315+ describe ( 'server' , ( ) => {
316+ let app : HttpRouter ;
317+ let server : Server ;
318+ beforeEach ( ( ) => {
319+ app = new HttpRouter ( ) ;
320+ server = app . listen ( { port : port ++ } ) ;
321+ } ) ;
322+ afterEach ( ( ) => {
323+ server . stop ( true ) ;
324+ } ) ;
325+ it ( 'should return correct statuses, headers, and bytes' , async ( ) => {
326+ app . headGet ( '/bun-logo.jpg' , c => {
327+ return c . file ( `${ import . meta. dirname } /../../testFixtures/bun-logo.jpg` ) ;
328+ } ) ;
329+ const url = `${ server . url } /bun-logo.jpg` ;
330+
331+ // Step 1: Fetch entire file
332+ const fullResponse = await fetch ( url ) ;
333+ const fullFileBytes = await fullResponse . blob ( ) ;
334+ const fileSize = Number ( fullResponse . headers . get ( 'content-length' ) ) ;
335+
336+ expect ( fullResponse . status ) . toBe ( 200 ) ;
337+ expect ( fullFileBytes . size ) . toBe ( fileSize ) ;
338+ expect ( fullResponse . headers . get ( 'accept-ranges' ) ) . toBe ( 'bytes' ) ;
339+ expect ( fullResponse . headers . get ( 'content-type' ) ) . toBe ( 'image/jpeg' ) ;
340+
341+ // Step 2: Fetch HEAD and validate
342+ const headResponse = await fetch ( url , { method : 'HEAD' } ) ;
343+
344+ expect ( headResponse . status ) . toBe ( 200 ) ;
345+ expect ( headResponse . headers . get ( 'content-length' ) ) . toBe ( String ( fileSize ) ) ;
346+ expect ( headResponse . headers . get ( 'accept-ranges' ) ) . toBe ( 'bytes' ) ;
347+
348+ // Step 3: Fetch range "bytes=0-" and validate
349+ const rangeResponse1 = await fetch ( url , {
350+ headers : { Range : 'bytes=0-' } ,
351+ } ) ;
352+ const range1Bytes = await rangeResponse1 . blob ( ) ;
353+
354+ expect ( rangeResponse1 . status ) . toBe ( 206 ) ;
355+ expect ( rangeResponse1 . headers . get ( 'accept-ranges' ) ) . toBe ( 'bytes' ) ;
356+ expect ( rangeResponse1 . headers . get ( 'content-type' ) ) . toBe ( 'image/jpeg' ) ;
357+ expect ( range1Bytes . size ) . toBe ( fileSize ) ;
358+ expect ( rangeResponse1 . headers . get ( 'content-length' ) ) . toBe (
359+ String ( fileSize )
360+ ) ;
361+ expect ( range1Bytes ) . toEqual ( fullFileBytes ) ;
362+
363+ // Step 4: Fetch range "bytes=0-999" and validate
364+ const rangeResponse2 = await fetch ( url , {
365+ headers : { Range : 'bytes=0-999' } ,
366+ } ) ;
367+ const range2Bytes = await rangeResponse2 . blob ( ) ;
368+
369+ expect ( rangeResponse2 . status ) . toBe ( 206 ) ;
370+ expect ( rangeResponse2 . headers . get ( 'accept-ranges' ) ) . toBe ( 'bytes' ) ;
371+ expect ( rangeResponse2 . headers . get ( 'content-length' ) ) . toBe ( '1000' ) ;
372+ expect ( range2Bytes . size ) . toBe ( 1000 ) ;
373+ expect ( rangeResponse2 . headers . get ( 'content-range' ) ) . toBe (
374+ `bytes 0-999/${ fileSize } `
375+ ) ;
376+ expect ( range2Bytes ) . toEqual ( fullFileBytes . slice ( 0 , 1000 ) ) ;
377+ expect ( rangeResponse2 . headers . get ( 'content-type' ) ) . toBe ( 'image/jpeg' ) ;
378+
379+ // Step 5: Fetch range "bytes=1000-1999" and validate
380+ const rangeResponse3 = await fetch ( url , {
381+ headers : { Range : 'bytes=1000-1999' } ,
382+ } ) ;
383+ const range3Bytes = await rangeResponse3 . blob ( ) ;
384+
385+ expect ( rangeResponse3 . status ) . toBe ( 206 ) ;
386+ expect ( rangeResponse3 . headers . get ( 'accept-ranges' ) ) . toBe ( 'bytes' ) ;
387+ expect ( rangeResponse3 . headers . get ( 'content-length' ) ) . toBe ( '1000' ) ;
388+ expect ( range3Bytes . size ) . toBe ( 1000 ) ;
389+ expect ( rangeResponse3 . headers . get ( 'content-range' ) ) . toBe (
390+ `bytes 1000-1999/${ fileSize } `
391+ ) ;
392+ expect ( range3Bytes ) . toEqual ( fullFileBytes . slice ( 1000 , 2000 ) ) ;
393+ expect ( rangeResponse3 . headers . get ( 'content-type' ) ) . toBe ( 'image/jpeg' ) ;
394+
395+ // Step 5: Fetch range "bytes=-1000" and validate
396+ const rangeResponse4 = await fetch ( url , {
397+ headers : { Range : 'bytes=-1000' } ,
398+ } ) ;
399+ const range4Bytes = await rangeResponse4 . blob ( ) ;
400+
401+ expect ( rangeResponse4 . status ) . toBe ( 206 ) ;
402+ expect ( rangeResponse4 . headers . get ( 'accept-ranges' ) ) . toBe ( 'bytes' ) ;
403+ expect ( rangeResponse4 . headers . get ( 'content-length' ) ) . toBe ( '1000' ) ;
404+ expect ( range4Bytes . size ) . toBe ( 1000 ) ;
405+ expect ( rangeResponse4 . headers . get ( 'content-range' ) ) . toBe (
406+ `bytes ${ fileSize - 1001 } -${ fileSize - 1 } /${ fileSize } `
407+ ) ;
408+ expect ( range4Bytes ) . toEqual ( fullFileBytes . slice ( - 1000 ) ) ;
409+ expect ( rangeResponse4 . headers . get ( 'content-type' ) ) . toBe ( 'image/jpeg' ) ;
410+
411+ // Step 7: Request invalid range
412+ const rangeResponse5 = await fetch ( url , {
413+ headers : { Range : 'bytes=9999999-' } ,
414+ } ) ;
415+ expect ( rangeResponse5 . status ) . toBe ( 416 ) ;
416+ expect ( rangeResponse5 . statusText ) . toBe ( 'Range Not Satisfiable' ) ;
417+ expect ( rangeResponse5 . headers . get ( 'content-range' ) ) . toBe (
418+ `bytes */${ fileSize } `
419+ ) ;
420+ } ) ;
316421 it ( 'should get client ip info' , async ( ) => {
317422 app . get ( '/' , c => c . json ( c . ip ) ) ;
318- server = app . listen ( { port : 7770 } ) ;
319- const resp = await fetch ( `http://localhost:${ server . port } /` ) ;
423+ const resp = await fetch ( server . url ) ;
320424 const info = ( await resp . json ( ) ) as {
321425 address : string ;
322426 family : string ;
@@ -328,33 +432,28 @@ describe('HttpRouter', () => {
328432 } ) ;
329433 it ( 'should emit url' , async ( ) => {
330434 app . all ( '/' , ( ) => new Response ( 'Hi' ) ) ;
331- server = app . listen ( { port : 7771 } ) ;
332- let output : string ;
435+ let output : string = '' ;
333436 const to = ( message : string ) => ( output = message ) ;
334437 app . emitUrl ( { to } ) ;
335- // @ts -expect-error
336438 expect ( output ) . toContain ( String ( server . url ) ) ;
337439 } ) ;
338440 it ( 'should handle all' , async ( ) => {
339441 app . all ( '/' , ( ) => new Response ( 'Hi' ) ) ;
340- server = app . listen ( { port : 7772 } ) ;
341- const resp = await fetch ( 'http://localhost:7772/' ) ;
442+ const resp = await fetch ( server . url ) ;
342443 expect ( resp . status ) . toBe ( 200 ) ;
343444 expect ( await resp . text ( ) ) . toBe ( 'Hi' ) ;
344445 } ) ;
345446 it ( 'should handle GET' , async ( ) => {
346447 app . get ( '/' , ( ) => new Response ( 'Hi' ) ) ;
347- server = app . listen ( { port : 7773 } ) ;
348- const resp = await fetch ( 'http://localhost:7773/' ) ;
448+ const resp = await fetch ( server . url ) ;
349449 expect ( resp . status ) . toBe ( 200 ) ;
350450 expect ( await resp . text ( ) ) . toBe ( 'Hi' ) ;
351451 } ) ;
352452 it ( 'should handle PUT' , async ( ) => {
353453 app . put ( '/' , async ( { request, json } ) => {
354454 return json ( await request . json ( ) ) ;
355455 } ) ;
356- server = app . listen ( { port : 7774 } ) ;
357- const resp = await fetch ( 'http://localhost:7774/' , {
456+ const resp = await fetch ( server . url , {
358457 method : 'PUT' ,
359458 headers : {
360459 'Content-Type' : 'application/json' ,
@@ -376,8 +475,7 @@ describe('HttpRouter', () => {
376475 } ,
377476 } ) ;
378477 } ) ;
379- server = app . listen ( { port : 7775 } ) ;
380- const resp = await fetch ( 'http://localhost:7775/hi?name=Bob' , {
478+ const resp = await fetch ( `${ server . url } /hi?name=Bob` , {
381479 method : 'HEAD' ,
382480 } ) ;
383481 expect ( resp . status ) . toBe ( 204 ) ;
@@ -394,10 +492,9 @@ describe('HttpRouter', () => {
394492 } ,
395493 } ) ;
396494 } ) ;
397- server = app . listen ( { port : 7776 } ) ;
398495 const formData = new URLSearchParams ( ) ;
399496 formData . append ( 'key' , 'secret' ) ;
400- const resp = await fetch ( 'http://localhost:7776/ parrot' , {
497+ const resp = await fetch ( ` ${ server . url } / parrot` , {
401498 method : 'POST' ,
402499 headers : {
403500 'Content-type' : 'application/x-www-form-urlencoded' ,
@@ -418,10 +515,9 @@ describe('HttpRouter', () => {
418515 } ,
419516 } ) ;
420517 } ) ;
421- server = app . listen ( { port : 7777 } ) ;
422518 const formData = new FormData ( ) ;
423519 formData . append ( 'key2' , 'secret2' ) ;
424- const resp = await fetch ( 'http://localhost:7777/ parrot' , {
520+ const resp = await fetch ( ` ${ server . url } / parrot` , {
425521 method : 'POST' ,
426522 body : formData ,
427523 } ) ;
@@ -432,8 +528,7 @@ describe('HttpRouter', () => {
432528 app . patch ( '/' , async ( { request, json } ) => {
433529 return json ( await request . json ( ) ) ;
434530 } ) ;
435- server = app . listen ( { port : 7778 } ) ;
436- const resp = await fetch ( 'http://localhost:7778/' , {
531+ const resp = await fetch ( server . url , {
437532 method : 'PATCH' ,
438533 headers : {
439534 'Content-Type' : 'application/json' ,
@@ -453,8 +548,7 @@ describe('HttpRouter', () => {
453548 } ,
454549 } ) ;
455550 } ) ;
456- server = app . listen ( { port : 7779 } ) ;
457- const resp = await fetch ( 'http://localhost:7779/' , {
551+ const resp = await fetch ( server . url , {
458552 method : 'TRACE' ,
459553 headers : {
460554 'Max-Forwards' : '0' ,
@@ -474,8 +568,7 @@ describe('HttpRouter', () => {
474568 } ,
475569 } ) ;
476570 } ) ;
477- server = app . listen ( { port : 7780 } ) ;
478- const resp = await fetch ( 'http://localhost:7780/users/42' , {
571+ const resp = await fetch ( `${ server . url } /users/42` , {
479572 method : 'DELETE' ,
480573 } ) ;
481574 expect ( resp . status ) . toBe ( 204 ) ;
@@ -490,8 +583,7 @@ describe('HttpRouter', () => {
490583 } ,
491584 } ) ;
492585 } ) ;
493- server = app . listen ( { port : 7781 } ) ;
494- const resp = await fetch ( 'http://localhost:7781/users/42' , {
586+ const resp = await fetch ( `${ server . url } /users/42` , {
495587 method : 'OPTIONS' ,
496588 } ) ;
497589 expect ( resp . status ) . toBe ( 204 ) ;
@@ -501,9 +593,8 @@ describe('HttpRouter', () => {
501593 app . get ( '/home' , ( { app } ) => {
502594 return new Response ( app . locals . foo ) ;
503595 } ) ;
504- server = app . listen ( { port : 7782 } ) ;
505596 app . locals . foo = 'bar' ;
506- const resp = await fetch ( 'http://localhost:7782/ home' ) ;
597+ const resp = await fetch ( ` ${ server . url } / home` ) ;
507598 expect ( resp . status ) . toBe ( 200 ) ;
508599 expect ( await resp . text ( ) ) . toBe ( 'bar' ) ;
509600 } ) ;
@@ -573,7 +664,7 @@ describe('HttpRouter', () => {
573664 it ( 'should handle unnamed data' , async ( ) => {
574665 const events = await sseTest ( {
575666 event : 'message' ,
576- port : 7784 ,
667+ port : port ++ ,
577668 payloads : [ [ 'Hello' ] , [ 'World' ] ] ,
578669 } ) ;
579670 expect ( events . length ) . toBe ( 2 ) ;
@@ -583,7 +674,7 @@ describe('HttpRouter', () => {
583674 it ( 'should send last event id and origin' , async ( ) => {
584675 const events = await sseTest ( {
585676 event : 'myEvent' ,
586- port : 7785 ,
677+ port : port ++ ,
587678 payloads : [
588679 [ 'myEvent' , 'hi1' , 'id1' ] ,
589680 [ 'myEvent' , 'hi2' , 'id2' ] ,
@@ -594,13 +685,13 @@ describe('HttpRouter', () => {
594685 expect ( events [ 1 ] . data ) . toBe ( 'hi2' ) ;
595686 expect ( events [ 0 ] . lastEventId ) . toBe ( 'id1' ) ;
596687 expect ( events [ 1 ] . lastEventId ) . toBe ( 'id2' ) ;
597- expect ( events [ 0 ] . origin ) . toBe ( ' http://localhost:7785' ) ;
598- expect ( events [ 1 ] . origin ) . toBe ( ' http://localhost:7785' ) ;
688+ expect ( events [ 0 ] . origin ) . toBe ( ` http://localhost:${ port - 1 } ` ) ;
689+ expect ( events [ 1 ] . origin ) . toBe ( ` http://localhost:${ port - 1 } ` ) ;
599690 } ) ;
600691 it ( 'should JSON encode data if needed' , async ( ) => {
601692 const events = await sseTest ( {
602693 event : 'myEvent' ,
603- port : 7786 ,
694+ port : port ++ ,
604695 payloads : [ [ 'myEvent' , { name : 'Bob' } ] ] ,
605696 } ) ;
606697 expect ( events . length ) . toBe ( 1 ) ;
@@ -610,7 +701,7 @@ describe('HttpRouter', () => {
610701 spyOn ( console , 'warn' ) . mockImplementation ( ( ) => { } ) ;
611702 await sseTest ( {
612703 event : 'myEvent' ,
613- port : 7787 ,
704+ port : port ++ ,
614705 payloads : [ [ 'myEvent' , { name : 'Bob' } ] ] ,
615706 headers : {
616707 'Content-Type' : 'text/plain' ,
@@ -626,7 +717,7 @@ describe('HttpRouter', () => {
626717 spyOn ( console , 'warn' ) . mockImplementation ( ( ) => { } ) ;
627718 await sseTest ( {
628719 event : 'myEvent' ,
629- port : 7788 ,
720+ port : port ++ ,
630721 payloads : [ [ 'myEvent' , { name : 'Bob' } ] ] ,
631722 headers : {
632723 'Content-Type' : 'text/event-stream' ,
0 commit comments