1- import { describe , it , expect , beforeEach , vi } from 'vitest' ;
1+ /**
2+ * Copyright (c) 2025 Bayan Flow
3+ * Licensed under Elastic License 2.0 OR Commercial
4+ * See LICENSE for details.
5+ */
6+
7+ import { describe , it , expect , beforeEach , afterEach , vi } from 'vitest' ;
28import {
39 mockSupabaseConfigured ,
410 resetSupabaseMocks ,
@@ -23,6 +29,13 @@ describe('authService', () => {
2329 vi . mocked ( requestGoogleSignInPopup ) . mockResolvedValue ( {
2430 idToken : 'google-id-token' ,
2531 } ) ;
32+ vi . stubEnv ( 'VITE_TURNSTILE_SITE_KEY' , '' ) ;
33+ delete globalThis . turnstile ;
34+ } ) ;
35+
36+ afterEach ( ( ) => {
37+ vi . unstubAllEnvs ( ) ;
38+ delete globalThis . turnstile ;
2639 } ) ;
2740
2841 it ( 'signInWithGoogleIdToken calls Supabase signInWithIdToken' , async ( ) => {
@@ -77,6 +90,23 @@ describe('authService', () => {
7790 } ) ;
7891 } ) ;
7992
93+ it ( 'signInWithGoogleIdToken includes Turnstile token in user metadata when provided' , async ( ) => {
94+ await authService . signInWithGoogleIdToken (
95+ 'token-123' ,
96+ undefined ,
97+ undefined ,
98+ 'turnstile-token'
99+ ) ;
100+
101+ expect ( supabaseAuthMock . signInWithIdToken ) . toHaveBeenCalledWith ( {
102+ provider : 'google' ,
103+ token : 'token-123' ,
104+ nonce : undefined ,
105+ access_token : undefined ,
106+ data : { cf_turnstile_response : 'turnstile-token' } ,
107+ } ) ;
108+ } ) ;
109+
80110 it ( 'signInWithGoogle uses GIS button then Supabase signInWithIdToken' , async ( ) => {
81111 await authService . signInWithGoogle ( ) ;
82112
@@ -89,6 +119,81 @@ describe('authService', () => {
89119 } ) ;
90120 } ) ;
91121
122+ it ( 'signInWithGoogle omits Turnstile metadata when site key is unset' , async ( ) => {
123+ await authService . signInWithGoogle ( ) ;
124+
125+ expect ( supabaseAuthMock . signInWithIdToken ) . toHaveBeenCalledWith ( {
126+ provider : 'google' ,
127+ token : 'google-id-token' ,
128+ nonce : undefined ,
129+ access_token : undefined ,
130+ } ) ;
131+ } ) ;
132+
133+ it ( 'signInWithGoogle passes Turnstile token when widget succeeds' , async ( ) => {
134+ vi . stubEnv ( 'VITE_TURNSTILE_SITE_KEY' , 'test-site-key' ) ;
135+ globalThis . turnstile = {
136+ render : vi . fn ( ( _container , options ) => {
137+ options . callback ( 'cf-turnstile-ok' ) ;
138+ return 'widget-id' ;
139+ } ) ,
140+ } ;
141+
142+ await authService . signInWithGoogle ( ) ;
143+
144+ expect ( supabaseAuthMock . signInWithIdToken ) . toHaveBeenCalledWith ( {
145+ provider : 'google' ,
146+ token : 'google-id-token' ,
147+ nonce : undefined ,
148+ access_token : undefined ,
149+ data : { cf_turnstile_response : 'cf-turnstile-ok' } ,
150+ } ) ;
151+ } ) ;
152+
153+ it ( 'signInWithGoogle throws when site key is set but Turnstile script is missing' , async ( ) => {
154+ vi . stubEnv ( 'VITE_TURNSTILE_SITE_KEY' , 'test-site-key' ) ;
155+
156+ await expect ( authService . signInWithGoogle ( ) ) . rejects . toThrow (
157+ / T u r n s t i l e i s n o t a v a i l a b l e /
158+ ) ;
159+ expect ( supabaseAuthMock . signInWithIdToken ) . not . toHaveBeenCalled ( ) ;
160+ } ) ;
161+
162+ it ( 'signInWithGoogle throws when Turnstile challenge errors' , async ( ) => {
163+ vi . stubEnv ( 'VITE_TURNSTILE_SITE_KEY' , 'test-site-key' ) ;
164+ globalThis . turnstile = {
165+ render : vi . fn ( ( _container , options ) => {
166+ options [ 'error-callback' ] ( ) ;
167+ return 'widget-id' ;
168+ } ) ,
169+ } ;
170+
171+ await expect ( authService . signInWithGoogle ( ) ) . rejects . toThrow (
172+ / T u r n s t i l e v e r i f i c a t i o n f a i l e d /
173+ ) ;
174+ expect ( supabaseAuthMock . signInWithIdToken ) . not . toHaveBeenCalled ( ) ;
175+ } ) ;
176+
177+ it ( 'signInWithGoogle throws when Turnstile challenge times out' , async ( ) => {
178+ vi . useFakeTimers ( ) ;
179+ try {
180+ vi . stubEnv ( 'VITE_TURNSTILE_SITE_KEY' , 'test-site-key' ) ;
181+ globalThis . turnstile = {
182+ render : vi . fn ( ( ) => 'widget-id' ) ,
183+ } ;
184+
185+ const signInPromise = authService . signInWithGoogle ( ) ;
186+ const rejection = expect ( signInPromise ) . rejects . toThrow (
187+ / T u r n s t i l e v e r i f i c a t i o n t i m e d o u t /
188+ ) ;
189+ await vi . advanceTimersByTimeAsync ( 10000 ) ;
190+ await rejection ;
191+ expect ( supabaseAuthMock . signInWithIdToken ) . not . toHaveBeenCalled ( ) ;
192+ } finally {
193+ vi . useRealTimers ( ) ;
194+ }
195+ } ) ;
196+
92197 it ( 'signOut delegates to Supabase auth' , async ( ) => {
93198 await authService . signOut ( ) ;
94199 expect ( supabaseAuthMock . signOut ) . toHaveBeenCalled ( ) ;
0 commit comments