@@ -3,6 +3,7 @@ import { NextApiRequest, NextApiResponse } from 'next';
33import {
44 createNextRoute ,
55 createNextRouter ,
6+ createSingleRouteHandler ,
67 RequestValidationError ,
78} from './ts-rest-next' ;
89import { z } from 'zod' ;
@@ -41,6 +42,9 @@ const contract = c.router({
4142 responses : {
4243 200 : c . response < { id : string ; test : string } > ( ) ,
4344 } ,
45+ pathParams : z . object ( {
46+ id : z . string ( ) ,
47+ } ) ,
4448 } ,
4549 getZodQuery : {
4650 method : 'GET' ,
@@ -437,6 +441,168 @@ describe('createNextRouter', () => {
437441 } ) ;
438442} ) ;
439443
444+ describe ( 'createSingleUrlNextRouter' , ( ) => {
445+ beforeEach ( ( ) => {
446+ jest . clearAllMocks ( ) ;
447+ } ) ;
448+
449+ it ( 'should send back a 200' , async ( ) => {
450+ const resultingRouter = createSingleRouteHandler (
451+ contract . getWithParams ,
452+ nextEndpoint . getWithParams ,
453+ ) ;
454+
455+ const req = mockSingleUrlReq ( '/test/123' , {
456+ method : 'GET' ,
457+ query : { id : '123' } ,
458+ } ) ;
459+
460+ await resultingRouter ( req , mockRes ) ;
461+
462+ expect ( mockRes . status ) . toHaveBeenCalledWith ( 200 ) ;
463+ expect ( jsonMock ) . toHaveBeenCalledWith ( {
464+ id : '123' ,
465+ } ) ;
466+ } ) ;
467+
468+ it ( 'should send back a 404' , async ( ) => {
469+ const resultingRouter = createSingleRouteHandler (
470+ contract . getWithParams ,
471+ nextEndpoint . getWithParams ,
472+ ) ;
473+
474+ const req = mockSingleUrlReq ( '/wrong-url' , {
475+ method : 'GET' ,
476+ } ) ;
477+
478+ await resultingRouter ( req , mockRes ) ;
479+
480+ expect ( mockRes . status ) . toHaveBeenCalledWith ( 404 ) ;
481+ expect ( jsonMock ) . not . toHaveBeenCalled ( ) ;
482+ } ) ;
483+
484+ it ( 'should send back a 404' , async ( ) => {
485+ const resultingRouter = createSingleRouteHandler (
486+ contract . getWithParams ,
487+ nextEndpoint . getWithParams ,
488+ ) ;
489+
490+ const req = mockSingleUrlReq ( '/test' , {
491+ method : 'GET' ,
492+ } ) ;
493+
494+ await resultingRouter ( req , mockRes ) ;
495+
496+ expect ( mockRes . status ) . toHaveBeenCalledWith ( 404 ) ;
497+ expect ( jsonMock ) . not . toHaveBeenCalled ( ) ;
498+ } ) ;
499+
500+ it ( 'should send body, params and query correctly' , async ( ) => {
501+ const resultingRouter = createSingleRouteHandler (
502+ contract . advanced ,
503+ nextEndpoint . advanced ,
504+ ) ;
505+
506+ const req = mockSingleUrlReq ( '/advanced/123' , {
507+ method : 'POST' ,
508+ body : {
509+ test : 'test-body' ,
510+ } ,
511+ query : { id : '123' } ,
512+ } ) ;
513+
514+ await resultingRouter ( req , mockRes ) ;
515+
516+ expect ( mockRes . status ) . toHaveBeenCalledWith ( 200 ) ;
517+ expect ( jsonMock ) . toHaveBeenCalledWith ( {
518+ id : '123' ,
519+ test : 'test-body' ,
520+ } ) ;
521+ } ) ;
522+
523+ it ( 'should send json query correctly' , async ( ) => {
524+ const resultingRouter = createSingleRouteHandler (
525+ contract . getWithQuery ,
526+ nextEndpoint . getWithQuery ,
527+ { jsonQuery : true } ,
528+ ) ;
529+
530+ const req = mockSingleUrlReq ( '/test-query' , {
531+ method : 'GET' ,
532+ query : { test : '"test-query-string"' , foo : '123' } ,
533+ } ) ;
534+
535+ await resultingRouter ( req , mockRes ) ;
536+
537+ expect ( mockRes . status ) . toHaveBeenCalledWith ( 200 ) ;
538+ expect ( jsonMock ) . toHaveBeenCalledWith ( {
539+ test : 'test-query-string' ,
540+ foo : 123 ,
541+ } ) ;
542+ } ) ;
543+
544+ it ( 'should differentiate between /test and /test/id' , async ( ) => {
545+ const resultingRouter = createSingleRouteHandler (
546+ contract . getWithParams ,
547+ nextEndpoint . getWithParams ,
548+ ) ;
549+
550+ const req = mockSingleUrlReq ( '/test/3' , {
551+ method : 'GET' ,
552+ query : { id : '3' } ,
553+ } ) ;
554+
555+ await resultingRouter ( req , mockRes ) ;
556+
557+ expect ( mockRes . status ) . toHaveBeenCalledWith ( 200 ) ;
558+ expect ( jsonMock ) . toHaveBeenCalledWith ( {
559+ id : '3' ,
560+ } ) ;
561+ } ) ;
562+
563+ describe ( 'response validation' , ( ) => {
564+ it ( 'should include default value and removes extra field' , async ( ) => {
565+ const resultingRouter = createSingleRouteHandler (
566+ contract . getZodQuery ,
567+ nextEndpoint . getZodQuery ,
568+ { responseValidation : true } ,
569+ ) ;
570+
571+ const req = mockSingleUrlReq ( '/test/123/name' , {
572+ method : 'GET' ,
573+ query : { field : 'foo' , id : '123' , name : 'name' } ,
574+ } ) ;
575+
576+ await resultingRouter ( req , mockRes ) ;
577+
578+ expect ( mockRes . status ) . toHaveBeenCalledWith ( 200 ) ;
579+ expect ( jsonMock ) . toHaveBeenCalledWith ( {
580+ id : 123 ,
581+ name : 'name' ,
582+ defaultValue : 'hello world' ,
583+ } ) ;
584+ } ) ;
585+
586+ it ( 'fails with invalid field' , async ( ) => {
587+ const errorHandler = jest . fn ( ) ;
588+ const resultingRouter = createSingleRouteHandler (
589+ contract . getZodQuery ,
590+ nextEndpoint . getZodQuery ,
591+ { responseValidation : true , errorHandler } ,
592+ ) ;
593+
594+ const req = mockSingleUrlReq ( '/test/2000/name' , {
595+ method : 'GET' ,
596+ query : { id : '2000' , name : 'name' } ,
597+ } ) ;
598+
599+ await resultingRouter ( req , mockRes ) ;
600+
601+ expect ( errorHandler ) . toHaveBeenCalled ( ) ;
602+ } ) ;
603+ } ) ;
604+ } ) ;
605+
440606export const mockReq = (
441607 url : string ,
442608 args : {
@@ -458,3 +624,17 @@ export const mockReq = (
458624
459625 return req ;
460626} ;
627+
628+ const mockSingleUrlReq = (
629+ url : string ,
630+ args : { query ?: Record < string , unknown > ; body ?: unknown ; method : string } ,
631+ ) : NextApiRequest => {
632+ const req = {
633+ url,
634+ query : args . query ,
635+ body : args . body ,
636+ method : args . method ,
637+ } as unknown as NextApiRequest ;
638+
639+ return req ;
640+ } ;
0 commit comments