@@ -4,6 +4,12 @@ import { z } from 'zod';
44import fastify from 'fastify' ;
55import * as supertest from 'supertest' ;
66
7+ declare module 'fastify' {
8+ interface FastifyReply {
9+ errorMessage ?: string ;
10+ }
11+ }
12+
713const c = initContract ( ) ;
814
915const contract = c . router ( {
@@ -793,4 +799,262 @@ describe('ts-rest-fastify', () => {
793799 expect ( res . body ) . toEqual ( { message : 'Not found' } ) ;
794800 } ) ;
795801 } ) ;
802+
803+ it ( 'should be able to use a hook on a single endpoint' , async ( ) => {
804+ const contract = c . router ( {
805+ getMe : {
806+ method : 'GET' ,
807+ path : '/me' ,
808+ responses : { 200 : z . boolean ( ) } ,
809+ } ,
810+ } ) ;
811+
812+ const router = s . router ( contract , {
813+ getMe : {
814+ hooks : {
815+ preValidation : async ( request , reply ) => {
816+ reply . status ( 401 ) . send ( { message : 'Unauthorized' } ) ;
817+ } ,
818+ } ,
819+ async handler ( ) {
820+ return { status : 200 , body : true } ;
821+ } ,
822+ } ,
823+ } ) ;
824+
825+ const app = fastify ( ) ;
826+ app . register ( s . plugin ( router ) ) ;
827+
828+ await app . ready ( ) ;
829+
830+ const response = await supertest ( app . server ) . get ( '/me' ) ;
831+
832+ expect ( response . statusCode ) . toEqual ( 401 ) ;
833+ expect ( response . body ) . toEqual ( { message : 'Unauthorized' } ) ;
834+ } ) ;
835+
836+ it ( 'should be able to use array of hooks on a single endpoint' , async ( ) => {
837+ const contract = c . router ( {
838+ getMe : {
839+ method : 'GET' ,
840+ path : '/me' ,
841+ responses : { 200 : z . boolean ( ) } ,
842+ } ,
843+ } ) ;
844+
845+ const router = s . router ( contract , {
846+ getMe : {
847+ hooks : {
848+ preValidation : [
849+ async ( request , reply ) => {
850+ reply . errorMessage = 'Unauthorized' ;
851+ } ,
852+ async ( request , reply ) => {
853+ reply . status ( 401 ) . send ( { message : reply . errorMessage } ) ;
854+ } ,
855+ ] ,
856+ } ,
857+ async handler ( ) {
858+ return { status : 200 , body : true } ;
859+ } ,
860+ } ,
861+ } ) ;
862+
863+ const app = fastify ( ) ;
864+ app . register ( s . plugin ( router ) ) ;
865+
866+ await app . ready ( ) ;
867+
868+ const response = await supertest ( app . server ) . get ( '/me' ) ;
869+
870+ expect ( response . statusCode ) . toEqual ( 401 ) ;
871+ expect ( response . body ) . toEqual ( { message : 'Unauthorized' } ) ;
872+ } ) ;
873+
874+ it ( 'should be able to use multiple hooks on a single endpoint' , async ( ) => {
875+ let calledTimes = 0 ;
876+ const contract = c . router ( {
877+ getMe : {
878+ method : 'GET' ,
879+ path : '/me' ,
880+ responses : { 200 : z . boolean ( ) } ,
881+ } ,
882+ } ) ;
883+
884+ const router = s . router ( contract , {
885+ getMe : {
886+ hooks : {
887+ preValidation : async ( ) => {
888+ calledTimes += 1 ;
889+ } ,
890+ onRequest : [
891+ async ( ) => {
892+ calledTimes += 1 ;
893+ } ,
894+ ( _ , __ , done ) => {
895+ calledTimes += 1 ;
896+ done ( ) ;
897+ } ,
898+ ] ,
899+ } ,
900+ async handler ( ) {
901+ return { status : 200 , body : true } ;
902+ } ,
903+ } ,
904+ } ) ;
905+
906+ const app = fastify ( ) ;
907+ app . register ( s . plugin ( router ) ) ;
908+
909+ await app . ready ( ) ;
910+
911+ const response = await supertest ( app . server ) . get ( '/me' ) ;
912+
913+ expect ( response . statusCode ) . toEqual ( 200 ) ;
914+ expect ( response . body ) . toBeTruthy ( ) ;
915+ expect ( calledTimes ) . toEqual ( 3 ) ;
916+ } ) ;
917+
918+ it ( 'should be able to use a global hook' , async ( ) => {
919+ const contract = c . router ( {
920+ getMe : {
921+ method : 'GET' ,
922+ path : '/me' ,
923+ responses : { 200 : z . boolean ( ) } ,
924+ } ,
925+ } ) ;
926+
927+ const router = s . router ( contract , {
928+ getMe : {
929+ async handler ( ) {
930+ return { status : 200 , body : true } ;
931+ } ,
932+ } ,
933+ } ) ;
934+
935+ const fn = jest . fn ( ) ;
936+
937+ const app = fastify ( ) ;
938+ app . register ( s . plugin ( router ) , {
939+ hooks : {
940+ onRoute : async ( routeOptions ) => {
941+ fn ( {
942+ method : routeOptions . method ,
943+ path : routeOptions . config ?. tsRestRoute . path ,
944+ } ) ;
945+ } ,
946+ onRequest : async ( request , reply ) => {
947+ reply . status ( 401 ) . send ( { message : 'Unauthorized' } ) ;
948+ } ,
949+ } ,
950+ } ) ;
951+
952+ await app . ready ( ) ;
953+
954+ expect ( fn . mock . calls ) . toEqual ( [
955+ [ { method : 'GET' , path : '/me' } ] ,
956+ [ { method : 'HEAD' , path : '/me' } ] ,
957+ ] ) ;
958+
959+ const response = await supertest ( app . server ) . get ( '/me' ) ;
960+
961+ expect ( response . statusCode ) . toEqual ( 401 ) ;
962+ expect ( response . body ) . toEqual ( { message : 'Unauthorized' } ) ;
963+ } ) ;
964+
965+ it ( 'should be able to use a global hook array' , async ( ) => {
966+ const contract = c . router ( {
967+ getMe : {
968+ method : 'GET' ,
969+ path : '/me' ,
970+ responses : { 200 : z . boolean ( ) } ,
971+ } ,
972+ } ) ;
973+
974+ const router = s . router ( contract , {
975+ getMe : {
976+ async handler ( ) {
977+ return { status : 200 , body : true } ;
978+ } ,
979+ } ,
980+ } ) ;
981+
982+ const fn = jest . fn ( ) ;
983+
984+ const app = fastify ( ) ;
985+ app . decorateReply ( 'errorMessage' , undefined ) ;
986+ app . register ( s . plugin ( router ) , {
987+ hooks : {
988+ onRoute : [
989+ async ( routeOptions ) => {
990+ fn ( routeOptions . method ) ;
991+ } ,
992+ async ( routeOptions ) => {
993+ fn ( routeOptions . config ?. tsRestRoute . path ) ;
994+ } ,
995+ ] ,
996+ onRequest : [
997+ async ( request , reply ) => {
998+ reply . errorMessage = 'Unauthorized' ;
999+ } ,
1000+ async ( request , reply ) => {
1001+ reply . status ( 401 ) . send ( { message : reply . errorMessage } ) ;
1002+ } ,
1003+ ] ,
1004+ } ,
1005+ } ) ;
1006+
1007+ await app . ready ( ) ;
1008+
1009+ expect ( fn . mock . calls ) . toEqual ( [ [ 'GET' ] , [ '/me' ] , [ 'HEAD' ] , [ '/me' ] ] ) ;
1010+
1011+ const response = await supertest ( app . server ) . get ( '/me' ) ;
1012+
1013+ expect ( response . statusCode ) . toEqual ( 401 ) ;
1014+ expect ( response . body ) . toEqual ( { message : 'Unauthorized' } ) ;
1015+ } ) ;
1016+
1017+ it ( 'should be able to combine global hooks and route hooks' , async ( ) => {
1018+ let calledTimes = 0 ;
1019+ const contract = c . router ( {
1020+ getMe : {
1021+ method : 'GET' ,
1022+ path : '/me' ,
1023+ responses : { 200 : z . boolean ( ) } ,
1024+ } ,
1025+ } ) ;
1026+
1027+ const router = s . router ( contract , {
1028+ getMe : {
1029+ hooks : {
1030+ preValidation : async ( ) => {
1031+ calledTimes += 1 ;
1032+ } ,
1033+ } ,
1034+ async handler ( ) {
1035+ return { status : 200 , body : true } ;
1036+ } ,
1037+ } ,
1038+ } ) ;
1039+
1040+ const app = fastify ( ) ;
1041+ app . register ( s . plugin ( router ) , {
1042+ hooks : {
1043+ onRequest : async ( ) => {
1044+ calledTimes += 1 ;
1045+ } ,
1046+ preValidation : async ( ) => {
1047+ calledTimes += 1 ;
1048+ } ,
1049+ } ,
1050+ } ) ;
1051+
1052+ await app . ready ( ) ;
1053+
1054+ const response = await supertest ( app . server ) . get ( '/me' ) ;
1055+
1056+ expect ( response . statusCode ) . toEqual ( 200 ) ;
1057+ expect ( response . body ) . toBeTruthy ( ) ;
1058+ expect ( calledTimes ) . toEqual ( 3 ) ;
1059+ } ) ;
7961060} ) ;
0 commit comments