11import context from '@aws-lambda-powertools/testing-utils/context' ;
2+ import { AssertionError } from 'assert' ;
23import type { AppSyncResolverEvent , Context } from 'aws-lambda' ;
34import { beforeEach , describe , expect , it , vi } from 'vitest' ;
45import { AppSyncGraphQLResolver } from '../../../src/appsync-graphql/AppSyncGraphQLResolver.js' ;
@@ -8,6 +9,27 @@ import {
89} from '../../../src/appsync-graphql/index.js' ;
910import { onGraphqlEventFactory } from '../../helpers/factories.js' ;
1011
12+ class ValidationError extends Error {
13+ constructor ( message : string ) {
14+ super ( message ) ;
15+ this . name = 'ValidationError' ;
16+ }
17+ }
18+
19+ class NotFoundError extends Error {
20+ constructor ( message : string ) {
21+ super ( message ) ;
22+ this . name = 'NotFoundError' ;
23+ }
24+ }
25+
26+ class DatabaseError extends Error {
27+ constructor ( message : string ) {
28+ super ( message ) ;
29+ this . name = 'DatabaseError' ;
30+ }
31+ }
32+
1133describe ( 'Class: AppSyncGraphQLResolver' , ( ) => {
1234 beforeEach ( ( ) => {
1335 vi . clearAllMocks ( ) ;
@@ -706,4 +728,282 @@ describe('Class: AppSyncGraphQLResolver', () => {
706728 expect ( resultMutation ) . toEqual ( [ 'scoped' , 'scoped' ] ) ;
707729 }
708730 ) ;
731+
732+ // #region Exception Handling
733+
734+ it ( 'should register and use an exception handler for specific error types' , async ( ) => {
735+ // Prepare
736+ const app = new AppSyncGraphQLResolver ( ) ;
737+
738+ app . exceptionHandler ( ValidationError , async ( error ) => {
739+ return {
740+ message : 'Validation failed' ,
741+ details : error . message ,
742+ type : 'validation_error' ,
743+ } ;
744+ } ) ;
745+
746+ app . onQuery < { id : string } > ( 'getUser' , async ( { id } ) => {
747+ if ( ! id ) {
748+ throw new ValidationError ( 'User ID is required' ) ;
749+ }
750+ return { id, name : 'John Doe' } ;
751+ } ) ;
752+
753+ // Act
754+ const result = await app . resolve (
755+ onGraphqlEventFactory ( 'getUser' , 'Query' , { } ) ,
756+ context
757+ ) ;
758+
759+ // Assess
760+ expect ( result ) . toEqual ( {
761+ message : 'Validation failed' ,
762+ details : 'User ID is required' ,
763+ type : 'validation_error' ,
764+ } ) ;
765+ } ) ;
766+
767+ it ( 'should handle multiple different error types with specific handlers' , async ( ) => {
768+ // Prepare
769+ const app = new AppSyncGraphQLResolver ( ) ;
770+
771+ app . exceptionHandler ( ValidationError , async ( error ) => {
772+ return {
773+ message : 'Validation failed' ,
774+ details : error . message ,
775+ type : 'validation_error' ,
776+ } ;
777+ } ) ;
778+
779+ app . exceptionHandler ( NotFoundError , async ( error ) => {
780+ return {
781+ message : 'Resource not found' ,
782+ details : error . message ,
783+ type : 'not_found_error' ,
784+ } ;
785+ } ) ;
786+
787+ app . onQuery < { id : string } > ( 'getUser' , async ( { id } ) => {
788+ if ( ! id ) {
789+ throw new ValidationError ( 'User ID is required' ) ;
790+ }
791+ if ( id === 'not-found' ) {
792+ throw new NotFoundError ( `User with ID ${ id } not found` ) ;
793+ }
794+ return { id, name : 'John Doe' } ;
795+ } ) ;
796+
797+ // Act
798+ const validationResult = await app . resolve (
799+ onGraphqlEventFactory ( 'getUser' , 'Query' , { } ) ,
800+ context
801+ ) ;
802+ const notFoundResult = await app . resolve (
803+ onGraphqlEventFactory ( 'getUser' , 'Query' , { id : 'not-found' } ) ,
804+ context
805+ ) ;
806+
807+ // Asses
808+ expect ( validationResult ) . toEqual ( {
809+ message : 'Validation failed' ,
810+ details : 'User ID is required' ,
811+ type : 'validation_error' ,
812+ } ) ;
813+ expect ( notFoundResult ) . toEqual ( {
814+ message : 'Resource not found' ,
815+ details : 'User with ID not-found not found' ,
816+ type : 'not_found_error' ,
817+ } ) ;
818+ } ) ;
819+
820+ it ( 'should prefer exact error class match over inheritance match' , async ( ) => {
821+ // Prepare
822+ const app = new AppSyncGraphQLResolver ( ) ;
823+
824+ app . exceptionHandler ( Error , async ( error ) => {
825+ return {
826+ message : 'Generic error occurred' ,
827+ details : error . message ,
828+ type : 'generic_error' ,
829+ } ;
830+ } ) ;
831+
832+ app . exceptionHandler ( ValidationError , async ( error ) => {
833+ return {
834+ message : 'Validation failed' ,
835+ details : error . message ,
836+ type : 'validation_error' ,
837+ } ;
838+ } ) ;
839+
840+ app . onQuery ( 'getUser' , async ( ) => {
841+ throw new ValidationError ( 'Specific validation error' ) ;
842+ } ) ;
843+
844+ // Act
845+ const result = await app . resolve (
846+ onGraphqlEventFactory ( 'getUser' , 'Query' , { } ) ,
847+ context
848+ ) ;
849+
850+ // Assess
851+ expect ( result ) . toEqual ( {
852+ message : 'Validation failed' ,
853+ details : 'Specific validation error' ,
854+ type : 'validation_error' ,
855+ } ) ;
856+ } ) ;
857+
858+ it ( 'should fall back to default error formatting when no exception handler is found' , async ( ) => {
859+ // Prepare
860+ const app = new AppSyncGraphQLResolver ( ) ;
861+
862+ app . exceptionHandler ( AssertionError , async ( error ) => {
863+ return {
864+ message : 'Validation failed' ,
865+ details : error . message ,
866+ type : 'validation_error' ,
867+ } ;
868+ } ) ;
869+
870+ app . onQuery ( 'getUser' , async ( ) => {
871+ throw new DatabaseError ( 'Database connection failed' ) ;
872+ } ) ;
873+
874+ // Act
875+ const result = await app . resolve (
876+ onGraphqlEventFactory ( 'getUser' , 'Query' , { } ) ,
877+ context
878+ ) ;
879+
880+ // Assess
881+ expect ( result ) . toEqual ( {
882+ error : 'DatabaseError - Database connection failed' ,
883+ } ) ;
884+ } ) ;
885+
886+ it ( 'should fall back to default error formatting when exception handler throws an error' , async ( ) => {
887+ // Prepare
888+ const app = new AppSyncGraphQLResolver ( { logger : console } ) ;
889+
890+ app . exceptionHandler ( ValidationError , async ( ) => {
891+ throw new Error ( 'Exception handler failed' ) ;
892+ } ) ;
893+
894+ app . onQuery ( 'getUser' , async ( ) => {
895+ throw new ValidationError ( 'Original error' ) ;
896+ } ) ;
897+
898+ // Act
899+ const result = await app . resolve (
900+ onGraphqlEventFactory ( 'getUser' , 'Query' , { } ) ,
901+ context
902+ ) ;
903+
904+ // Assess
905+ expect ( result ) . toEqual ( {
906+ error : 'ValidationError - Original error' ,
907+ } ) ;
908+ expect ( console . error ) . toHaveBeenNthCalledWith (
909+ 2 ,
910+ 'Exception handler for ValidationError threw an error' ,
911+ new Error ( 'Exception handler failed' )
912+ ) ;
913+ } ) ;
914+
915+ it ( 'should work with async exception handlers' , async ( ) => {
916+ // Prepare
917+ const app = new AppSyncGraphQLResolver ( ) ;
918+
919+ app . exceptionHandler ( ValidationError , async ( error ) => {
920+ return {
921+ message : 'Async validation failed' ,
922+ details : error . message ,
923+ type : 'async_validation_error' ,
924+ } ;
925+ } ) ;
926+
927+ app . onQuery ( 'getUser' , async ( ) => {
928+ throw new ValidationError ( 'Async error test' ) ;
929+ } ) ;
930+
931+ // Act
932+ const result = await app . resolve (
933+ onGraphqlEventFactory ( 'getUser' , 'Query' , { } ) ,
934+ context
935+ ) ;
936+
937+ // Assess
938+ expect ( result ) . toEqual ( {
939+ message : 'Async validation failed' ,
940+ details : 'Async error test' ,
941+ type : 'async_validation_error' ,
942+ } ) ;
943+ } ) ;
944+
945+ it ( 'should not interfere with ResolverNotFoundException' , async ( ) => {
946+ // Prepare
947+ const app = new AppSyncGraphQLResolver ( ) ;
948+
949+ app . exceptionHandler ( RangeError , async ( error ) => {
950+ return {
951+ message : 'This should not be called' ,
952+ details : error . message ,
953+ type : 'should_not_happen' ,
954+ } ;
955+ } ) ;
956+
957+ // Act & Assess
958+ await expect (
959+ app . resolve (
960+ onGraphqlEventFactory ( 'nonExistentResolver' , 'Query' ) ,
961+ context
962+ )
963+ ) . rejects . toThrow ( 'No resolver found for Query-nonExistentResolver' ) ;
964+ } ) ;
965+
966+ it ( 'should work as a method decorator' , async ( ) => {
967+ // Prepare
968+ const app = new AppSyncGraphQLResolver ( ) ;
969+
970+ class TestService {
971+ @app . exceptionHandler ( ValidationError )
972+ async handleValidationError ( error : ValidationError ) {
973+ return {
974+ message : 'Decorator validation failed' ,
975+ details : error . message ,
976+ type : 'decorator_validation_error' ,
977+ } ;
978+ }
979+
980+ @app . onQuery ( 'getUser' )
981+ async getUser ( ) {
982+ throw new ValidationError ( 'Decorator error test' ) ;
983+ }
984+
985+ async handler ( event : unknown , context : Context ) {
986+ return app . resolve ( event , context , {
987+ scope : this ,
988+ } ) ;
989+ }
990+ }
991+
992+ const service = new TestService ( ) ;
993+
994+ // Act
995+ const result = await service . handler (
996+ onGraphqlEventFactory ( 'getUser' , 'Query' , { } ) ,
997+ context
998+ ) ;
999+
1000+ // Assess
1001+ expect ( result ) . toEqual ( {
1002+ message : 'Decorator validation failed' ,
1003+ details : 'Decorator error test' ,
1004+ type : 'decorator_validation_error' ,
1005+ } ) ;
1006+ } ) ;
1007+
1008+ // #endregion Exception handling
7091009} ) ;
0 commit comments