@@ -797,6 +797,133 @@ 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+ expect ( response . headers . get ( "Cache-Control" ) ) . toBe ( "no-store" ) ;
851+ await expect ( response . json ( ) ) . resolves . toStrictEqual ( { cardId : "proc-active" , cardSecret : "secret-active" } ) ;
852+ expect ( panda . getProcessorDetails ) . toHaveBeenCalledWith ( walletCardId ) ;
853+ } ) ;
854+
855+ it ( "returns credentials for frozen card" , async ( ) => {
856+ vi . spyOn ( panda , "getProcessorDetails" ) . mockResolvedValueOnce ( {
857+ processorCardId : "proc-frozen" ,
858+ timeBasedSecret : "secret-frozen" ,
859+ } ) ;
860+
861+ const response = await appClient . wallet . $get ( { } , { headers : { "test-credential-id" : "wallet-frozen" } } ) ;
862+
863+ expect ( response . status ) . toBe ( 200 ) ;
864+ expect ( response . headers . get ( "Cache-Control" ) ) . toBe ( "no-store" ) ;
865+ await expect ( response . json ( ) ) . resolves . toStrictEqual ( { cardId : "proc-frozen" , cardSecret : "secret-frozen" } ) ;
866+ expect ( panda . getProcessorDetails ) . toHaveBeenCalledWith ( walletFrozenCardId ) ;
867+ } ) ;
868+
869+ it ( "returns 500 when panda api fails" , async ( ) => {
870+ vi . spyOn ( panda , "getProcessorDetails" ) . mockRejectedValueOnce ( new ServiceError ( "Rain" , 500 , "internal error" ) ) ;
871+
872+ const response = await appClient . wallet . $get ( { } , { headers : { "test-credential-id" : "wallet-active" } } ) ;
873+
874+ expect ( response . status ) . toBe ( 500 ) ;
875+ } ) ;
876+
877+ it ( "returns 404 when panda card is stale" , async ( ) => {
878+ vi . spyOn ( panda , "getProcessorDetails" ) . mockRejectedValueOnce ( new ServiceError ( "Panda" , 404 , "not found" ) ) ;
879+
880+ const response = await appClient . wallet . $get ( { } , { headers : { "test-credential-id" : "wallet-active" } } ) ;
881+
882+ expect ( response . status ) . toBe ( 404 ) ;
883+ await expect ( response . json ( ) ) . resolves . toStrictEqual ( { code : "no card" } ) ;
884+ expect ( panda . getProcessorDetails ) . toHaveBeenCalledWith ( walletCardId ) ;
885+ } ) ;
886+
887+ it ( "returns 403 when panda user is not approved" , async ( ) => {
888+ vi . spyOn ( panda , "getProcessorDetails" ) . mockRejectedValueOnce (
889+ new ServiceError (
890+ "Panda" ,
891+ 403 ,
892+ '{"message":"User exists but is not approved yet","error":"ForbiddenError","statusCode":403}' ,
893+ "ForbiddenError" ,
894+ "User exists but is not approved yet" ,
895+ ) ,
896+ ) ;
897+
898+ const response = await appClient . wallet . $get ( { } , { headers : { "test-credential-id" : "wallet-active" } } ) ;
899+
900+ expect ( response . status ) . toBe ( 403 ) ;
901+ await expect ( response . json ( ) ) . resolves . toStrictEqual ( { code : "no panda" } ) ;
902+ expect ( panda . getProcessorDetails ) . toHaveBeenCalledWith ( walletCardId ) ;
903+ } ) ;
904+
905+ it ( "returns 404 when only deleted card" , async ( ) => {
906+ const response = await appClient . wallet . $get ( { } , { headers : { "test-credential-id" : "wallet-no-card" } } ) ;
907+
908+ expect ( response . status ) . toBe ( 404 ) ;
909+ await expect ( response . json ( ) ) . resolves . toStrictEqual ( { code : "no card" } ) ;
910+ } ) ;
911+
912+ it ( "returns 403 when no panda customer" , async ( ) => {
913+ const response = await appClient . wallet . $get ( { } , { headers : { "test-credential-id" : "wallet-no-panda" } } ) ;
914+
915+ expect ( response . status ) . toBe ( 403 ) ;
916+ await expect ( response . json ( ) ) . resolves . toStrictEqual ( { code : "no panda" } ) ;
917+ } ) ;
918+
919+ it ( "returns 500 when credential not found" , async ( ) => {
920+ const response = await appClient . wallet . $get ( { } , { headers : { "test-credential-id" : "nonexistent" } } ) ;
921+
922+ expect ( response . status ) . toBe ( 500 ) ;
923+ await expect ( response . json ( ) ) . resolves . toStrictEqual ( { code : "no credential" } ) ;
924+ } ) ;
925+ } ) ;
926+
800927 describe ( "migration" , ( ) => {
801928 it ( "creates a panda card having a cm card with upgraded plugin" , async ( ) => {
802929 await database . insert ( cards ) . values ( [ { id : "cm" , credentialId : "default" , lastFour : "1234" } ] ) ;
0 commit comments