1
- import { describe , expect , it } from 'vitest' ;
1
+ import { beforeEach , describe , expect , it , vi } from 'vitest' ;
2
2
3
3
import { shouldRetryTurnstileErrorCode } from '../captcha/turnstile' ;
4
+ import type { CaptchaOptions } from '../captcha/types' ;
4
5
5
6
describe ( 'shouldRetryTurnstileErrorCode' , ( ) => {
6
7
it . each ( [
@@ -23,3 +24,229 @@ describe('shouldRetryTurnstileErrorCode', () => {
23
24
expect ( shouldRetryTurnstileErrorCode ( str ) ) . toBe ( expected ) ;
24
25
} ) ;
25
26
} ) ;
27
+
28
+ describe ( 'Nonce support' , ( ) => {
29
+ beforeEach ( ( ) => {
30
+ vi . clearAllMocks ( ) ;
31
+ vi . resetModules ( ) ;
32
+ } ) ;
33
+
34
+ describe ( 'retrieveCaptchaInfo' , ( ) => {
35
+ it ( 'should extract nonce from clerk options when available' , async ( ) => {
36
+ // Mock clerk instance with internal options
37
+ const mockClerk = {
38
+ __unstable__environment : {
39
+ displayConfig : {
40
+ captchaProvider : 'turnstile' ,
41
+ captchaPublicKey : 'test-site-key' ,
42
+ captchaWidgetType : 'managed' ,
43
+ captchaPublicKeyInvisible : 'test-invisible-key' ,
44
+ } ,
45
+ userSettings : {
46
+ signUp : {
47
+ captcha_enabled : true ,
48
+ } ,
49
+ } ,
50
+ } ,
51
+ isStandardBrowser : true ,
52
+ __internal_getOption : vi . fn ( ) . mockReturnValue ( 'test-nonce-123' ) ,
53
+ } ;
54
+
55
+ const { retrieveCaptchaInfo } = await import ( '../captcha/retrieveCaptchaInfo' ) ;
56
+ const result = retrieveCaptchaInfo ( mockClerk as any ) ;
57
+
58
+ expect ( mockClerk . __internal_getOption ) . toHaveBeenCalledWith ( 'nonce' ) ;
59
+ expect ( result . nonce ) . toBe ( 'test-nonce-123' ) ;
60
+ expect ( result . captchaSiteKey ) . toBe ( 'test-site-key' ) ;
61
+ expect ( result . captchaProvider ) . toBe ( 'turnstile' ) ;
62
+ } ) ;
63
+
64
+ it ( 'should return undefined nonce when not available in clerk options' , async ( ) => {
65
+ const mockClerk = {
66
+ __unstable__environment : {
67
+ displayConfig : {
68
+ captchaProvider : 'turnstile' ,
69
+ captchaPublicKey : 'test-site-key' ,
70
+ captchaWidgetType : 'managed' ,
71
+ captchaPublicKeyInvisible : 'test-invisible-key' ,
72
+ } ,
73
+ userSettings : {
74
+ signUp : {
75
+ captcha_enabled : true ,
76
+ } ,
77
+ } ,
78
+ } ,
79
+ isStandardBrowser : true ,
80
+ __internal_getOption : vi . fn ( ) . mockReturnValue ( undefined ) ,
81
+ } ;
82
+
83
+ const { retrieveCaptchaInfo } = await import ( '../captcha/retrieveCaptchaInfo' ) ;
84
+ const result = retrieveCaptchaInfo ( mockClerk as any ) ;
85
+
86
+ expect ( result . nonce ) . toBeUndefined ( ) ;
87
+ } ) ;
88
+
89
+ it ( 'should handle clerk instance without __internal_getOption method' , async ( ) => {
90
+ const mockClerk = {
91
+ __unstable__environment : {
92
+ displayConfig : {
93
+ captchaProvider : 'turnstile' ,
94
+ captchaPublicKey : 'test-site-key' ,
95
+ captchaWidgetType : 'managed' ,
96
+ captchaPublicKeyInvisible : 'test-invisible-key' ,
97
+ } ,
98
+ userSettings : {
99
+ signUp : {
100
+ captcha_enabled : true ,
101
+ } ,
102
+ } ,
103
+ } ,
104
+ isStandardBrowser : true ,
105
+ // No __internal_getOption method
106
+ } ;
107
+
108
+ const { retrieveCaptchaInfo } = await import ( '../captcha/retrieveCaptchaInfo' ) ;
109
+ const result = retrieveCaptchaInfo ( mockClerk as any ) ;
110
+
111
+ expect ( result . nonce ) . toBeUndefined ( ) ;
112
+ } ) ;
113
+ } ) ;
114
+
115
+ describe ( 'CaptchaOptions type support' , ( ) => {
116
+ it ( 'should accept nonce in CaptchaOptions type definition' , ( ) => {
117
+ // This test verifies that the CaptchaOptions type includes the nonce field
118
+ const validOptions : CaptchaOptions = {
119
+ action : 'signup' ,
120
+ captchaProvider : 'turnstile' ,
121
+ closeModal : async ( ) => { } ,
122
+ invisibleSiteKey : 'test-invisible-key' ,
123
+ modalContainerQuerySelector : '.modal' ,
124
+ modalWrapperQuerySelector : '.wrapper' ,
125
+ nonce : 'test-nonce-from-csp' ,
126
+ openModal : async ( ) => { } ,
127
+ siteKey : 'test-site-key' ,
128
+ widgetType : 'invisible' ,
129
+ } ;
130
+
131
+ // If this compiles without TypeScript errors, the test passes
132
+ expect ( validOptions . nonce ) . toBe ( 'test-nonce-from-csp' ) ;
133
+ } ) ;
134
+
135
+ it ( 'should allow undefined nonce in CaptchaOptions' , ( ) => {
136
+ const validOptionsWithoutNonce : CaptchaOptions = {
137
+ action : 'signup' ,
138
+ captchaProvider : 'turnstile' ,
139
+ invisibleSiteKey : 'test-invisible-key' ,
140
+ siteKey : 'test-site-key' ,
141
+ widgetType : 'invisible' ,
142
+ // nonce is optional
143
+ } ;
144
+
145
+ expect ( validOptionsWithoutNonce . nonce ) . toBeUndefined ( ) ;
146
+ } ) ;
147
+ } ) ;
148
+
149
+ describe ( 'CaptchaChallenge nonce integration' , ( ) => {
150
+ let mockClerk : any ;
151
+
152
+ beforeEach ( async ( ) => {
153
+ // Mock clerk instance
154
+ mockClerk = {
155
+ __unstable__environment : {
156
+ displayConfig : {
157
+ captchaProvider : 'turnstile' ,
158
+ captchaPublicKey : 'test-site-key' ,
159
+ captchaWidgetType : 'managed' ,
160
+ captchaPublicKeyInvisible : 'test-invisible-key' ,
161
+ } ,
162
+ userSettings : {
163
+ signUp : {
164
+ captcha_enabled : true ,
165
+ } ,
166
+ } ,
167
+ } ,
168
+ isStandardBrowser : true ,
169
+ __internal_getOption : vi . fn ( ) . mockReturnValue ( 'clerk-nonce-789' ) ,
170
+ } ;
171
+
172
+ // Mock getCaptchaToken
173
+ vi . doMock ( '../captcha/getCaptchaToken' , ( ) => ( {
174
+ getCaptchaToken : vi . fn ( ) . mockResolvedValue ( {
175
+ captchaToken : 'mock-token' ,
176
+ captchaWidgetType : 'invisible' ,
177
+ } ) ,
178
+ } ) ) ;
179
+ } ) ;
180
+
181
+ it ( 'should use nonce from clerk options in invisible challenge' , async ( ) => {
182
+ const { getCaptchaToken } = await import ( '../captcha/getCaptchaToken' ) ;
183
+ const { CaptchaChallenge } = await import ( '../captcha/CaptchaChallenge' ) ;
184
+
185
+ const challenge = new CaptchaChallenge ( mockClerk ) ;
186
+ await challenge . invisible ( { action : 'signup' } ) ;
187
+
188
+ expect ( getCaptchaToken ) . toHaveBeenCalledWith (
189
+ expect . objectContaining ( {
190
+ nonce : 'clerk-nonce-789' ,
191
+ captchaProvider : 'turnstile' ,
192
+ siteKey : 'test-invisible-key' ,
193
+ widgetType : 'invisible' ,
194
+ } ) ,
195
+ ) ;
196
+ } ) ;
197
+
198
+ it ( 'should use nonce from clerk options in managedOrInvisible challenge' , async ( ) => {
199
+ const { getCaptchaToken } = await import ( '../captcha/getCaptchaToken' ) ;
200
+ const { CaptchaChallenge } = await import ( '../captcha/CaptchaChallenge' ) ;
201
+
202
+ const challenge = new CaptchaChallenge ( mockClerk ) ;
203
+ await challenge . managedOrInvisible ( { action : 'verify' } ) ;
204
+
205
+ expect ( getCaptchaToken ) . toHaveBeenCalledWith (
206
+ expect . objectContaining ( {
207
+ nonce : 'clerk-nonce-789' ,
208
+ captchaProvider : 'turnstile' ,
209
+ siteKey : 'test-site-key' ,
210
+ widgetType : 'managed' ,
211
+ } ) ,
212
+ ) ;
213
+ } ) ;
214
+
215
+ it ( 'should prefer explicit nonce over clerk options nonce' , async ( ) => {
216
+ const { getCaptchaToken } = await import ( '../captcha/getCaptchaToken' ) ;
217
+ const { CaptchaChallenge } = await import ( '../captcha/CaptchaChallenge' ) ;
218
+
219
+ const challenge = new CaptchaChallenge ( mockClerk ) ;
220
+ await challenge . invisible ( {
221
+ action : 'signup' ,
222
+ nonce : 'explicit-nonce-override' ,
223
+ } ) ;
224
+
225
+ expect ( getCaptchaToken ) . toHaveBeenCalledWith (
226
+ expect . objectContaining ( {
227
+ nonce : 'explicit-nonce-override' ,
228
+ } ) ,
229
+ ) ;
230
+ } ) ;
231
+
232
+ it ( 'should handle missing nonce gracefully' , async ( ) => {
233
+ // Mock clerk without nonce
234
+ const clerkWithoutNonce = {
235
+ ...mockClerk ,
236
+ __internal_getOption : vi . fn ( ) . mockReturnValue ( undefined ) ,
237
+ } ;
238
+
239
+ const { getCaptchaToken } = await import ( '../captcha/getCaptchaToken' ) ;
240
+ const { CaptchaChallenge } = await import ( '../captcha/CaptchaChallenge' ) ;
241
+
242
+ const challenge = new CaptchaChallenge ( clerkWithoutNonce ) ;
243
+ await challenge . invisible ( { action : 'signup' } ) ;
244
+
245
+ expect ( getCaptchaToken ) . toHaveBeenCalledWith (
246
+ expect . objectContaining ( {
247
+ nonce : undefined ,
248
+ } ) ,
249
+ ) ;
250
+ } ) ;
251
+ } ) ;
252
+ } ) ;
0 commit comments