@@ -291,9 +291,113 @@ public void ResolveDefaultKeyPolicy_OlderUnpropagatedKeyPreferred()
291
291
Assert . False ( resolution . ShouldGenerateNewKey ) ;
292
292
}
293
293
294
+ [ Fact ]
295
+ public void CreateEncryptor_NoRetryOnNullReturn ( )
296
+ {
297
+ // Arrange
298
+ var resolver = CreateDefaultKeyResolver ( ) ;
299
+
300
+ var now = ParseDateTimeOffset ( "2010-01-01 00:00:00Z" ) ;
301
+
302
+ var mockKey = new Mock < IKey > ( ) ;
303
+ mockKey . Setup ( o => o . KeyId ) . Returns ( Guid . NewGuid ( ) ) ;
304
+ mockKey . Setup ( o => o . CreationDate ) . Returns ( now . AddDays ( - 3 ) ) ; // Propagated
305
+ mockKey . Setup ( o => o . ActivationDate ) . Returns ( now . AddDays ( - 1 ) ) ; // Activated
306
+ mockKey . Setup ( o => o . ExpirationDate ) . Returns ( now . AddDays ( 14 ) ) ; // Unexpired
307
+ mockKey . Setup ( o => o . IsRevoked ) . Returns ( false ) ; // Unrevoked
308
+ mockKey . Setup ( o => o . CreateEncryptor ( ) ) . Returns ( ( IAuthenticatedEncryptor ) null ) ; // Anomalous null return
309
+
310
+ // Act
311
+ var resolution = resolver . ResolveDefaultKeyPolicy ( now , [ mockKey . Object ] ) ;
312
+
313
+ // Assert
314
+ Assert . Null ( resolution . DefaultKey ) ;
315
+ Assert . Null ( resolution . FallbackKey ) ;
316
+ Assert . True ( resolution . ShouldGenerateNewKey ) ;
317
+
318
+ mockKey . Verify ( o => o . CreateEncryptor ( ) , Times . Once ) ; // Not retried
319
+ }
320
+
321
+ [ Fact ]
322
+ public void CreateEncryptor_FirstAttemptIsNotARetry ( )
323
+ {
324
+ // Arrange
325
+ var options = Options . Create ( new KeyManagementOptions ( )
326
+ {
327
+ MaximumDefaultKeyResolverRetries = 3 ,
328
+ DefaultKeyResolverRetryDelay = TimeSpan . Zero ,
329
+ } ) ;
330
+
331
+ var resolver = new DefaultKeyResolver ( options , NullLoggerFactory . Instance ) ;
332
+
333
+ var now = ParseDateTimeOffset ( "2010-01-01 00:00:00Z" ) ;
334
+
335
+ var creation1 = now . AddDays ( - 3 ) ;
336
+ var activation1 = creation1 . AddDays ( 2 ) ;
337
+ var expiration1 = creation1 . AddDays ( 90 ) ;
338
+
339
+ // Newer but still propagated => preferred
340
+ var creation2 = creation1 . AddHours ( 1 ) ;
341
+ var activation2 = activation1 . AddHours ( 1 ) ;
342
+ var expiration2 = expiration1 . AddHours ( 1 ) ;
343
+
344
+ var mockKey1 = CreateMockKey ( activation1 , expiration1 , creation1 , isRevoked : false , createEncryptorThrows : false ) ;
345
+ var mockKey2 = CreateMockKey ( activation2 , expiration2 , creation2 , isRevoked : false , createEncryptorThrows : true ) ; // Uses up all the retries
346
+
347
+ // Act
348
+ var resolution = resolver . ResolveDefaultKeyPolicy ( now , [ mockKey1 . Object , mockKey2 . Object ] ) ;
349
+
350
+ // Assert
351
+ Assert . Null ( resolution . DefaultKey ) ;
352
+ Assert . Same ( mockKey1 . Object , resolution . FallbackKey ) ;
353
+ Assert . True ( resolution . ShouldGenerateNewKey ) ;
354
+
355
+ mockKey1 . Verify ( o => o . CreateEncryptor ( ) , Times . Once ) ; // 1 try
356
+ mockKey2 . Verify ( o => o . CreateEncryptor ( ) , Times . Exactly ( 4 ) ) ; // 1 try plus max (3) retries
357
+ }
358
+
359
+ [ Fact ]
360
+ public void CreateEncryptor_SucceedsOnRetry ( )
361
+ {
362
+ // Arrange
363
+ var options = Options . Create ( new KeyManagementOptions ( )
364
+ {
365
+ MaximumDefaultKeyResolverRetries = 3 ,
366
+ DefaultKeyResolverRetryDelay = TimeSpan . Zero ,
367
+ } ) ;
368
+
369
+ var resolver = new DefaultKeyResolver ( options , NullLoggerFactory . Instance ) ;
370
+
371
+ var now = ParseDateTimeOffset ( "2010-01-01 00:00:00Z" ) ;
372
+
373
+ var creation = now . AddDays ( - 3 ) ;
374
+ var activation = creation . AddDays ( 2 ) ;
375
+ var expiration = creation . AddDays ( 90 ) ;
376
+
377
+ var mockKey = CreateMockKey ( activation , expiration , creation ) ;
378
+ mockKey
379
+ . Setup ( o => o . CreateEncryptor ( ) )
380
+ . Returns ( ( ) =>
381
+ {
382
+ // Added to list before the callback is called
383
+ if ( mockKey . Invocations . Count ( i => i . Method . Name == nameof ( IKey . CreateEncryptor ) ) == 1 )
384
+ {
385
+ throw new Exception ( "This method fails." ) ;
386
+ }
387
+ return new Mock < IAuthenticatedEncryptor > ( ) . Object ;
388
+ } ) ;
389
+
390
+ // Act
391
+ var resolution = resolver . ResolveDefaultKeyPolicy ( now , [ mockKey . Object ] ) ;
392
+
393
+ // Assert
394
+ Assert . Same ( mockKey . Object , resolution . DefaultKey ) ;
395
+ mockKey . Verify ( o => o . CreateEncryptor ( ) , Times . Exactly ( 2 ) ) ; // 1 try plus 1 retry
396
+ }
397
+
294
398
private static IDefaultKeyResolver CreateDefaultKeyResolver ( )
295
399
{
296
- return new DefaultKeyResolver ( NullLoggerFactory . Instance ) ;
400
+ return new DefaultKeyResolver ( Options . Create ( new KeyManagementOptions ( ) ) , NullLoggerFactory . Instance ) ;
297
401
}
298
402
299
403
private static IKey CreateKey ( string activationDate , string expirationDate , string creationDate = null , bool isRevoked = false , bool createEncryptorThrows = false )
@@ -302,6 +406,11 @@ private static IKey CreateKey(string activationDate, string expirationDate, stri
302
406
}
303
407
304
408
private static IKey CreateKey ( DateTimeOffset activationDate , DateTimeOffset expirationDate , DateTimeOffset ? creationDate = null , bool isRevoked = false , bool createEncryptorThrows = false )
409
+ {
410
+ return CreateMockKey ( activationDate , expirationDate , creationDate , isRevoked , createEncryptorThrows ) . Object ;
411
+ }
412
+
413
+ private static Mock < IKey > CreateMockKey ( DateTimeOffset activationDate , DateTimeOffset expirationDate , DateTimeOffset ? creationDate = null , bool isRevoked = false , bool createEncryptorThrows = false )
305
414
{
306
415
var mockKey = new Mock < IKey > ( ) ;
307
416
mockKey . Setup ( o => o . KeyId ) . Returns ( Guid . NewGuid ( ) ) ;
@@ -318,7 +427,7 @@ private static IKey CreateKey(DateTimeOffset activationDate, DateTimeOffset expi
318
427
mockKey . Setup ( o => o . CreateEncryptor ( ) ) . Returns ( Mock . Of < IAuthenticatedEncryptor > ( ) ) ;
319
428
}
320
429
321
- return mockKey . Object ;
430
+ return mockKey ;
322
431
}
323
432
324
433
private static DateTimeOffset ParseDateTimeOffset ( string dto )
0 commit comments