11import cors from "cors" ;
22import depd from "depd" ;
3+ import express from "express" ;
4+ import { readFile } from "node:fs/promises" ;
35import { z } from "zod" ;
46import {
57 EndpointsFactory ,
@@ -10,6 +12,7 @@ import {
1012 ResultHandler ,
1113 BuiltinLogger ,
1214 Middleware ,
15+ ez ,
1316} from "../../src" ;
1417import { givePort } from "../helpers" ;
1518import { setTimeout } from "node:timers/promises" ;
@@ -100,23 +103,44 @@ describe("App in production mode", async () => {
100103 output : z . object ( { } ) ,
101104 handler : async ( ) => setTimeout ( 5000 , { } ) ,
102105 } ) ;
106+ const rawEndpoint = new EndpointsFactory ( defaultResultHandler ) . build ( {
107+ method : "post" ,
108+ input : ez . raw ( ) ,
109+ output : z . object ( { crc : z . number ( ) } ) ,
110+ handler : async ( { input : { raw } } ) => ( { crc : raw . length } ) ,
111+ } ) ;
112+ const uploadEndpoint = new EndpointsFactory ( defaultResultHandler ) . buildVoid ( {
113+ method : "post" ,
114+ input : z . object ( { avatar : ez . upload ( ) } ) ,
115+ handler : vi . fn ( ) ,
116+ } ) ;
103117 const routing = {
104118 v1 : {
105119 corsed : corsedEndpoint ,
106120 faulty : faultyEndpoint ,
107121 test : testEndpoint ,
108122 long : longEndpoint ,
123+ raw : rawEndpoint ,
124+ upload : uploadEndpoint ,
109125 } ,
110126 } ;
111127 vi . spyOn ( process . stdout , "write" ) . mockImplementation ( vi . fn ( ) ) ; // mutes logo output
112128 const config = createConfig ( {
113129 http : { listen : port } ,
114130 compression : { threshold : 1 } ,
131+ rawParser : express . raw ( { limit : 20 } ) ,
132+ upload : {
133+ beforeUpload : ( { request } ) => {
134+ if ( "trigger" in request . query ) throw new Error ( "beforeUpload failure" ) ;
135+ } ,
136+ } ,
115137 beforeRouting : ( { app, getLogger } ) => {
116138 depd ( "express" ) ( "Sample deprecation message" ) ;
117139 app . use ( ( req , { } , next ) => {
118140 const childLogger = getLogger ( req ) ;
119141 assert ( "isChild" in childLogger && childLogger . isChild ) ;
142+ if ( req . path === "/trigger/beforeRouting" )
143+ return next ( new Error ( "Failure of beforeRouting triggered" ) ) ;
120144 next ( ) ;
121145 } ) ;
122146 } ,
@@ -247,6 +271,35 @@ describe("App in production mode", async () => {
247271 "Content-Range,X-Content-Range" ,
248272 ) ;
249273 } ) ;
274+
275+ test ( "Should handle raw request" , async ( ) => {
276+ const response = await fetch ( `http://127.0.0.1:${ port } /v1/raw` , {
277+ method : "POST" ,
278+ headers : { "content-type" : "application/octet-stream" } ,
279+ body : Buffer . from ( "testing" ) ,
280+ } ) ;
281+ expect ( response . status ) . toBe ( 200 ) ;
282+ const json = await response . json ( ) ;
283+ expect ( json ) . toEqual ( { status : "success" , data : { crc : 7 } } ) ;
284+ } ) ;
285+
286+ test ( "Should handle upload request" , async ( ) => {
287+ const filename = "logo.svg" ;
288+ const logo = await readFile ( filename , "utf-8" ) ;
289+ const data = new FormData ( ) ;
290+ data . append (
291+ "avatar" ,
292+ new Blob ( [ logo ] , { type : "image/svg+xml" } ) ,
293+ filename ,
294+ ) ;
295+ const response = await fetch ( `http://localhost:${ port } /v1/upload` , {
296+ method : "POST" ,
297+ body : data ,
298+ } ) ;
299+ expect ( response . status ) . toBe ( 200 ) ;
300+ const json = await response . json ( ) ;
301+ expect ( json ) . toEqual ( { data : { } , status : "success" } ) ;
302+ } ) ;
250303 } ) ;
251304
252305 describe ( "Negative" , ( ) => {
@@ -297,6 +350,38 @@ describe("App in production mode", async () => {
297350 expect ( text ) . toBe ( "Internal Server Error" ) ;
298351 expect ( errorMethod . mock . lastCall ) . toMatchSnapshot ( ) ;
299352 } ) ;
353+
354+ test ( "Should treat beforeRouting error as internal" , async ( ) => {
355+ const response = await fetch (
356+ `http://127.0.0.1:${ port } /trigger/beforeRouting` ,
357+ ) ;
358+ expect ( await response . json ( ) ) . toEqual ( {
359+ status : "error" ,
360+ error : { message : "Internal Server Error" } ,
361+ } ) ;
362+ expect ( response . status ) . toBe ( 500 ) ;
363+ } ) ;
364+
365+ test ( "Should treat beforeUpload error as internal" , async ( ) => {
366+ const filename = "logo.svg" ;
367+ const logo = await readFile ( filename , "utf-8" ) ;
368+ const data = new FormData ( ) ;
369+ data . append (
370+ "avatar" ,
371+ new Blob ( [ logo ] , { type : "image/svg+xml" } ) ,
372+ filename ,
373+ ) ;
374+ const response = await fetch (
375+ `http://localhost:${ port } /v1/upload?trigger=beforeUpload` ,
376+ { method : "POST" , body : data } ,
377+ ) ;
378+ expect ( response . status ) . toBe ( 500 ) ;
379+ const json = await response . json ( ) ;
380+ expect ( json ) . toEqual ( {
381+ error : { message : "Internal Server Error" } ,
382+ status : "error" ,
383+ } ) ;
384+ } ) ;
300385 } ) ;
301386
302387 describe ( "Protocol" , ( ) => {
@@ -316,7 +401,7 @@ describe("App in production mode", async () => {
316401 expect ( json ) . toMatchSnapshot ( ) ;
317402 } ) ;
318403
319- test ( "Should fail on malformed body " , async ( ) => {
404+ test ( "Should handle JSON parser failures " , async ( ) => {
320405 const response = await fetch ( `http://127.0.0.1:${ port } /v1/test` , {
321406 method : "POST" , // valid method this time
322407 headers : {
@@ -337,6 +422,20 @@ describe("App in production mode", async () => {
337422 } ) ;
338423 } ) ;
339424
425+ test ( "Should handle Raw parser failures" , async ( ) => {
426+ const response = await fetch ( `http://127.0.0.1:${ port } /v1/raw` , {
427+ method : "POST" ,
428+ headers : { "content-type" : "application/octet-stream" } ,
429+ body : Buffer . alloc ( 100 ) ,
430+ } ) ;
431+ expect ( response . status ) . toBe ( 413 ) ;
432+ const json = await response . json ( ) ;
433+ expect ( json ) . toEqual ( {
434+ status : "error" ,
435+ error : { message : "request entity too large" } ,
436+ } ) ;
437+ } ) ;
438+
340439 test ( "Should fail when missing content type header" , async ( ) => {
341440 const response = await fetch ( `http://127.0.0.1:${ port } /v1/test` , {
342441 method : "POST" ,
@@ -446,7 +545,7 @@ describe("App in production mode", async () => {
446545 await setTimeout ( 500 ) ;
447546 process . emit ( "FAKE" as "SIGTERM" ) ;
448547 expect ( infoMethod ) . toHaveBeenCalledWith ( "Graceful shutdown" , {
449- sockets : 1 ,
548+ sockets : expect . any ( Number ) ,
450549 timeout : 1000 ,
451550 } ) ;
452551 await setTimeout ( 1500 ) ;
0 commit comments