11import { initContract } from '@ts-rest/core' ;
22import { parse as parseMultipart , getBoundary } from 'parse-multipart-data' ;
33import { z } from 'zod' ;
4+ import { vi } from 'vitest' ;
45import { fetchRequestHandler } from './ts-rest-fetch' ;
6+ import { TsRestRequest } from '../request' ;
57
68const c = initContract ( ) ;
79
@@ -54,8 +56,17 @@ const contract = c.router({
5456 } ) ,
5557 } ,
5658 } ,
59+ throw : {
60+ method : 'GET' ,
61+ path : '/throw' ,
62+ responses : {
63+ 500 : c . noBody ( ) ,
64+ } ,
65+ } ,
5766} ) ;
5867
68+ const mockFn = vi . fn ( ) ;
69+
5970const testFetchRequestHandler = ( request : Request ) => {
6071 return fetchRequestHandler ( {
6172 contract,
@@ -77,11 +88,14 @@ const testFetchRequestHandler = (request: Request) => {
7788 } ,
7889 } ;
7990 } ,
80- noContent : async ( ) => {
81- return {
82- status : 204 ,
83- body : undefined ,
84- } ;
91+ noContent : {
92+ middleware : [ ( req ) => mockFn ( req . foo ) ] ,
93+ handler : async ( ) => {
94+ return {
95+ status : 204 ,
96+ body : undefined ,
97+ } as const ;
98+ } ,
8599 } ,
86100 upload : async ( _ , { request } ) => {
87101 const boundary = getBoundary (
@@ -97,20 +111,37 @@ const testFetchRequestHandler = (request: Request) => {
97111 body : blob ,
98112 } ;
99113 } ,
114+ throw : async ( ) => {
115+ throw new Error ( 'Test error' ) ;
116+ } ,
100117 } ,
101118 options : {
102119 jsonQuery : true ,
103120 responseValidation : true ,
104121 cors : {
105- origins : [ 'http://localhost' ] ,
122+ origin : [ 'http://localhost' ] ,
106123 credentials : true ,
107124 } ,
125+ requestMiddleware : [
126+ ( req : TsRestRequest & { foo : string } ) => {
127+ req . foo = 'bar' ;
128+ } ,
129+ ] ,
130+ responseHandlers : [
131+ ( res , req ) => {
132+ res . headers . set ( 'x-foo' , req . foo ) ;
133+ } ,
134+ ] ,
108135 } ,
109136 request,
110137 } ) ;
111138} ;
112139
113140describe ( 'fetchRequestHandler' , ( ) => {
141+ afterEach ( ( ) => {
142+ vi . clearAllMocks ( ) ;
143+ } ) ;
144+
114145 it ( 'should handle GET request' , async ( ) => {
115146 const request = new Request ( 'http://localhost/test?foo=bar' , {
116147 headers : { origin : 'http://localhost' } ,
@@ -123,6 +154,7 @@ describe('fetchRequestHandler', () => {
123154 'access-control-allow-origin' : 'http://localhost' ,
124155 'content-type' : 'application/json' ,
125156 vary : 'Origin' ,
157+ 'x-foo' : 'bar' ,
126158 } ,
127159 } ) ;
128160
@@ -148,6 +180,7 @@ describe('fetchRequestHandler', () => {
148180 'access-control-allow-origin' : 'http://localhost' ,
149181 'content-type' : 'application/json' ,
150182 vary : 'Origin' ,
183+ 'x-foo' : 'bar' ,
151184 } ,
152185 } ) ;
153186
@@ -169,12 +202,14 @@ describe('fetchRequestHandler', () => {
169202 'access-control-allow-credentials' : 'true' ,
170203 'access-control-allow-origin' : 'http://localhost' ,
171204 vary : 'Origin' ,
205+ 'x-foo' : 'bar' ,
172206 } ,
173207 } ) ;
174208
175209 expect ( response . status ) . toEqual ( expectedResponse . status ) ;
176210 expect ( response . headers ) . toEqual ( expectedResponse . headers ) ;
177211 expect ( await response . text ( ) ) . toEqual ( '' ) ;
212+ expect ( mockFn ) . toHaveBeenCalledWith ( 'bar' ) ;
178213 } ) ;
179214
180215 it ( 'should handle file upload' , async ( ) => {
@@ -203,6 +238,7 @@ describe('fetchRequestHandler', () => {
203238 'access-control-allow-origin' : 'http://localhost' ,
204239 'content-type' : 'text/html' ,
205240 vary : 'Origin' ,
241+ 'x-foo' : 'bar' ,
206242 } ,
207243 } ,
208244 ) ;
@@ -211,4 +247,52 @@ describe('fetchRequestHandler', () => {
211247 expect ( response . headers ) . toEqual ( expectedResponse . headers ) ;
212248 expect ( await response . text ( ) ) . toEqual ( await expectedResponse . text ( ) ) ;
213249 } ) ;
250+
251+ it ( 'should handle validation error' , async ( ) => {
252+ const request = new Request ( 'http://localhost/test' , {
253+ headers : { origin : 'http://localhost' } ,
254+ } ) ;
255+
256+ const response = await testFetchRequestHandler ( request ) ;
257+ const expectedResponse = new Response (
258+ '{"message":"Request validation failed","pathParameterErrors":null,"headerErrors":null,"queryParameterErrors":{"issues":[{"code":"invalid_type","expected":"string","received":"undefined","path":["foo"],"message":"Required"}],"name":"ZodError"},"bodyErrors":null}' ,
259+ {
260+ status : 400 ,
261+ headers : {
262+ 'access-control-allow-credentials' : 'true' ,
263+ 'access-control-allow-origin' : 'http://localhost' ,
264+ 'content-type' : 'application/json' ,
265+ vary : 'Origin' ,
266+ 'x-foo' : 'bar' ,
267+ } ,
268+ } ,
269+ ) ;
270+
271+ expect ( response . status ) . toEqual ( expectedResponse . status ) ;
272+ expect ( response . headers ) . toEqual ( expectedResponse . headers ) ;
273+ expect ( await response . json ( ) ) . toEqual ( await expectedResponse . json ( ) ) ;
274+ } ) ;
275+
276+ it ( 'should handle 500 response' , async ( ) => {
277+ const request = new Request ( 'http://localhost/throw' , {
278+ method : 'GET' ,
279+ headers : { origin : 'http://localhost' } ,
280+ } ) ;
281+
282+ const response = await testFetchRequestHandler ( request ) ;
283+ const expectedResponse = new Response ( null , {
284+ status : 500 ,
285+ headers : {
286+ 'access-control-allow-credentials' : 'true' ,
287+ 'access-control-allow-origin' : 'http://localhost' ,
288+ 'content-type' : 'application/json' ,
289+ vary : 'Origin' ,
290+ 'x-foo' : 'bar' ,
291+ } ,
292+ } ) ;
293+
294+ expect ( response . status ) . toEqual ( expectedResponse . status ) ;
295+ expect ( response . headers ) . toEqual ( expectedResponse . headers ) ;
296+ expect ( await response . json ( ) ) . toEqual ( { message : 'Server Error' } ) ;
297+ } ) ;
214298} ) ;
0 commit comments