@@ -134,6 +134,7 @@ describe('keyless-custom-headers', () => {
134
134
xPort : '3000' ,
135
135
xProtocol : 'https' ,
136
136
xClerkAuthStatus : 'signed-out' ,
137
+ isCI : true ,
137
138
} ;
138
139
139
140
const result = formatMetadataHeaders ( metadata ) ;
@@ -149,6 +150,7 @@ describe('keyless-custom-headers', () => {
149
150
expect ( result . get ( 'Clerk-X-Port' ) ) . toBe ( '3000' ) ;
150
151
expect ( result . get ( 'Clerk-X-Protocol' ) ) . toBe ( 'https' ) ;
151
152
expect ( result . get ( 'Clerk-Auth-Status' ) ) . toBe ( 'signed-out' ) ;
153
+ expect ( result . get ( 'Clerk-Is-CI' ) ) . toBe ( 'true' ) ;
152
154
} ) ;
153
155
154
156
it ( 'should handle missing optional fields gracefully' , ( ) => {
@@ -159,6 +161,7 @@ describe('keyless-custom-headers', () => {
159
161
xPort : '3000' ,
160
162
xProtocol : 'https' ,
161
163
xClerkAuthStatus : 'signed-out' ,
164
+ isCI : false ,
162
165
// Missing: nodeVersion, nextVersion, npmConfigUserAgent, port
163
166
} ;
164
167
@@ -177,6 +180,7 @@ describe('keyless-custom-headers', () => {
177
180
expect ( result . get ( 'Clerk-Next-Version' ) ) . toBeNull ( ) ;
178
181
expect ( result . get ( 'Clerk-NPM-Config-User-Agent' ) ) . toBeNull ( ) ;
179
182
expect ( result . get ( 'Clerk-Node-Port' ) ) . toBeNull ( ) ;
183
+ expect ( result . get ( 'Clerk-Is-CI' ) ) . toBeNull ( ) ;
180
184
} ) ;
181
185
182
186
it ( 'should handle undefined values for optional fields' , ( ) => {
@@ -191,6 +195,7 @@ describe('keyless-custom-headers', () => {
191
195
xPort : 'test-x-port' ,
192
196
xProtocol : 'test-x-protocol' ,
193
197
xClerkAuthStatus : 'test-auth-status' ,
198
+ isCI : false ,
194
199
} ;
195
200
196
201
const result = formatMetadataHeaders ( metadata ) ;
@@ -208,6 +213,7 @@ describe('keyless-custom-headers', () => {
208
213
expect ( result . get ( 'Clerk-X-Port' ) ) . toBe ( 'test-x-port' ) ;
209
214
expect ( result . get ( 'Clerk-X-Protocol' ) ) . toBe ( 'test-x-protocol' ) ;
210
215
expect ( result . get ( 'Clerk-Auth-Status' ) ) . toBe ( 'test-auth-status' ) ;
216
+ expect ( result . get ( 'Clerk-Is-CI' ) ) . toBeNull ( ) ;
211
217
} ) ;
212
218
213
219
it ( 'should handle empty string values' , ( ) => {
@@ -222,6 +228,7 @@ describe('keyless-custom-headers', () => {
222
228
xPort : '' ,
223
229
xProtocol : '' ,
224
230
xClerkAuthStatus : '' ,
231
+ isCI : false ,
225
232
} ;
226
233
227
234
const result = formatMetadataHeaders ( metadata ) ;
@@ -237,6 +244,7 @@ describe('keyless-custom-headers', () => {
237
244
expect ( result . get ( 'Clerk-X-Port' ) ) . toBeNull ( ) ;
238
245
expect ( result . get ( 'Clerk-X-Protocol' ) ) . toBeNull ( ) ;
239
246
expect ( result . get ( 'Clerk-Auth-Status' ) ) . toBeNull ( ) ;
247
+ expect ( result . get ( 'Clerk-Is-CI' ) ) . toBeNull ( ) ;
240
248
} ) ;
241
249
} ) ;
242
250
@@ -334,6 +342,7 @@ describe('keyless-custom-headers', () => {
334
342
xHost : 'example.com' ,
335
343
xProtocol : 'https' ,
336
344
xClerkAuthStatus : 'signed-out' ,
345
+ isCI : false ,
337
346
} ) ;
338
347
339
348
// Restore original values
@@ -419,6 +428,88 @@ describe('keyless-custom-headers', () => {
419
428
expect ( result . xHost ) . toBe ( 'forwarded-test-host.example.com' ) ;
420
429
expect ( result . xProtocol ) . toBe ( 'https' ) ;
421
430
} ) ;
431
+
432
+ it ( 'should detect CI environment with truthy values' , async ( ) => {
433
+ const truthyValues = [ '1' , 'true' , '0.1' ] ;
434
+ const ciPlatforms = [ 'CI' , 'GITHUB_ACTIONS' , 'VERCEL' ] ;
435
+
436
+ for ( const platform of ciPlatforms ) {
437
+ for ( const value of truthyValues ) {
438
+ vi . unstubAllEnvs ( ) ;
439
+ vi . stubEnv ( platform , value ) ;
440
+
441
+ // Recreate headers mock for each iteration to avoid state pollution
442
+ mockHeaders . mockImplementation ( async ( ) => createMockHeaders ( ) ) ;
443
+
444
+ const result = await collectKeylessMetadata ( ) ;
445
+ expect ( result . isCI ) . toBe ( true ) ;
446
+ }
447
+ }
448
+ } ) ;
449
+
450
+ it ( 'should not detect CI environment with falsy values' , async ( ) => {
451
+ const falsyValues = [ '0' , 'false' , '' ] ;
452
+ const ciPlatforms = [ 'CI' , 'GITHUB_ACTIONS' ] ;
453
+
454
+ for ( const platform of ciPlatforms ) {
455
+ for ( const value of falsyValues ) {
456
+ vi . unstubAllEnvs ( ) ;
457
+ vi . stubEnv ( platform , value ) ;
458
+
459
+ // Recreate headers mock for each iteration to avoid state pollution
460
+ mockHeaders . mockImplementation ( async ( ) => createMockHeaders ( ) ) ;
461
+
462
+ const result = await collectKeylessMetadata ( ) ;
463
+ expect ( result . isCI ) . toBe ( false ) ;
464
+ }
465
+ }
466
+ } ) ;
467
+
468
+ it ( 'should not detect CI environment when no CI indicators are present' , async ( ) => {
469
+ // Clear all CI-related environment variables
470
+ vi . stubEnv ( 'CI' , undefined ) ;
471
+ vi . stubEnv ( 'GITHUB_ACTIONS' , undefined ) ;
472
+ vi . stubEnv ( 'VERCEL' , undefined ) ;
473
+ vi . stubEnv ( 'NETLIFY' , undefined ) ;
474
+ vi . stubEnv ( 'CIRCLECI' , undefined ) ;
475
+ vi . stubEnv ( 'TRAVIS' , undefined ) ;
476
+ vi . stubEnv ( 'JENKINS_URL' , undefined ) ;
477
+
478
+ const result = await collectKeylessMetadata ( ) ;
479
+
480
+ expect ( result . isCI ) . toBe ( false ) ;
481
+ } ) ;
482
+
483
+ it ( 'should only add Clerk-Is-CI header when isCI is true' , ( ) => {
484
+ const metadataWithCI = {
485
+ userAgent : 'test-user-agent' ,
486
+ host : 'test-host' ,
487
+ xHost : 'test-x-host' ,
488
+ xPort : 'test-x-port' ,
489
+ xProtocol : 'test-x-protocol' ,
490
+ xClerkAuthStatus : 'test-auth-status' ,
491
+ isCI : true ,
492
+ } ;
493
+
494
+ const metadataWithoutCI = {
495
+ userAgent : 'test-user-agent' ,
496
+ host : 'test-host' ,
497
+ xHost : 'test-x-host' ,
498
+ xPort : 'test-x-port' ,
499
+ xProtocol : 'test-x-protocol' ,
500
+ xClerkAuthStatus : 'test-auth-status' ,
501
+ isCI : false ,
502
+ } ;
503
+
504
+ const resultWithCI = formatMetadataHeaders ( metadataWithCI ) ;
505
+ const resultWithoutCI = formatMetadataHeaders ( metadataWithoutCI ) ;
506
+
507
+ // When isCI is true, header should be set to 'true'
508
+ expect ( resultWithCI . get ( 'Clerk-Is-CI' ) ) . toBe ( 'true' ) ;
509
+
510
+ // When isCI is false, header should not be set
511
+ expect ( resultWithoutCI . get ( 'Clerk-Is-CI' ) ) . toBeNull ( ) ;
512
+ } ) ;
422
513
} ) ;
423
514
424
515
it ( 'should format metadata collected from collectKeylessMetadata correctly' , async ( ) => {
@@ -475,5 +566,67 @@ describe('keyless-custom-headers', () => {
475
566
expect ( headers . get ( 'Clerk-X-Protocol' ) ) . toBe ( 'https' ) ;
476
567
expect ( headers . get ( 'Clerk-Auth-Status' ) ) . toBe ( 'integration-status' ) ;
477
568
expect ( headers . get ( 'Clerk-NPM-Config-User-Agent' ) ) . toBe ( 'test-npm-agent' ) ;
569
+ expect ( headers . get ( 'Clerk-Is-CI' ) ) . toBeNull ( ) ; // Should be null when no CI environment is detected
570
+ } ) ;
571
+
572
+ it ( 'should format metadata with CI environment detected correctly' , async ( ) => {
573
+ // Reset mock to ensure clean state from previous test
574
+ mockHeaders . mockReset ( ) ;
575
+
576
+ // Setup environment with CI detection
577
+ vi . stubEnv ( 'PORT' , '4000' ) ;
578
+ vi . stubEnv ( 'npm_config_user_agent' , 'test-npm-agent' ) ;
579
+ vi . stubEnv ( 'CI' , '1' ) ; // Set CI environment variable
580
+
581
+ const mockHeaderStore = new Headers ( {
582
+ 'User-Agent' : 'Integration-Test-Agent' ,
583
+ host : 'localhost:4000' ,
584
+ 'x-forwarded-port' : '4000' ,
585
+ 'x-forwarded-host' : 'integration-forwarded-host' ,
586
+ 'x-forwarded-proto' : 'https' ,
587
+ 'x-clerk-auth-status' : 'integration-status' ,
588
+ } ) ;
589
+
590
+ mockHeaders . mockResolvedValue ( {
591
+ get : ( key : string ) => mockHeaderStore . get ( key ) || null ,
592
+ has : ( key : string ) => mockHeaderStore . has ( key ) ,
593
+ forEach : ( ) => { } ,
594
+ entries : function * ( ) {
595
+ const headerEntries : [ string , string ] [ ] = [ ] ;
596
+ mockHeaderStore . forEach ( ( value , key ) => headerEntries . push ( [ key , value ] ) ) ;
597
+ for ( const entry of headerEntries ) {
598
+ yield entry ;
599
+ }
600
+ } ,
601
+ keys : function * ( ) {
602
+ const headerKeys : string [ ] = [ ] ;
603
+ mockHeaderStore . forEach ( ( _ , key ) => headerKeys . push ( key ) ) ;
604
+ for ( const key of headerKeys ) {
605
+ yield key ;
606
+ }
607
+ } ,
608
+ values : function * ( ) {
609
+ const headerValues : string [ ] = [ ] ;
610
+ mockHeaderStore . forEach ( value => headerValues . push ( value ) ) ;
611
+ for ( const value of headerValues ) {
612
+ yield value ;
613
+ }
614
+ } ,
615
+ } as MockHeaders ) ;
616
+
617
+ // Collect metadata and format headers
618
+ const metadata = await collectKeylessMetadata ( ) ;
619
+ const headers = formatMetadataHeaders ( metadata ) ;
620
+
621
+ // Verify the full pipeline works correctly with CI detection
622
+ expect ( headers . get ( 'Clerk-Client-User-Agent' ) ) . toBe ( 'Integration-Test-Agent' ) ;
623
+ expect ( headers . get ( 'Clerk-Client-Host' ) ) . toBe ( 'localhost:4000' ) ;
624
+ expect ( headers . get ( 'Clerk-Node-Port' ) ) . toBe ( '4000' ) ;
625
+ expect ( headers . get ( 'Clerk-X-Port' ) ) . toBe ( '4000' ) ;
626
+ expect ( headers . get ( 'Clerk-X-Host' ) ) . toBe ( 'integration-forwarded-host' ) ;
627
+ expect ( headers . get ( 'Clerk-X-Protocol' ) ) . toBe ( 'https' ) ;
628
+ expect ( headers . get ( 'Clerk-Auth-Status' ) ) . toBe ( 'integration-status' ) ;
629
+ expect ( headers . get ( 'Clerk-NPM-Config-User-Agent' ) ) . toBe ( 'test-npm-agent' ) ;
630
+ expect ( headers . get ( 'Clerk-Is-CI' ) ) . toBe ( 'true' ) ; // Should be 'true' when CI environment is detected
478
631
} ) ;
479
632
} ) ;
0 commit comments