21
21
using Microsoft . WebJobs . Script . Tests ;
22
22
using WebJobs . Script . Tests ;
23
23
using Xunit ;
24
+ using Xunit . Abstractions ;
24
25
25
26
namespace Microsoft . Azure . WebJobs . Script . Tests
26
27
{
@@ -30,10 +31,13 @@ public class SecretsRepositoryTests : IClassFixture<SecretsRepositoryTests.Fixtu
30
31
private readonly string functionName = "Test_test" ;
31
32
public static string KeyName = "Te!@#st!1-te_st" ;
32
33
33
- public SecretsRepositoryTests ( SecretsRepositoryTests . Fixture fixture )
34
+ private ITestOutputHelper _output ;
35
+
36
+ public SecretsRepositoryTests ( SecretsRepositoryTests . Fixture fixture , ITestOutputHelper outputHelper )
34
37
{
35
38
Utility . ColdStartDelayMS = 50 ;
36
39
_fixture = fixture ;
40
+ _output = outputHelper ;
37
41
}
38
42
39
43
public enum SecretsRepositoryType
@@ -378,35 +382,39 @@ public async Task BlobRepository_WriteAsync_DoesNot_ClearBlobContents(SecretsRep
378
382
using ( var directory = new TempDirectory ( ) )
379
383
{
380
384
await _fixture . TestInitialize ( repositoryType , directory . Path ) ;
385
+ string testFunctionName = "host" ;
386
+ var target = _fixture . GetNewSecretRepository ( ) ;
381
387
382
- ScriptSecrets testSecrets = new HostSecrets ( )
388
+ async Task RunTest ( )
383
389
{
384
- MasterKey = new Key ( "master" , "test" ) ,
385
- FunctionKeys = new List < Key > ( ) { new Key ( KeyName , "test" ) } ,
386
- SystemKeys = new List < Key > ( ) { new Key ( KeyName , "test" ) }
387
- } ;
388
-
389
- string testFunctionName = "host" ;
390
+ HostSecrets testSecrets = new HostSecrets ( )
391
+ {
392
+ MasterKey = new Key ( "master" , "test" ) ,
393
+ FunctionKeys = new List < Key > ( ) ,
394
+ SystemKeys = new List < Key > ( ) { new Key ( KeyName , "test" ) }
395
+ } ;
390
396
391
- var target = _fixture . GetNewSecretRepository ( ) ;
397
+ // make the payload larger to guarantee the race condition
398
+ for ( int i = 0 ; i < 1000 ; i ++ )
399
+ {
400
+ testSecrets . FunctionKeys . Add ( new Key ( KeyName + i , "test" ) ) ;
401
+ }
402
+ // Set up initial secrets.
403
+ await _fixture . WriteSecret ( testFunctionName , testSecrets ) ;
392
404
393
- // Set up initial secrets.
394
- await _fixture . WriteSecret ( testFunctionName , testSecrets ) ;
405
+ // Perform a write and read similtaneously. Previously, our usage of OpenWriteAsync
406
+ // would erase the content of the blob while writing, resulting in null secrets from the
407
+ // read.
408
+ Task writeTask = target . WriteAsync ( ScriptSecretsType . Host , testFunctionName , testSecrets ) ;
409
+ HostSecrets secretsContent = await target . ReadAsync ( ScriptSecretsType . Host , testFunctionName ) as HostSecrets ;
395
410
396
- // Perform a write and read similtaneously. Previously, our usage of OpenWriteAsync
397
- // would erase the content of the blob while writing, resulting in null secrets from the
398
- // read.
399
- Task writeTask = target . WriteAsync ( ScriptSecretsType . Host , testFunctionName , testSecrets ) ;
400
- HostSecrets secretsContent = await target . ReadAsync ( ScriptSecretsType . Host , testFunctionName ) as HostSecrets ;
411
+ await writeTask ;
401
412
402
- await writeTask ;
413
+ Assert . NotNull ( secretsContent ) ;
414
+ }
403
415
404
- Assert . Equal ( secretsContent . MasterKey . Name , "master" ) ;
405
- Assert . Equal ( secretsContent . MasterKey . Value , "test" ) ;
406
- Assert . Equal ( secretsContent . FunctionKeys [ 0 ] . Name , KeyName ) ;
407
- Assert . Equal ( secretsContent . FunctionKeys [ 0 ] . Value , "test" ) ;
408
- Assert . Equal ( secretsContent . SystemKeys [ 0 ] . Name , KeyName ) ;
409
- Assert . Equal ( secretsContent . SystemKeys [ 0 ] . Value , "test" ) ;
416
+ // this is a race so it may not fire every time; try several times
417
+ await TestHelpers . RetryFailedTest ( RunTest , 10 , _output ) ;
410
418
}
411
419
}
412
420
@@ -418,40 +426,50 @@ public async Task BlobRepository_SimultaneousWrites_Throws_PreconditionFailed(Se
418
426
using ( var directory = new TempDirectory ( ) )
419
427
{
420
428
await _fixture . TestInitialize ( repositoryType , directory . Path ) ;
429
+ var target = _fixture . GetNewSecretRepository ( ) ;
430
+ string testFunctionName = "host" ;
421
431
422
- HostSecrets testSecrets = new HostSecrets ( )
432
+ async Task RunTest ( )
423
433
{
424
- MasterKey = new Key ( "master" , "test" ) ,
425
- FunctionKeys = new List < Key > ( ) { new Key ( KeyName , "test" ) } ,
426
- SystemKeys = new List < Key > ( ) { new Key ( KeyName , "test" ) }
427
- } ;
434
+ HostSecrets testSecrets = new HostSecrets ( )
435
+ {
436
+ MasterKey = new Key ( "master" , "test" ) ,
437
+ FunctionKeys = new List < Key > ( ) ,
438
+ SystemKeys = new List < Key > ( ) { new Key ( KeyName , "test" ) }
439
+ } ;
428
440
429
- string testFunctionName = "host" ;
441
+ // make the payload larger to guarantee the race condition
442
+ for ( int i = 0 ; i < 1000 ; i ++ )
443
+ {
444
+ testSecrets . FunctionKeys . Add ( new Key ( KeyName + i , "test" ) ) ;
445
+ }
430
446
431
- var target = _fixture . GetNewSecretRepository ( ) ;
447
+ // Set up initial secrets.
448
+ await _fixture . WriteSecret ( testFunctionName , testSecrets ) ;
449
+ HostSecrets secretsContent = await target . ReadAsync ( ScriptSecretsType . Host , testFunctionName ) as HostSecrets ;
450
+ Assert . Equal ( "test" , secretsContent . FunctionKeys . First ( ) . Value ) ;
432
451
433
- // Set up initial secrets.
434
- await _fixture . WriteSecret ( testFunctionName , testSecrets ) ;
435
- HostSecrets secretsContent = await target . ReadAsync ( ScriptSecretsType . Host , testFunctionName ) as HostSecrets ;
436
- Assert . Equal ( "test" , secretsContent . FunctionKeys . Single ( ) . Value ) ;
452
+ testSecrets . FunctionKeys . First ( ) . Value = "changed" ;
437
453
438
- testSecrets . FunctionKeys . Single ( ) . Value = "changed" ;
454
+ // Simultaneous writes will result in one of the writes being discarded due to
455
+ // non-matching ETag.
456
+ Task writeTask1 = target . WriteAsync ( ScriptSecretsType . Host , testFunctionName , testSecrets ) ;
457
+ Task writeTask2 = target . WriteAsync ( ScriptSecretsType . Host , testFunctionName , testSecrets ) ;
439
458
440
- // Simultaneous writes will result in one of the writes being discarded due to
441
- // non-matching ETag.
442
- Task writeTask1 = target . WriteAsync ( ScriptSecretsType . Host , testFunctionName , testSecrets ) ;
443
- Task writeTask2 = target . WriteAsync ( ScriptSecretsType . Host , testFunctionName , testSecrets ) ;
459
+ var ex = await Assert . ThrowsAsync < RequestFailedException > ( ( ) => Task . WhenAll ( writeTask1 , writeTask2 ) ) ;
444
460
445
- var ex = await Assert . ThrowsAsync < RequestFailedException > ( ( ) => Task . WhenAll ( writeTask1 , writeTask2 ) ) ;
461
+ // Ensure the write went through.
462
+ secretsContent = await target . ReadAsync ( ScriptSecretsType . Host , testFunctionName ) as HostSecrets ;
463
+ Assert . Equal ( "changed" , secretsContent . FunctionKeys . First ( ) . Value ) ;
446
464
447
- // Ensure the write went through.
448
- secretsContent = await target . ReadAsync ( ScriptSecretsType . Host , testFunctionName ) as HostSecrets ;
449
- Assert . Equal ( "changed" , secretsContent . FunctionKeys . Single ( ) . Value ) ;
465
+ Assert . Equal ( "ConditionNotMet" , ex . ErrorCode ) ;
466
+ Assert . Equal ( 412 , ex . Status ) ;
467
+ Assert . True ( writeTask1 . IsCompletedSuccessfully || writeTask2 . IsCompletedSuccessfully ,
468
+ "One of the write operations should have completed successfully." ) ;
469
+ }
450
470
451
- Assert . Equal ( "ConditionNotMet" , ex . ErrorCode ) ;
452
- Assert . Equal ( 412 , ex . Status ) ;
453
- Assert . True ( writeTask1 . IsCompletedSuccessfully || writeTask2 . IsCompletedSuccessfully ,
454
- "One of the write operations should have completed successfully." ) ;
471
+ // this is a race so it may not fire every time; try several times
472
+ await TestHelpers . RetryFailedTest ( RunTest , 10 , _output ) ;
455
473
}
456
474
}
457
475
@@ -463,37 +481,47 @@ public async Task BlobRepository_SimultaneousCreates_Throws_Conflict(SecretsRepo
463
481
using ( var directory = new TempDirectory ( ) )
464
482
{
465
483
await _fixture . TestInitialize ( repositoryType , directory . Path ) ;
484
+ string testFunctionName = "host" ;
485
+ var target = _fixture . GetNewSecretRepository ( ) ;
466
486
467
- HostSecrets testSecrets = new HostSecrets ( )
487
+ async Task RunTest ( )
468
488
{
469
- MasterKey = new Key ( "master" , "test" ) ,
470
- FunctionKeys = new List < Key > ( ) { new Key ( KeyName , "test" ) } ,
471
- SystemKeys = new List < Key > ( ) { new Key ( KeyName , "test" ) }
472
- } ;
489
+ HostSecrets testSecrets = new HostSecrets ( )
490
+ {
491
+ MasterKey = new Key ( "master" , "test" ) ,
492
+ FunctionKeys = new List < Key > ( ) ,
493
+ SystemKeys = new List < Key > ( ) { new Key ( KeyName , "test" ) }
494
+ } ;
473
495
474
- string testFunctionName = "host" ;
496
+ // make the payload larger to guarantee the race condition
497
+ for ( int i = 0 ; i < 1000 ; i ++ )
498
+ {
499
+ testSecrets . FunctionKeys . Add ( new Key ( KeyName + i , "test" ) ) ;
500
+ }
475
501
476
- var target = _fixture . GetNewSecretRepository ( ) ;
502
+ // Ensure nothing is there.
503
+ HostSecrets secretsContent = await target . ReadAsync ( ScriptSecretsType . Host , testFunctionName ) as HostSecrets ;
504
+ Assert . Null ( secretsContent ) ;
477
505
478
- // Ensure nothing is there.
479
- HostSecrets secretsContent = await target . ReadAsync ( ScriptSecretsType . Host , testFunctionName ) as HostSecrets ;
480
- Assert . Null ( secretsContent ) ;
506
+ // Simultaneous creates will result in one of the writes being discarded due to
507
+ // non-matching ETag.
508
+ Task writeTask1 = target . WriteAsync ( ScriptSecretsType . Host , testFunctionName , testSecrets ) ;
509
+ Task writeTask2 = target . WriteAsync ( ScriptSecretsType . Host , testFunctionName , testSecrets ) ;
481
510
482
- // Simultaneous creates will result in one of the writes being discarded due to
483
- // non-matching ETag.
484
- Task writeTask1 = target . WriteAsync ( ScriptSecretsType . Host , testFunctionName , testSecrets ) ;
485
- Task writeTask2 = target . WriteAsync ( ScriptSecretsType . Host , testFunctionName , testSecrets ) ;
511
+ var ex = await Assert . ThrowsAsync < RequestFailedException > ( ( ) => Task . WhenAll ( writeTask1 , writeTask2 ) ) ;
486
512
487
- var ex = await Assert . ThrowsAsync < RequestFailedException > ( ( ) => Task . WhenAll ( writeTask1 , writeTask2 ) ) ;
513
+ // Ensure the write went through.
514
+ secretsContent = await target . ReadAsync ( ScriptSecretsType . Host , testFunctionName ) as HostSecrets ;
515
+ Assert . Equal ( "test" , secretsContent . FunctionKeys . First ( ) . Value ) ;
488
516
489
- // Ensure the write went through.
490
- secretsContent = await target . ReadAsync ( ScriptSecretsType . Host , testFunctionName ) as HostSecrets ;
491
- Assert . Equal ( "test" , secretsContent . FunctionKeys . Single ( ) . Value ) ;
517
+ Assert . Equal ( "BlobAlreadyExists" , ex . ErrorCode ) ;
518
+ Assert . Equal ( 409 , ex . Status ) ;
519
+ Assert . True ( writeTask1 . IsCompletedSuccessfully || writeTask2 . IsCompletedSuccessfully ,
520
+ "One of the write operations should have completed successfully." ) ;
521
+ }
492
522
493
- Assert . Equal ( "BlobAlreadyExists" , ex . ErrorCode ) ;
494
- Assert . Equal ( 409 , ex . Status ) ;
495
- Assert . True ( writeTask1 . IsCompletedSuccessfully || writeTask2 . IsCompletedSuccessfully ,
496
- "One of the write operations should have completed successfully." ) ;
523
+ // this is a race so it may not fire every time; try several times
524
+ await TestHelpers . RetryFailedTest ( RunTest , 10 , _output ) ;
497
525
}
498
526
}
499
527
@@ -504,10 +532,16 @@ public Fixture()
504
532
TestSiteName = "Test_test" ;
505
533
var configuration = TestHelpers . GetTestConfiguration ( ) ;
506
534
BlobConnectionString = configuration . GetWebJobsConnectionString ( ConnectionStringNames . Storage ) ;
535
+
507
536
KeyVaultConnectionString = configuration . GetWebJobsConnectionString ( EnvironmentSettingNames . AzureWebJobsSecretStorageKeyVaultConnectionString ) ;
508
537
KeyVaultName = configuration . GetWebJobsConnectionString ( EnvironmentSettingNames . AzureWebJobsSecretStorageKeyVaultName ) ;
509
- AzureServiceTokenProvider azureServiceTokenProvider = new AzureServiceTokenProvider ( KeyVaultConnectionString ) ;
510
- KeyVaultClient = new KeyVaultClient ( new KeyVaultClient . AuthenticationCallback ( azureServiceTokenProvider . KeyVaultTokenCallback ) ) ;
538
+
539
+ if ( KeyVaultConnectionString is not null && KeyVaultName is not null )
540
+ {
541
+ AzureServiceTokenProvider azureServiceTokenProvider = new AzureServiceTokenProvider ( KeyVaultConnectionString ) ;
542
+ KeyVaultClient = new KeyVaultClient ( new KeyVaultClient . AuthenticationCallback ( azureServiceTokenProvider . KeyVaultTokenCallback ) ) ;
543
+ }
544
+
511
545
Environment = new TestEnvironment ( ) ;
512
546
AzureStorageProvider = TestHelpers . GetAzureStorageProvider ( configuration ) ;
513
547
}
@@ -557,7 +591,11 @@ public async Task TestInitialize(SecretsRepositoryType repositoryType, string se
557
591
558
592
await ClearAllBlobSecrets ( ) ;
559
593
ClearAllFileSecrets ( ) ;
560
- await ClearAllKeyVaultSecrets ( ) ;
594
+
595
+ if ( KeyVaultClient != null )
596
+ {
597
+ await ClearAllKeyVaultSecrets ( ) ;
598
+ }
561
599
562
600
LoggerProvider = new TestLoggerProvider ( ) ;
563
601
var loggerFactory = new LoggerFactory ( ) ;
0 commit comments