33
44using System ;
55using System . Collections . Generic ;
6+ using System . Data ;
67using System . Data . Entity ;
8+ using System . Data . Entity . Infrastructure ;
9+ using System . Data . SqlClient ;
710using System . Linq ;
11+ using System . Reflection ;
812using System . Threading . Tasks ;
913using Microsoft . Extensions . Logging ;
1014using Moq ;
1115using NuGet . Jobs . Validation ;
1216using NuGet . Jobs . Validation . Storage ;
1317using Tests . ContextHelpers ;
1418using Xunit ;
19+ using Xunit . Abstractions ;
1520
1621namespace NuGet . Services . Validation
1722{
@@ -50,6 +55,10 @@ class BValidator : IValidator
5055
5156 public class TheGetStatusMethod : FactsBase
5257 {
58+ public TheGetStatusMethod ( ITestOutputHelper output ) : base ( output )
59+ {
60+ }
61+
5362 [ Fact ]
5463 public async Task GetStatusReturnsNotStartedIfNoPersistedStatus ( )
5564 {
@@ -170,6 +179,10 @@ public async Task GetStatusThrowsOnInvalidValidatorName()
170179
171180 public class TheIsRevalidationRequestMethod : FactsBase
172181 {
182+ public TheIsRevalidationRequestMethod ( ITestOutputHelper output ) : base ( output )
183+ {
184+ }
185+
173186 [ Fact ]
174187 public async Task ReturnsFalseIfPackageHasNeverBeenValidated ( )
175188 {
@@ -318,6 +331,10 @@ public async Task ReturnsTrueIfPackageHasBeenValidatedByMultipleValidatorsAndCur
318331
319332 public class TheAddStatusAsyncMethod : FactsBase
320333 {
334+ public TheAddStatusAsyncMethod ( ITestOutputHelper output ) : base ( output )
335+ {
336+ }
337+
321338 [ Fact ]
322339 public async Task AddStatusAsyncThrowsIfValidatorNameIsWrong ( )
323340 {
@@ -372,6 +389,10 @@ public async Task AddStatusAsyncMethodPersistsStatus(ValidationStatus status)
372389
373390 public class TheSaveStatusAsyncMethod : FactsBase
374391 {
392+ public TheSaveStatusAsyncMethod ( ITestOutputHelper output ) : base ( output )
393+ {
394+ }
395+
375396 [ Fact ]
376397 public async Task SaveStatusAsyncThrowsIfValidatorNameIsWrong ( )
377398 {
@@ -414,13 +435,42 @@ public async Task SaveStatusAsyncMethodPersistsStatus(ValidationStatus status)
414435
415436 public class TheTryAddValidatorStatusAsyncMethod : FactsBase
416437 {
438+ public TheTryAddValidatorStatusAsyncMethod ( ITestOutputHelper output ) : base ( output )
439+ {
440+ }
441+
417442 private ValidatorStatus NewStatus => new ValidatorStatus
418443 {
419444 ValidationId = ValidationId ,
420445 PackageKey = PackageKey ,
421446 ValidatorName = nameof ( AValidator ) ,
422447 } ;
423448
449+ [ Fact ]
450+ public async Task HandlesUniqueConstraintViolationGracefully ( )
451+ {
452+ // Arrange
453+ var exception = new DbUpdateException (
454+ message : "Fail!" ,
455+ innerException : CreateSqlException ( 2627 , "No can do, friend." ) ) ;
456+ _validationContext
457+ . Setup ( x => x . SaveChangesAsync ( ) )
458+ . ThrowsAsync ( exception ) ;
459+
460+ _validationContext . Mock ( validatorStatusesMock : new Mock < IDbSet < ValidatorStatus > > ( ) ) ;
461+ var status = NewStatus ;
462+
463+ // Act
464+ var result = await _target . TryAddValidatorStatusAsync (
465+ _validationRequest . Object ,
466+ status ,
467+ ValidationStatus . Succeeded ) ;
468+
469+ // Assert
470+ Assert . Same ( status , result ) ;
471+ _validationContext . Verify ( x => x . ValidatorStatuses , Times . Exactly ( 2 ) ) ;
472+ }
473+
424474 [ Fact ]
425475 public async Task PersistsStatus ( )
426476 {
@@ -453,6 +503,10 @@ public async Task PersistsStatus()
453503
454504 public class TheTryUpdateValidationStatusAsyncMethod : FactsBase
455505 {
506+ public TheTryUpdateValidationStatusAsyncMethod ( ITestOutputHelper output ) : base ( output )
507+ {
508+ }
509+
456510 private ValidatorStatus ExistingStatus => new ValidatorStatus
457511 {
458512 ValidationId = ValidationId ,
@@ -461,6 +515,30 @@ public class TheTryUpdateValidationStatusAsyncMethod : FactsBase
461515 State = ValidationStatus . NotStarted ,
462516 } ;
463517
518+ [ Fact ]
519+ public async Task HandlesUniqueConstraintViolationGracefully ( )
520+ {
521+ // Arrange
522+ var exception = new DbUpdateConcurrencyException ( "Fail!" ) ;
523+ _validationContext
524+ . Setup ( x => x . SaveChangesAsync ( ) )
525+ . ThrowsAsync ( exception ) ;
526+
527+ _validationContext . Mock ( validatorStatusesMock : new Mock < IDbSet < ValidatorStatus > > ( ) ) ;
528+ var status = ExistingStatus ;
529+ _validationContext . Object . ValidatorStatuses . Add ( status ) ;
530+
531+ // Act
532+ var result = await _target . TryUpdateValidationStatusAsync (
533+ _validationRequest . Object ,
534+ status ,
535+ ValidationStatus . Succeeded ) ;
536+
537+ // Assert
538+ Assert . Same ( status , result ) ;
539+ _validationContext . Verify ( x => x . ValidatorStatuses , Times . Exactly ( 2 ) ) ;
540+ }
541+
464542 [ Fact ]
465543 public async Task PersistsStatus ( )
466544 {
@@ -484,15 +562,17 @@ public async Task PersistsStatus()
484562
485563 public abstract class FactsBase
486564 {
565+ protected readonly ITestOutputHelper _output ;
487566 protected readonly Mock < IValidationEntitiesContext > _validationContext ;
488- protected readonly Mock < ILogger < ValidatorStateService > > _logger ;
567+ protected readonly ILogger < ValidatorStateService > _logger ;
489568 protected readonly Mock < IValidationRequest > _validationRequest ;
490569 protected readonly ValidatorStateService _target ;
491570
492- public FactsBase ( )
571+ public FactsBase ( ITestOutputHelper output )
493572 {
573+ _output = output ?? throw new ArgumentNullException ( nameof ( output ) ) ;
494574 _validationContext = new Mock < IValidationEntitiesContext > ( ) ;
495- _logger = new Mock < ILogger < ValidatorStateService > > ( ) ;
575+ _logger = new LoggerFactory ( ) . AddXunit ( _output ) . CreateLogger < ValidatorStateService > ( ) ;
496576
497577 _validationRequest = new Mock < IValidationRequest > ( ) ;
498578 _validationRequest . Setup ( x => x . NupkgUrl ) . Returns ( NupkgUrl ) ;
@@ -508,7 +588,52 @@ protected ValidatorStateService CreateValidatorStateService(string validatorName
508588 => new ValidatorStateService (
509589 _validationContext . Object ,
510590 validatorName ,
511- _logger . Object ) ;
591+ _logger ) ;
592+
593+ /// <summary>
594+ /// Source: http://blog.gauffin.org/2014/08/how-to-create-a-sqlexception/
595+ /// </summary>
596+ protected SqlException CreateSqlException ( int number , string message )
597+ {
598+ var collectionConstructor = typeof ( SqlErrorCollection )
599+ . GetConstructor (
600+ bindingAttr : BindingFlags . NonPublic | BindingFlags . Instance ,
601+ binder : null ,
602+ types : new Type [ 0 ] ,
603+ modifiers : null ) ;
604+ var addMethod = typeof ( SqlErrorCollection ) . GetMethod ( "Add" , BindingFlags . NonPublic | BindingFlags . Instance ) ;
605+ var errorCollection = ( SqlErrorCollection ) collectionConstructor . Invoke ( null ) ;
606+
607+ var errorConstructor = typeof ( SqlError ) . GetConstructor (
608+ bindingAttr : BindingFlags . NonPublic | BindingFlags . Instance ,
609+ binder : null ,
610+ types : new [ ]
611+ {
612+ typeof ( int ) , typeof ( byte ) , typeof ( byte ) , typeof ( string ) , typeof ( string ) , typeof ( string ) ,
613+ typeof ( int ) , typeof ( uint )
614+ } ,
615+ modifiers : null ) ;
616+ var error = errorConstructor . Invoke ( new object [ ]
617+ {
618+ number ,
619+ ( byte ) 0 ,
620+ ( byte ) 0 ,
621+ "server" ,
622+ "errMsg" ,
623+ "procedure" ,
624+ 100 ,
625+ ( uint ) 0
626+ } ) ;
627+
628+ addMethod . Invoke ( errorCollection , new [ ] { error } ) ;
629+
630+ var constructor = typeof ( SqlException ) . GetConstructor (
631+ bindingAttr : BindingFlags . NonPublic | BindingFlags . Instance ,
632+ binder : null ,
633+ types : new [ ] { typeof ( string ) , typeof ( SqlErrorCollection ) , typeof ( Exception ) , typeof ( Guid ) } ,
634+ modifiers : null ) ;
635+ return ( SqlException ) constructor . Invoke ( new object [ ] { message , errorCollection , null , Guid . NewGuid ( ) } ) ;
636+ }
512637 }
513638 }
514639}
0 commit comments