public async Task RetryPolicy_RecoversSuccessfullyWhenWorkSucceedsAfterRetryUponTransientException() { var cancellationToken = new CancellationToken(); var logger = new Moq.Mock <ILogger <AzureSqlClient> >().Object; var sqlCnFactoryLogger = new Moq.Mock <ILogger <AzureSqlDbConnectionFactory> >().Object; var sqlDbConnectionFactory = new AzureSqlDbConnectionFactory("read-write", "read-only", sqlCnFactoryLogger); var azureSqlClient = new AzureSqlClient(sqlDbConnectionFactory, logger); var retryPolicy = azureSqlClient.GetAsyncRetryPolicy(); var retryCount = 0; var policyResult = await retryPolicy.ExecuteAndCaptureAsync( ct => ++ retryCount > 1?Task.CompletedTask : throw new TimeoutException(), cancellationToken); Assert.AreEqual(2, retryCount); Assert.AreEqual(OutcomeType.Successful, policyResult.Outcome); Assert.IsNull(policyResult.ExceptionType); Assert.IsNull(policyResult.FinalException); }
public async Task RetryPolicy_FailsWhenWorkFailsWithNonTransientException() { var cancellationToken = new CancellationToken(); var logger = new Moq.Mock <ILogger <AzureSqlClient> >().Object; var sqlCnFactoryLogger = new Moq.Mock <ILogger <AzureSqlDbConnectionFactory> >().Object; var sqlDbConnectionFactory = new AzureSqlDbConnectionFactory("read-write", "read-only", sqlCnFactoryLogger); var azureSqlClient = new AzureSqlClient(sqlDbConnectionFactory, logger); var retryPolicy = azureSqlClient.GetAsyncRetryPolicy(); var retryCount = 0; var policyResult = await retryPolicy.ExecuteAndCaptureAsync( ct => ++ retryCount > 1?Task.CompletedTask : throw new ApplicationException(), cancellationToken); Assert.AreEqual(1, retryCount); Assert.AreEqual(OutcomeType.Failure, policyResult.Outcome); Assert.IsNotNull(policyResult.ExceptionType); Assert.IsNotNull(policyResult.FinalException); Assert.AreEqual(ExceptionType.Unhandled, policyResult.ExceptionType.Value); Assert.IsInstanceOfType(policyResult.FinalException, typeof(ApplicationException)); }
public async Task RetryPolicy_FailsAfterMaxAttemptsAfterRetryOnTransientExceptions() { var cancellationToken = new CancellationToken(); var logger = new Moq.Mock <ILogger <AzureSqlClient> >().Object; var sqlCnFactoryLogger = new Moq.Mock <ILogger <AzureSqlDbConnectionFactory> >().Object; var sqlDbConnectionFactory = new AzureSqlDbConnectionFactory("read-write", "read-only", sqlCnFactoryLogger); var azureSqlClient = new AzureSqlClient(sqlDbConnectionFactory, logger); var retryPolicy = azureSqlClient.GetAsyncRetryPolicy(); var retryCount = 0; var policyResult = await retryPolicy.ExecuteAndCaptureAsync( ct => { retryCount++; throw new TimeoutException(); }, cancellationToken); Assert.AreEqual(1 + 5, retryCount); Assert.AreEqual(OutcomeType.Failure, policyResult.Outcome); Assert.IsNotNull(policyResult.ExceptionType); Assert.IsNotNull(policyResult.FinalException); Assert.AreEqual(ExceptionType.HandledByThisPolicy, policyResult.ExceptionType.Value); // Exception type the policy handles but too many of them to keep going Assert.IsInstanceOfType(policyResult.FinalException, typeof(TimeoutException)); }
public async Task AsyncResiliencyPolicy_IsUnemployedWhenWorkSucceeds() { var cancellationToken = new CancellationToken(); var logger = new Moq.Mock <ILogger <AzureSqlClient> >().Object; var sqlCnFactoryLogger = new Moq.Mock <ILogger <AzureSqlDbConnectionFactory> >().Object; var sqlDbConnectionFactory = new AzureSqlDbConnectionFactory("read-write", "read-only", sqlCnFactoryLogger); var azureSqlClient = new AzureSqlClient(sqlDbConnectionFactory, logger); var retryPolicy = azureSqlClient.GetAsyncRetryPolicy(); var policyKey = Guid.NewGuid().ToString(); var globalResiliencyPolicy = AzureSqlClient.GetAsyncGlobalResiliencyPolicyFor(policyKey); var policy = Policy.WrapAsync(retryPolicy, globalResiliencyPolicy); var policyResult = await policy.ExecuteAndCaptureAsync(ct => Task.CompletedTask, cancellationToken); Assert.AreEqual(OutcomeType.Successful, policyResult.Outcome); Assert.IsNull(policyResult.ExceptionType); Assert.IsNull(policyResult.FinalException); }
public async Task GetReadOnlyConnectionAsync_ReturnsClosedConnection() { var cancellationToken = CancellationToken.None; var logger = new Moq.Mock <ILogger <AzureSqlDbConnectionFactory> >().Object; var configurationBuilder = new ConfigurationBuilder(); configurationBuilder.AddUserSecrets(Assembly.GetExecutingAssembly()); var configuration = configurationBuilder.Build(); var readWriteConnectionString = configuration.GetValue <string>("AzurePlatform:AzureSql:ReadWriteConnectionString"); var readOnlyConnectionString = configuration.GetValue <string>("AzurePlatform:AzureSql:ReadOnlyConnectionString"); IAzureSqlDbConnectionFactory azureSqlDbConnectionFactory = new AzureSqlDbConnectionFactory(readOnlyConnectionString, readWriteConnectionString, logger); using var connection = await azureSqlDbConnectionFactory.GetReadOnlyConnectionAsync(cancellationToken); Assert.IsNotNull(connection); Assert.IsInstanceOfType(connection, typeof(SqlConnection)); //Assert.AreEqual(readOnlyConnectionString, connection.ConnectionString); // Can't test this as it rewrites the connection string (application intent is lost) Assert.AreEqual(System.Data.ConnectionState.Closed, connection.State); }
public async Task GetReadWriteConnectionAsync_ThrowsIfInvalidStructureForConnectionString() { var cancellationToken = CancellationToken.None; var logger = new Moq.Mock <ILogger <AzureSqlDbConnectionFactory> >().Object; IAzureSqlDbConnectionFactory azureSqlDbConnectionFactory = new AzureSqlDbConnectionFactory("read-write", "read-only", logger); _ = await azureSqlDbConnectionFactory.GetReadWriteConnectionAsync(cancellationToken); }
public async Task GetReadOnlyConnectionAsync_DoesNotThrowIfInvalidConnectionString_ThrowsOnOpen() { var cancellationToken = CancellationToken.None; var logger = new Moq.Mock <ILogger <AzureSqlDbConnectionFactory> >().Object; var INVALID_READONLY_CONNECTIONSTRING = "Server=tcp:" + Guid.NewGuid().ToString() + ".database.windows.net,1433;Initial Catalog=initial-catalog;Persist Security Info=False;User ID=my-user-id;Password=my-password;MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;ApplicationIntent=ReadOnly"; IAzureSqlDbConnectionFactory azureSqlDbConnectionFactory = new AzureSqlDbConnectionFactory("read-write", INVALID_READONLY_CONNECTIONSTRING, logger); _ = await azureSqlDbConnectionFactory.GetReadOnlyConnectionAsync(cancellationToken); }
public async Task GetReadOnlyConnectionAsync_ThrowsIfCancelled() { var cts = new CancellationTokenSource(); var cancellationToken = cts.Token; var logger = new Moq.Mock <ILogger <AzureSqlDbConnectionFactory> >().Object; cts.Cancel(); IAzureSqlDbConnectionFactory azureSqlDbConnectionFactory = new AzureSqlDbConnectionFactory("read-write", "read-only", logger); _ = await azureSqlDbConnectionFactory.GetReadOnlyConnectionAsync(cancellationToken); }
public async Task RetryPolicy_EmploysExponentialBackoffUponTransientExceptions() { var cancellationToken = new CancellationToken(); var logger = new Moq.Mock <ILogger <AzureSqlClient> >().Object; var sqlCnFactoryLogger = new Moq.Mock <ILogger <AzureSqlDbConnectionFactory> >().Object; var sqlDbConnectionFactory = new AzureSqlDbConnectionFactory("read-write", "read-only", sqlCnFactoryLogger); var azureSqlClient = new AzureSqlClient(sqlDbConnectionFactory, logger); var retryPolicy = azureSqlClient.GetAsyncRetryPolicy(); var retryCount = 0; var xs = new TimeSpan[6]; var sw = new Stopwatch(); sw.Start(); var policyResult = await retryPolicy.ExecuteAndCaptureAsync( ct => { xs[retryCount++] = sw.Elapsed; throw new TimeoutException(); }, cancellationToken); sw.Stop(); // First delay in array can be ignored because it's just the amount of time until the first invocation - ie not a retry var firstDelay = xs[1] - xs[0]; var secondDelay = xs[2] - xs[1]; var thirdDelay = xs[3] - xs[2]; var fourthDelay = xs[4] - xs[3]; var fifthDelay = xs[5] - xs[4]; // Interestingly, it seems expoenential + jitter in Polly land doesn't actually mean exponential .. just pretty close. // Could be a side effect of what it uses to time/sleep (ie best efforts), but in all practicality it doesn't actually make // and difference to how we are using it; just makes it harder to test const int TOLERANCE_IN_MS = 1000; Assert.IsTrue(thirdDelay.TotalMilliseconds > (secondDelay * 2).TotalMilliseconds - TOLERANCE_IN_MS, $"Expected third delay '{thirdDelay}' to be at least twice as long as second '{secondDelay}'"); Assert.IsTrue(fourthDelay.TotalMilliseconds > (thirdDelay * 2).TotalMilliseconds - TOLERANCE_IN_MS, $"Expected fourth delay '{fourthDelay}' to be at least twice as long as third '{thirdDelay}'"); Assert.IsTrue(fifthDelay.TotalMilliseconds > (fourthDelay * 2).TotalMilliseconds - TOLERANCE_IN_MS, $"Expected fifth delay '{fifthDelay}' to be at least twice as long as fourth '{fourthDelay}'"); }
public async Task WriteToStreamAsync_SanityCheckForLocalDevOnly() { var cancellationToken = new CancellationToken(); var optionsAccessor = new Moq.Mock <IOptions <MemoryCacheOptions> >(); optionsAccessor.Setup(x => x.Value).Returns(new MemoryCacheOptions()); var memoryCache = new MemoryCache(optionsAccessor.Object); var logger = new Moq.Mock <ILogger <FileRepository> >().Object; var clock = new SystemClock(); var configurationBuilder = new ConfigurationBuilder(); // NB - Given the SUT is actually connecting to blob storage and a sql db, the connection strings etc are stored in a // local secrets file that is not included in source control. If running these tests locally, ensure this file is // present in your project and that it contains the entries we need configurationBuilder.AddUserSecrets(Assembly.GetExecutingAssembly()); var configuration = configurationBuilder.Build(); var azurePlatformConfiguration = new AzurePlatformConfiguration() { AzureBlobStorage = new AzureBlobStorageConfiguration() { ContainerName = configuration.GetValue <string>("AzurePlatform:AzureBlobStorage:ContainerName") } }; var azurePlatformConfigurationOptionsSnapshot = new Moq.Mock <IOptionsSnapshot <AzurePlatformConfiguration> >(); azurePlatformConfigurationOptionsSnapshot.Setup(x => x.Value).Returns(azurePlatformConfiguration); var primaryServiceUrl = new Uri(configuration.GetValue <string>("AzurePlatform:AzureBlobStorage:PrimaryServiceUrl"), UriKind.Absolute); var geoRedundantServiceUrl = new Uri(configuration.GetValue <string>("AzurePlatform:AzureBlobStorage:GeoRedundantServiceUrl"), UriKind.Absolute); var azureBlobStorageClient = new AzureBlobStoreClient(primaryServiceUrl, geoRedundantServiceUrl, memoryCache, clock, default); var readWriteConnectionString = configuration.GetValue <string>("AzurePlatform:AzureSql:ReadWriteConnectionString"); var readOnlyConnectionString = configuration.GetValue <string>("AzurePlatform:AzureSql:ReadOnlyConnectionString"); var sqlLogger = new Moq.Mock <ILogger <AzureSqlClient> >().Object; var sqlCnFactoryLogger = new Moq.Mock <ILogger <AzureSqlDbConnectionFactory> >().Object; var sqlDbConnectionFactory = new AzureSqlDbConnectionFactory(readWriteConnectionString, readOnlyConnectionString, sqlCnFactoryLogger); var azureSqlClient = new AzureSqlClient(sqlDbConnectionFactory, sqlLogger); IFileRepository fileRepository = new FileRepository(azureBlobStorageClient, azureSqlClient, azurePlatformConfigurationOptionsSnapshot.Object, logger); var blobName = "4d6fa0f8-34a7-4f34-922f-8b06416097e1.pdf"; var file = File.With("DF796179-DB2F-4A06-B4D5-AD7F012CC2CC", "2021-08-09T18:15:02.4214747Z"); var fileHash = "8n45KHxmXabrze7rq/s9Ww=="; using var destinationStream = new System.IO.MemoryStream(); var fileMetadata = new FileMetadata("title", "description", "group-name", file.Version, "owner", file.Name, ".extension", 396764, blobName, clock.UtcNow, fileHash, FileStatus.Verified); var fileWriteDetails = await fileRepository.WriteToStreamAsync(fileMetadata, destinationStream, cancellationToken); Assert.IsNotNull(fileWriteDetails); var fileBytes = destinationStream.ToArray(); Assert.IsTrue(396764 == fileBytes.Length); Assert.IsTrue(396764 == fileWriteDetails.ContentLength); Assert.AreEqual(fileHash, fileWriteDetails.ContentHash); }
public async Task GenerateEphemeralDownloadLink_SanityCheckForLocalDevOnly() { var cancellationToken = new CancellationToken(); var optionsAccessor = new Moq.Mock <IOptions <MemoryCacheOptions> >(); optionsAccessor.Setup(x => x.Value).Returns(new MemoryCacheOptions()); var memoryCache = new MemoryCache(optionsAccessor.Object); var logger = new Moq.Mock <ILogger <FileRepository> >().Object; var clock = new SystemClock(); var configurationBuilder = new ConfigurationBuilder(); // NB - Given the SUT is actually connecting to blob storage and a sql db, the connection strings etc are stored in a // local secrets file that is not included in source control. If running these tests locally, ensure this file is // present in your project and that it contains the entries we need configurationBuilder.AddUserSecrets(Assembly.GetExecutingAssembly()); var configuration = configurationBuilder.Build(); var azurePlatformConfiguration = new AzurePlatformConfiguration() { AzureBlobStorage = new AzureBlobStorageConfiguration() { ContainerName = configuration.GetValue <string>("AzurePlatform:AzureBlobStorage:ContainerName") } }; var azurePlatformConfigurationOptionsSnapshot = new Moq.Mock <IOptionsSnapshot <AzurePlatformConfiguration> >(); azurePlatformConfigurationOptionsSnapshot.Setup(x => x.Value).Returns(azurePlatformConfiguration); var primaryServiceUrl = new Uri(configuration.GetValue <string>("AzurePlatform:AzureBlobStorage:PrimaryServiceUrl"), UriKind.Absolute); var geoRedundantServiceUrl = new Uri(configuration.GetValue <string>("AzurePlatform:AzureBlobStorage:GeoRedundantServiceUrl"), UriKind.Absolute); var azureBlobStorageClient = new AzureBlobStoreClient(primaryServiceUrl, geoRedundantServiceUrl, memoryCache, clock, default); var readWriteConnectionString = configuration.GetValue <string>("AzurePlatform:AzureSql:ReadWriteConnectionString"); var readOnlyConnectionString = configuration.GetValue <string>("AzurePlatform:AzureSql:ReadOnlyConnectionString"); var sqlLogger = new Moq.Mock <ILogger <AzureSqlClient> >().Object; var sqlCnFactoryLogger = new Moq.Mock <ILogger <AzureSqlDbConnectionFactory> >().Object; var sqlDbConnectionFactory = new AzureSqlDbConnectionFactory(readWriteConnectionString, readOnlyConnectionString, sqlCnFactoryLogger); var azureSqlClient = new AzureSqlClient(sqlDbConnectionFactory, sqlLogger); IFileRepository fileRepository = new FileRepository(azureBlobStorageClient, azureSqlClient, azurePlatformConfigurationOptionsSnapshot.Object, logger); var file = File.With("DF796179-DB2F-4A06-B4D5-AD7F012CC2CC", "2021-08-09T18:15:02.4214747Z"); var fileMetadata = await fileRepository.GetMetadataAsync(file, cancellationToken); var uri = await fileRepository.GeneratePublicEphemeralDownloadLink(fileMetadata, cancellationToken); Assert.IsNotNull(uri); Assert.IsTrue(uri.IsAbsoluteUri); }