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 ,
@@ -9,6 +11,7 @@ import {
911 ResultHandler ,
1012 BuiltinLogger ,
1113 Middleware ,
14+ ez ,
1215} from "../../src" ;
1316import { givePort } from "../helpers" ;
1417import { setTimeout } from "node:timers/promises" ;
@@ -102,12 +105,26 @@ describe("App in production mode", async () => {
102105 output : z . object ( { } ) ,
103106 handler : async ( ) => setTimeout ( 5000 , { } ) ,
104107 } ) ;
108+ const rawEndpoint = new EndpointsFactory ( defaultResultHandler ) . build ( {
109+ method : "post" ,
110+ input : ez . raw ( ) ,
111+ output : z . object ( { crc : z . number ( ) } ) ,
112+ handler : async ( { input : { raw } } ) => ( { crc : raw . length } ) ,
113+ } ) ;
114+ const uploadEndpoint = new EndpointsFactory ( defaultResultHandler ) . build ( {
115+ method : "post" ,
116+ input : z . object ( { avatar : ez . upload ( ) } ) ,
117+ output : z . object ( { } ) ,
118+ handler : async ( ) => ( { } ) ,
119+ } ) ;
105120 const routing = {
106121 v1 : {
107122 corsed : corsedEndpoint ,
108123 faulty : faultyEndpoint ,
109124 test : testEndpoint ,
110125 long : longEndpoint ,
126+ raw : rawEndpoint ,
127+ upload : uploadEndpoint ,
111128 } ,
112129 } ;
113130 vi . spyOn ( process . stdout , "write" ) . mockImplementation ( vi . fn ( ) ) ; // mutes logo output
@@ -117,11 +134,20 @@ describe("App in production mode", async () => {
117134 server : {
118135 listen : port ,
119136 compression : { threshold : 1 } ,
137+ rawParser : express . raw ( { limit : 20 } ) ,
138+ upload : {
139+ beforeUpload : ( { request } ) => {
140+ if ( "trigger" in request . query )
141+ throw new Error ( "beforeUpload failure" ) ;
142+ } ,
143+ } ,
120144 beforeRouting : ( { app, getChildLogger } ) => {
121145 depd ( "express" ) ( "Sample deprecation message" ) ;
122146 app . use ( ( req , { } , next ) => {
123147 const childLogger = getChildLogger ( req ) ;
124148 assert ( "isChild" in childLogger && childLogger . isChild ) ;
149+ if ( req . path === "/trigger/beforeRouting" )
150+ return next ( new Error ( "Failure of beforeRouting triggered" ) ) ;
125151 next ( ) ;
126152 } ) ;
127153 } ,
@@ -253,6 +279,35 @@ describe("App in production mode", async () => {
253279 "Content-Range,X-Content-Range" ,
254280 ) ;
255281 } ) ;
282+
283+ test ( "Should handle raw request" , async ( ) => {
284+ const response = await fetch ( `http://127.0.0.1:${ port } /v1/raw` , {
285+ method : "POST" ,
286+ headers : { "content-type" : "application/octet-stream" } ,
287+ body : Buffer . from ( "testing" ) ,
288+ } ) ;
289+ expect ( response . status ) . toBe ( 200 ) ;
290+ const json = await response . json ( ) ;
291+ expect ( json ) . toEqual ( { status : "success" , data : { crc : 7 } } ) ;
292+ } ) ;
293+
294+ test ( "Should handle upload request" , async ( ) => {
295+ const filename = "logo.svg" ;
296+ const logo = await readFile ( filename , "utf-8" ) ;
297+ const data = new FormData ( ) ;
298+ data . append (
299+ "avatar" ,
300+ new Blob ( [ logo ] , { type : "image/svg+xml" } ) ,
301+ filename ,
302+ ) ;
303+ const response = await fetch ( `http://localhost:${ port } /v1/upload` , {
304+ method : "POST" ,
305+ body : data ,
306+ } ) ;
307+ expect ( response . status ) . toBe ( 200 ) ;
308+ const json = await response . json ( ) ;
309+ expect ( json ) . toEqual ( { data : { } , status : "success" } ) ;
310+ } ) ;
256311 } ) ;
257312
258313 describe ( "Negative" , ( ) => {
@@ -303,6 +358,38 @@ describe("App in production mode", async () => {
303358 expect ( text ) . toBe ( "Internal Server Error" ) ;
304359 expect ( errorMethod . mock . lastCall ) . toMatchSnapshot ( ) ;
305360 } ) ;
361+
362+ test ( "Should treat beforeRouting error as internal" , async ( ) => {
363+ const response = await fetch (
364+ `http://127.0.0.1:${ port } /trigger/beforeRouting` ,
365+ ) ;
366+ expect ( await response . json ( ) ) . toEqual ( {
367+ status : "error" ,
368+ error : { message : "Internal Server Error" } ,
369+ } ) ;
370+ expect ( response . status ) . toBe ( 500 ) ;
371+ } ) ;
372+
373+ test ( "Should treat beforeUpload error as internal" , async ( ) => {
374+ const filename = "logo.svg" ;
375+ const logo = await readFile ( filename , "utf-8" ) ;
376+ const data = new FormData ( ) ;
377+ data . append (
378+ "avatar" ,
379+ new Blob ( [ logo ] , { type : "image/svg+xml" } ) ,
380+ filename ,
381+ ) ;
382+ const response = await fetch (
383+ `http://localhost:${ port } /v1/upload?trigger=beforeUpload` ,
384+ { method : "POST" , body : data } ,
385+ ) ;
386+ expect ( response . status ) . toBe ( 500 ) ;
387+ const json = await response . json ( ) ;
388+ expect ( json ) . toEqual ( {
389+ error : { message : "Internal Server Error" } ,
390+ status : "error" ,
391+ } ) ;
392+ } ) ;
306393 } ) ;
307394
308395 describe ( "Protocol" , ( ) => {
@@ -322,7 +409,7 @@ describe("App in production mode", async () => {
322409 expect ( json ) . toMatchSnapshot ( ) ;
323410 } ) ;
324411
325- test ( "Should fail on malformed body " , async ( ) => {
412+ test ( "Should handle JSON parser failures " , async ( ) => {
326413 const response = await fetch ( `http://127.0.0.1:${ port } /v1/test` , {
327414 method : "POST" , // valid method this time
328415 headers : {
@@ -343,6 +430,20 @@ describe("App in production mode", async () => {
343430 } ) ;
344431 } ) ;
345432
433+ test ( "Should handle Raw parser failures" , async ( ) => {
434+ const response = await fetch ( `http://127.0.0.1:${ port } /v1/raw` , {
435+ method : "POST" ,
436+ headers : { "content-type" : "application/octet-stream" } ,
437+ body : Buffer . alloc ( 100 ) ,
438+ } ) ;
439+ expect ( response . status ) . toBe ( 413 ) ;
440+ const json = await response . json ( ) ;
441+ expect ( json ) . toEqual ( {
442+ status : "error" ,
443+ error : { message : "request entity too large" } ,
444+ } ) ;
445+ } ) ;
446+
346447 test ( "Should fail when missing content type header" , async ( ) => {
347448 const response = await fetch ( `http://127.0.0.1:${ port } /v1/test` , {
348449 method : "POST" ,
@@ -452,7 +553,7 @@ describe("App in production mode", async () => {
452553 await setTimeout ( 500 ) ;
453554 process . emit ( "FAKE" as "SIGTERM" ) ;
454555 expect ( infoMethod ) . toHaveBeenCalledWith ( "Graceful shutdown" , {
455- sockets : 1 ,
556+ sockets : expect . any ( Number ) ,
456557 timeout : 1000 ,
457558 } ) ;
458559 await setTimeout ( 1500 ) ;
0 commit comments