@@ -14,7 +14,10 @@ describe('_authenticateWithTransport', () => {
1414 getRedirectUrl : vi . fn ( ) . mockResolvedValue ( 'myapp://sso-callback' ) ,
1515 open : vi . fn ( ) . mockResolvedValue ( { callbackUrl : 'myapp://sso-callback?rotating_token_nonce=abc' } ) ,
1616 } ;
17- const resource = { reload : vi . fn ( ) . mockResolvedValue ( undefined ) } as any ;
17+ const resource = {
18+ reload : vi . fn ( ) . mockResolvedValue ( undefined ) ,
19+ create : vi . fn ( ) . mockResolvedValue ( undefined ) ,
20+ } as any ;
1821 const authenticateMethod = vi . fn ( async ( _params , navigate ) => {
1922 navigate ( new URL ( 'https://provider.example/auth' ) ) ;
2023 } ) ;
@@ -36,6 +39,7 @@ describe('_authenticateWithTransport', () => {
3639 expect ( transport . open ) . toHaveBeenCalledWith ( new URL ( 'https://provider.example/auth' ) ) ;
3740 expect ( resource . reload ) . toHaveBeenCalledWith ( { rotatingTokenNonce : 'abc' } ) ;
3841 expect ( clerk . __internal_handleResourceCallback ) . toHaveBeenCalledWith ( resource , callbackParams ) ;
42+ expect ( resource . create ) . not . toHaveBeenCalled ( ) ;
3943 } ) ;
4044
4145 it ( 'reloads without a nonce when the callback URL has no nonce' , async ( ) => {
@@ -60,6 +64,104 @@ describe('_authenticateWithTransport', () => {
6064 expect ( clerk . __internal_handleResourceCallback ) . toHaveBeenCalledWith ( resource , { } ) ;
6165 } ) ;
6266
67+ it ( 'rejects with a localizable error without reloading when the native callback reports OAuth failure' , async ( ) => {
68+ const clerk = makeClerk ( ) ;
69+ const transport = {
70+ getRedirectUrl : vi . fn ( ) . mockResolvedValue ( 'myapp://sso-callback' ) ,
71+ open : vi . fn ( ) . mockResolvedValue ( {
72+ callbackUrl : 'myapp://sso-callback?__clerk_status=failed&__clerk_error_code=oauth_access_denied' ,
73+ } ) ,
74+ } ;
75+ const resource = {
76+ reload : vi . fn ( ) . mockResolvedValue ( undefined ) ,
77+ create : vi . fn ( ) . mockResolvedValue ( undefined ) ,
78+ } as any ;
79+ const authenticateMethod = vi . fn ( async ( _params , navigate ) => navigate ( 'https://provider.example/auth' ) ) ;
80+
81+ await expect (
82+ _authenticateWithTransport ( {
83+ clerk : clerk as any ,
84+ transport,
85+ resource,
86+ authenticateMethod,
87+ params : { } as any ,
88+ callbackParams : { } ,
89+ } ) ,
90+ ) . rejects . toMatchObject ( {
91+ code : 'oauth_access_denied' ,
92+ clerkRuntimeError : true ,
93+ } satisfies Partial < ClerkRuntimeError > ) ;
94+
95+ expect ( resource . create ) . toHaveBeenCalledWith ( { } ) ;
96+ expect ( resource . reload ) . not . toHaveBeenCalled ( ) ;
97+ expect ( clerk . __internal_handleResourceCallback ) . not . toHaveBeenCalled ( ) ;
98+ } ) ;
99+
100+ it ( 'uses a generic error code for unknown native OAuth callback failure codes' , async ( ) => {
101+ const clerk = makeClerk ( ) ;
102+ const transport = {
103+ getRedirectUrl : vi . fn ( ) . mockResolvedValue ( 'myapp://sso-callback' ) ,
104+ open : vi . fn ( ) . mockResolvedValue ( {
105+ callbackUrl : 'myapp://sso-callback?__clerk_status=failed&__clerk_error_code=reverification_cancelled' ,
106+ } ) ,
107+ } ;
108+ const resource = {
109+ reload : vi . fn ( ) . mockResolvedValue ( undefined ) ,
110+ create : vi . fn ( ) . mockResolvedValue ( undefined ) ,
111+ } as any ;
112+ const authenticateMethod = vi . fn ( async ( _params , navigate ) => navigate ( 'https://provider.example/auth' ) ) ;
113+
114+ await expect (
115+ _authenticateWithTransport ( {
116+ clerk : clerk as any ,
117+ transport,
118+ resource,
119+ authenticateMethod,
120+ params : { } as any ,
121+ callbackParams : { } ,
122+ } ) ,
123+ ) . rejects . toMatchObject ( {
124+ code : 'oauth_callback_failed' ,
125+ clerkRuntimeError : true ,
126+ } satisfies Partial < ClerkRuntimeError > ) ;
127+
128+ expect ( resource . create ) . toHaveBeenCalledWith ( { } ) ;
129+ expect ( resource . reload ) . not . toHaveBeenCalled ( ) ;
130+ expect ( clerk . __internal_handleResourceCallback ) . not . toHaveBeenCalled ( ) ;
131+ } ) ;
132+
133+ it ( 'still surfaces the OAuth failure when resetting the attempt fails' , async ( ) => {
134+ const clerk = makeClerk ( ) ;
135+ const transport = {
136+ getRedirectUrl : vi . fn ( ) . mockResolvedValue ( 'myapp://sso-callback' ) ,
137+ open : vi . fn ( ) . mockResolvedValue ( {
138+ callbackUrl : 'myapp://sso-callback?__clerk_status=failed&__clerk_error_code=oauth_access_denied' ,
139+ } ) ,
140+ } ;
141+ const resource = {
142+ reload : vi . fn ( ) . mockResolvedValue ( undefined ) ,
143+ create : vi . fn ( ) . mockRejectedValue ( new Error ( 'network down' ) ) ,
144+ } as any ;
145+ const authenticateMethod = vi . fn ( async ( _params , navigate ) => navigate ( 'https://provider.example/auth' ) ) ;
146+
147+ await expect (
148+ _authenticateWithTransport ( {
149+ clerk : clerk as any ,
150+ transport,
151+ resource,
152+ authenticateMethod,
153+ params : { } as any ,
154+ callbackParams : { } ,
155+ } ) ,
156+ ) . rejects . toMatchObject ( {
157+ code : 'oauth_access_denied' ,
158+ clerkRuntimeError : true ,
159+ } satisfies Partial < ClerkRuntimeError > ) ;
160+
161+ expect ( resource . create ) . toHaveBeenCalledWith ( { } ) ;
162+ expect ( clerk . __internal_handleResourceCallback ) . not . toHaveBeenCalled ( ) ;
163+ } ) ;
164+
63165 it ( 'propagates transport.open rejection and does not complete the callback' , async ( ) => {
64166 const clerk = makeClerk ( ) ;
65167 const transport = {
0 commit comments