コード例 #1
0
        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);
        }
コード例 #2
0
        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));
        }
コード例 #3
0
        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));
        }
コード例 #4
0
        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);
        }
コード例 #9
0
        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}'");
        }
コード例 #10
0
        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);
        }
コード例 #11
0
        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);
        }