@@ -797,6 +797,131 @@ describe("authenticated", () => {
797797 expect ( card ?. status ) . toBe ( "DELETED" ) ;
798798 } ) ;
799799
800+ describe ( "wallet" , ( ) => {
801+ const walletCardId = "a1b2c3d4-e5f6-7890-abcd-ef1234567890" ;
802+ const walletFrozenCardId = "b2c3d4e5-f6a7-8901-bcde-f12345678901" ;
803+
804+ beforeAll ( async ( ) => {
805+ await database . insert ( credentials ) . values ( [
806+ {
807+ id : "wallet-active" ,
808+ publicKey : new Uint8Array ( ) ,
809+ account : padHex ( "0xaa01" , { size : 20 } ) ,
810+ factory : inject ( "ExaAccountFactory" ) ,
811+ pandaId : "wallet-active" ,
812+ } ,
813+ {
814+ id : "wallet-frozen" ,
815+ publicKey : new Uint8Array ( ) ,
816+ account : padHex ( "0xaa02" , { size : 20 } ) ,
817+ factory : inject ( "ExaAccountFactory" ) ,
818+ pandaId : "wallet-frozen" ,
819+ } ,
820+ {
821+ id : "wallet-no-card" ,
822+ publicKey : new Uint8Array ( ) ,
823+ account : padHex ( "0xaa03" , { size : 20 } ) ,
824+ factory : inject ( "ExaAccountFactory" ) ,
825+ pandaId : "wallet-no-card" ,
826+ } ,
827+ {
828+ id : "wallet-no-panda" ,
829+ publicKey : new Uint8Array ( ) ,
830+ account : padHex ( "0xaa04" , { size : 20 } ) ,
831+ factory : inject ( "ExaAccountFactory" ) ,
832+ } ,
833+ ] ) ;
834+ await database . insert ( cards ) . values ( [
835+ { id : walletCardId , credentialId : "wallet-active" , lastFour : "0001" } ,
836+ { id : walletFrozenCardId , credentialId : "wallet-frozen" , lastFour : "0002" , status : "FROZEN" } ,
837+ { id : "wallet-deleted" , credentialId : "wallet-no-card" , lastFour : "0003" , status : "DELETED" } ,
838+ ] ) ;
839+ } ) ;
840+
841+ it ( "returns credentials for active card" , async ( ) => {
842+ vi . spyOn ( panda , "getProcessorDetails" ) . mockResolvedValueOnce ( {
843+ processorCardId : "proc-active" ,
844+ timeBasedSecret : "secret-active" ,
845+ } ) ;
846+
847+ const response = await appClient . wallet . $get ( { } , { headers : { "test-credential-id" : "wallet-active" } } ) ;
848+
849+ expect ( response . status ) . toBe ( 200 ) ;
850+ await expect ( response . json ( ) ) . resolves . toStrictEqual ( { cardId : "proc-active" , cardSecret : "secret-active" } ) ;
851+ expect ( panda . getProcessorDetails ) . toHaveBeenCalledWith ( walletCardId ) ;
852+ } ) ;
853+
854+ it ( "returns credentials for frozen card" , async ( ) => {
855+ vi . spyOn ( panda , "getProcessorDetails" ) . mockResolvedValueOnce ( {
856+ processorCardId : "proc-frozen" ,
857+ timeBasedSecret : "secret-frozen" ,
858+ } ) ;
859+
860+ const response = await appClient . wallet . $get ( { } , { headers : { "test-credential-id" : "wallet-frozen" } } ) ;
861+
862+ expect ( response . status ) . toBe ( 200 ) ;
863+ await expect ( response . json ( ) ) . resolves . toStrictEqual ( { cardId : "proc-frozen" , cardSecret : "secret-frozen" } ) ;
864+ expect ( panda . getProcessorDetails ) . toHaveBeenCalledWith ( walletFrozenCardId ) ;
865+ } ) ;
866+
867+ it ( "returns 500 when panda api fails" , async ( ) => {
868+ vi . spyOn ( panda , "getProcessorDetails" ) . mockRejectedValueOnce ( new ServiceError ( "Rain" , 500 , "internal error" ) ) ;
869+
870+ const response = await appClient . wallet . $get ( { } , { headers : { "test-credential-id" : "wallet-active" } } ) ;
871+
872+ expect ( response . status ) . toBe ( 500 ) ;
873+ } ) ;
874+
875+ it ( "returns 404 when panda card is stale" , async ( ) => {
876+ vi . spyOn ( panda , "getProcessorDetails" ) . mockRejectedValueOnce ( new ServiceError ( "Panda" , 404 , "not found" ) ) ;
877+
878+ const response = await appClient . wallet . $get ( { } , { headers : { "test-credential-id" : "wallet-active" } } ) ;
879+
880+ expect ( response . status ) . toBe ( 404 ) ;
881+ await expect ( response . json ( ) ) . resolves . toStrictEqual ( { code : "no card" } ) ;
882+ expect ( panda . getProcessorDetails ) . toHaveBeenCalledWith ( walletCardId ) ;
883+ } ) ;
884+
885+ it ( "returns 403 when panda user is not approved" , async ( ) => {
886+ vi . spyOn ( panda , "getProcessorDetails" ) . mockRejectedValueOnce (
887+ new ServiceError (
888+ "Panda" ,
889+ 403 ,
890+ '{"message":"User exists but is not approved yet","error":"ForbiddenError","statusCode":403}' ,
891+ "ForbiddenError" ,
892+ "User exists but is not approved yet" ,
893+ ) ,
894+ ) ;
895+
896+ const response = await appClient . wallet . $get ( { } , { headers : { "test-credential-id" : "wallet-active" } } ) ;
897+
898+ expect ( response . status ) . toBe ( 403 ) ;
899+ await expect ( response . json ( ) ) . resolves . toStrictEqual ( { code : "no panda" } ) ;
900+ expect ( panda . getProcessorDetails ) . toHaveBeenCalledWith ( walletCardId ) ;
901+ } ) ;
902+
903+ it ( "returns 404 when only deleted card" , async ( ) => {
904+ const response = await appClient . wallet . $get ( { } , { headers : { "test-credential-id" : "wallet-no-card" } } ) ;
905+
906+ expect ( response . status ) . toBe ( 404 ) ;
907+ await expect ( response . json ( ) ) . resolves . toStrictEqual ( { code : "no card" } ) ;
908+ } ) ;
909+
910+ it ( "returns 403 when no panda customer" , async ( ) => {
911+ const response = await appClient . wallet . $get ( { } , { headers : { "test-credential-id" : "wallet-no-panda" } } ) ;
912+
913+ expect ( response . status ) . toBe ( 403 ) ;
914+ await expect ( response . json ( ) ) . resolves . toStrictEqual ( { code : "no panda" } ) ;
915+ } ) ;
916+
917+ it ( "returns 500 when credential not found" , async ( ) => {
918+ const response = await appClient . wallet . $get ( { } , { headers : { "test-credential-id" : "nonexistent" } } ) ;
919+
920+ expect ( response . status ) . toBe ( 500 ) ;
921+ await expect ( response . json ( ) ) . resolves . toStrictEqual ( { code : "no credential" } ) ;
922+ } ) ;
923+ } ) ;
924+
800925 describe ( "migration" , ( ) => {
801926 it ( "creates a panda card having a cm card with upgraded plugin" , async ( ) => {
802927 await database . insert ( cards ) . values ( [ { id : "cm" , credentialId : "default" , lastFour : "1234" } ] ) ;
0 commit comments