@@ -51,6 +51,7 @@ jest.mock('@urbackend/common', () => {
5151 redis : {
5252 set : jest . fn ( ) . mockResolvedValue ( 'OK' ) ,
5353 get : jest . fn ( ) ,
54+ getdel : jest . fn ( ) ,
5455 del : jest . fn ( ) . mockResolvedValue ( 1 ) ,
5556 } ,
5657 Project : {
@@ -441,7 +442,7 @@ describe('public userAuth social auth', () => {
441442 } ) ;
442443
443444 test ( 'exchangeSocialRefreshToken returns refresh token and deletes exchange code' , async ( ) => {
444- redis . get . mockResolvedValueOnce ( JSON . stringify ( {
445+ redis . getdel . mockResolvedValueOnce ( JSON . stringify ( {
445446 token : 'issued_access_token' ,
446447 refreshToken : 'issued_refresh_token' ,
447448 } ) ) ;
@@ -455,8 +456,7 @@ describe('public userAuth social auth', () => {
455456
456457 await controller . exchangeSocialRefreshToken ( req , res ) ;
457458
458- expect ( redis . get ) . toHaveBeenCalledWith ( 'project:social-auth:refresh-exchange:code_123' ) ;
459- expect ( redis . del ) . toHaveBeenCalledWith ( 'project:social-auth:refresh-exchange:code_123' ) ;
459+ expect ( redis . getdel ) . toHaveBeenCalledWith ( 'project:social-auth:refresh-exchange:code_123' ) ;
460460 expect ( res . status ) . toHaveBeenCalledWith ( 200 ) ;
461461 expect ( res . json ) . toHaveBeenCalledWith ( {
462462 success : true ,
@@ -468,7 +468,7 @@ describe('public userAuth social auth', () => {
468468 } ) ;
469469
470470 test ( 'exchangeSocialRefreshToken rejects invalid or expired code' , async ( ) => {
471- redis . get . mockResolvedValueOnce ( null ) ;
471+ redis . getdel . mockResolvedValueOnce ( null ) ;
472472
473473 const req = makeReq ( ) ;
474474 req . body = {
@@ -482,12 +482,13 @@ describe('public userAuth social auth', () => {
482482 expect ( res . status ) . toHaveBeenCalledWith ( 400 ) ;
483483 expect ( res . json ) . toHaveBeenCalledWith ( {
484484 success : false ,
485+ data : { } ,
485486 message : 'Invalid or expired refresh token exchange code' ,
486487 } ) ;
487488 } ) ;
488489
489490 test ( 'exchangeSocialRefreshToken rejects mismatched token and deletes exchange code' , async ( ) => {
490- redis . get . mockResolvedValueOnce ( JSON . stringify ( {
491+ redis . getdel . mockResolvedValueOnce ( JSON . stringify ( {
491492 token : 'expected_access_token' ,
492493 refreshToken : 'issued_refresh_token' ,
493494 } ) ) ;
@@ -500,11 +501,10 @@ describe('public userAuth social auth', () => {
500501 const res = makeRes ( ) ;
501502
502503 await controller . exchangeSocialRefreshToken ( req , res ) ;
503-
504- expect ( redis . del ) . toHaveBeenCalledWith ( 'project:social-auth:refresh-exchange:code_456' ) ;
505504 expect ( res . status ) . toHaveBeenCalledWith ( 403 ) ;
506505 expect ( res . json ) . toHaveBeenCalledWith ( {
507506 success : false ,
507+ data : { } ,
508508 message : 'Invalid refresh token exchange payload' ,
509509 } ) ;
510510 } ) ;
@@ -611,4 +611,83 @@ describe('public userAuth social auth', () => {
611611 expect ( res . redirect ) . toHaveBeenCalledWith ( expect . stringContaining ( 'error=' ) ) ;
612612 expect ( res . redirect ) . toHaveBeenCalledWith ( expect . stringContaining ( 'not+verified' ) ) ;
613613 } ) ;
614+
615+ test ( 'handleSocialAuthCallback rejects soft-deleted user by provider id' , async ( ) => {
616+ redis . get . mockResolvedValueOnce ( JSON . stringify ( {
617+ projectId : 'project_1' ,
618+ provider : 'github' ,
619+ callbackUrl : 'http://localhost:5173/auth/callback' ,
620+ } ) ) ;
621+ mockProjectFindByIdChain . lean . mockResolvedValueOnce ( makeProject ( ) ) ;
622+
623+ // mock soft deleted user
624+ mockUsersModel . findOne . mockResolvedValueOnce ( {
625+ _id : 'deleted_user' ,
626+ githubId : '123' ,
627+ isDeleted : true ,
628+ deletedAt : new Date ( ) . toISOString ( )
629+ } ) ;
630+
631+ global . fetch
632+ . mockResolvedValueOnce ( {
633+ ok : true ,
634+ json : async ( ) => ( { access_token : 'github_access_token' } ) ,
635+ } )
636+ . mockResolvedValueOnce ( {
637+ ok : true ,
638+ json : async ( ) => ( { id : 123 , login : 'alice' , avatar_url : '' } ) ,
639+ } )
640+ . mockResolvedValueOnce ( {
641+ ok : true ,
642+ json : async ( ) => ( [ { email : 'alice@example.com' , primary : true , verified : true } ] ) ,
643+ } ) ;
644+
645+ const req = makeReq ( { params : { provider : 'github' } , query : { code : 'code_1' , state : 'state_1' } } ) ;
646+ const res = makeRes ( ) ;
647+
648+ await controller . handleSocialAuthCallback ( req , res ) ;
649+
650+ expect ( res . redirect ) . toHaveBeenCalledWith ( expect . stringContaining ( 'error=' ) ) ;
651+ expect ( res . redirect ) . toHaveBeenCalledWith ( expect . stringContaining ( 'deletion' ) ) ;
652+ } ) ;
653+
654+ test ( 'handleSocialAuthCallback rejects soft-deleted user by verified email' , async ( ) => {
655+ redis . get . mockResolvedValueOnce ( JSON . stringify ( {
656+ projectId : 'project_1' ,
657+ provider : 'github' ,
658+ callbackUrl : 'http://localhost:5173/auth/callback' ,
659+ } ) ) ;
660+ mockProjectFindByIdChain . lean . mockResolvedValueOnce ( makeProject ( ) ) ;
661+
662+ mockUsersModel . findOne
663+ . mockResolvedValueOnce ( null )
664+ . mockResolvedValueOnce ( {
665+ _id : 'deleted_user' ,
666+ email : 'alice@example.com' ,
667+ isDeleted : true ,
668+ deletedAt : new Date ( ) . toISOString ( )
669+ } ) ;
670+
671+ global . fetch
672+ . mockResolvedValueOnce ( {
673+ ok : true ,
674+ json : async ( ) => ( { access_token : 'github_access_token' } ) ,
675+ } )
676+ . mockResolvedValueOnce ( {
677+ ok : true ,
678+ json : async ( ) => ( { id : 123 , login : 'alice' , avatar_url : '' } ) ,
679+ } )
680+ . mockResolvedValueOnce ( {
681+ ok : true ,
682+ json : async ( ) => ( [ { email : 'alice@example.com' , verified : true , primary : true } ] ) ,
683+ } ) ;
684+
685+ const req = makeReq ( { params : { provider : 'github' } , query : { code : 'code_1' , state : 'state_1' } } ) ;
686+ const res = makeRes ( ) ;
687+
688+ await controller . handleSocialAuthCallback ( req , res ) ;
689+
690+ expect ( res . redirect ) . toHaveBeenCalledWith ( expect . stringContaining ( 'error=' ) ) ;
691+ expect ( res . redirect ) . toHaveBeenCalledWith ( expect . stringContaining ( 'deletion' ) ) ;
692+ } ) ;
614693} ) ;
0 commit comments