diff --git a/src/Arcus.Testing.Storage.Blob/TemporaryBlobContainer.cs b/src/Arcus.Testing.Storage.Blob/TemporaryBlobContainer.cs index 53b45624..f34197e7 100644 --- a/src/Arcus.Testing.Storage.Blob/TemporaryBlobContainer.cs +++ b/src/Arcus.Testing.Storage.Blob/TemporaryBlobContainer.cs @@ -168,6 +168,9 @@ public class OnSetupBlobContainerOptions { private readonly List _filters = new(); + /// + /// Gets the configurable setup option on what to do with existing Azure Blobs in the Azure Blob container upon the test fixture creation. + /// internal OnSetupContainer Blobs { get; private set; } = OnSetupContainer.LeaveExisted; /// @@ -214,6 +217,11 @@ public OnSetupBlobContainerOptions LeaveAllBlobs() return this; } + /// + /// Determines whether the given matches the configured filter. + /// + /// The blob to match the filter against. + /// Thrown when the is null. internal bool IsMatched(BlobItem blob) { if (blob is null) @@ -238,7 +246,14 @@ public class OnTeardownBlobContainerOptions { private readonly List _filters = new(); + /// + /// Gets the configurable option on what to do with unlinked Azure Blobs in the Azure Blob container upon the disposal of the test fixture. + /// internal OnTeardownBlobs Blobs { get; private set; } = OnTeardownBlobs.CleanIfCreated; + + /// + /// Gets the configurable option on what to do with the Azure Blob container upon the disposal of the test fixture. + /// internal OnTeardownContainer Container { get; private set; } = OnTeardownContainer.DeleteIfCreated; /// @@ -310,6 +325,8 @@ public OnTeardownBlobContainerOptions DeleteExistingContainer() /// /// Determines whether the given should be deleted upon the disposal of the . /// + /// The blob to match the filter against. + /// Thrown when the is null. internal bool IsMatched(BlobItem blob) { if (blob is null) @@ -347,7 +364,6 @@ public class TemporaryBlobContainerOptions /// public class TemporaryBlobContainer : IAsyncDisposable { - private readonly BlobContainerClient _containerClient; private readonly Collection _blobs = new(); private readonly bool _createdByUs; private readonly TemporaryBlobContainerOptions _options; @@ -359,21 +375,22 @@ private TemporaryBlobContainer( TemporaryBlobContainerOptions options, ILogger logger) { - _containerClient = containerClient ?? throw new ArgumentNullException(nameof(containerClient)); _createdByUs = createdByUs; _options = options ?? new TemporaryBlobContainerOptions(); _logger = logger ?? NullLogger.Instance; + + Client = containerClient ?? throw new ArgumentNullException(nameof(containerClient)); } /// /// Gets the name of the temporary Azure Blob container currently in storage. /// - public string Name => _containerClient.Name; + public string Name => Client.Name; /// /// Gets the instance that represents the temporary Azure Blob container. /// - public BlobContainerClient Client => _containerClient; + public BlobContainerClient Client { get; } /// /// Creates a new instance of the which creates a new Azure Blob storage container if it doesn't exist yet. @@ -440,9 +457,13 @@ public static async Task CreateIfNotExistsAsync( /// /// The client to interact with the Azure Blob storage container. /// The logger to write diagnostic messages during the creation of the Azure Blob container. + /// Thrown when the is null. public static async Task CreateIfNotExistsAsync(BlobContainerClient containerClient, ILogger logger) { - return await CreateIfNotExistsAsync(containerClient, logger, configureOptions: null); + return await CreateIfNotExistsAsync( + containerClient ?? throw new ArgumentNullException(nameof(containerClient)), + logger, + configureOptions: null); } /// @@ -523,7 +544,7 @@ public async Task UploadBlobAsync(string blobName, BinaryData blobCo throw new ArgumentNullException(nameof(blobContent)); } - BlobClient blobClient = _containerClient.GetBlobClient(blobName); + BlobClient blobClient = Client.GetBlobClient(blobName); _blobs.Add(await TemporaryBlobFile.UploadIfNotExistsAsync(blobClient, blobContent, _logger, configureOptions)); return blobClient; @@ -540,15 +561,15 @@ public async ValueTask DisposeAsync() disposables.AddRange(_blobs); disposables.Add(AsyncDisposable.Create(async () => { - await CleanBlobContainerUponDeletionAsync(_containerClient, _options, _logger); + await CleanBlobContainerUponDeletionAsync(Client, _options, _logger); })); if (_createdByUs || _options.OnTeardown.Container is OnTeardownContainer.DeleteIfExists) { disposables.Add(AsyncDisposable.Create(async () => { - _logger.LogTrace("Deleting Azure Blob container '{ContainerName}'", _containerClient.Name); - await _containerClient.DeleteIfExistsAsync(); + _logger.LogTrace("Deleting Azure Blob container '{ContainerName}'", Client.Name); + await Client.DeleteIfExistsAsync(); })); } } @@ -560,7 +581,7 @@ private static async Task CleanBlobContainerUponCreationAsync(BlobContainerClien return; } - logger.LogDebug("Cleaning Azure Blob container '{ContainerName}'", containerClient.Name); + logger.LogTrace("Cleaning Azure Blob container '{ContainerName}'", containerClient.Name); await foreach (BlobItem blob in containerClient.GetBlobsAsync()) { if (options.OnSetup.IsMatched(blob)) @@ -578,7 +599,7 @@ private static async Task CleanBlobContainerUponDeletionAsync(BlobContainerClien return; } - logger.LogDebug("Cleaning Azure Blob container '{ContainerName}'", containerClient.Name); + logger.LogTrace("Cleaning Azure Blob container '{ContainerName}'", containerClient.Name); await foreach (BlobItem blob in containerClient.GetBlobsAsync()) { if (options.OnTeardown.IsMatched(blob)) diff --git a/src/Arcus.Testing.Storage.Blob/TemporaryBlobFile.cs b/src/Arcus.Testing.Storage.Blob/TemporaryBlobFile.cs index ede353ee..2a597291 100644 --- a/src/Arcus.Testing.Storage.Blob/TemporaryBlobFile.cs +++ b/src/Arcus.Testing.Storage.Blob/TemporaryBlobFile.cs @@ -13,6 +13,13 @@ namespace Arcus.Testing /// public class OnSetupBlobFileOptions { + /// + /// Gets the configured setup option on what to do with an existing Azure Blob file upon creation. + /// + /// + /// [true] overrides the existing Azure Blob file when it already exists; + /// [false] uses the existing Azure Blob file's content instead. + /// internal bool OverrideBlob { get; private set; } /// @@ -44,6 +51,9 @@ internal enum OnTeardownBlob { DeleteIfCreated, DeleteIfExisted } /// public class OnTeardownBlobFileOptions { + /// + /// Gets the configured teardown option on what to do with the Azure Blob content upon disposal. + /// internal OnTeardownBlob Content { get; private set; } /// @@ -87,7 +97,6 @@ public class TemporaryBlobFileOptions /// public class TemporaryBlobFile : IAsyncDisposable { - private readonly BlobClient _blobClient; private readonly bool _createdByUs; private readonly BinaryData _originalData; private readonly TemporaryBlobFileOptions _options; @@ -100,22 +109,28 @@ private TemporaryBlobFile( TemporaryBlobFileOptions options, ILogger logger) { - _blobClient = blobClient ?? throw new ArgumentNullException(nameof(blobClient)); _createdByUs = createdByUs; _originalData = originalData; _options = options; _logger = logger ?? NullLogger.Instance; + + Client = blobClient ?? throw new ArgumentNullException(nameof(blobClient)); } /// /// Gets the name of the Azure Blob file currently in storage. /// - public string Name => _blobClient.Name; + public string Name => Client.Name; + + /// + /// Gets the name of the Azure Blob container where the Azure Blob file is currently stored. + /// + public string ContainerName => Client.BlobContainerName; /// /// Gets the client to interact with the temporary stored Azure Blob file currently in storage. /// - public BlobClient Client => _blobClient; + public BlobClient Client { get; } /// /// Uploads a temporary blob to the Azure Blob container. @@ -134,7 +149,17 @@ private TemporaryBlobFile( /// Thrown when or the is null. public static async Task UploadIfNotExistsAsync(Uri blobContainerUri, string blobName, BinaryData blobContent, ILogger logger) { - return await UploadIfNotExistsAsync(blobContainerUri, blobName, blobContent, logger, configureOptions: null); + if (string.IsNullOrWhiteSpace(blobName)) + { + throw new ArgumentException("Requires a non-blank name for the Azure Blob file name for it to be uploaded to Azure Blob storage", nameof(blobName)); + } + + return await UploadIfNotExistsAsync( + blobContainerUri ?? throw new ArgumentNullException(nameof(blobContainerUri)), + blobName, + blobContent ?? throw new ArgumentNullException(nameof(blobContent)), + logger, + configureOptions: null); } /// @@ -165,8 +190,18 @@ public static async Task UploadIfNotExistsAsync( throw new ArgumentNullException(nameof(blobContainerUri)); } + if (string.IsNullOrWhiteSpace(blobName)) + { + throw new ArgumentException("Requires a non-blank name for the Azure Blob file name for it to be uploaded to Azure Blob storage", nameof(blobName)); + } + + if (blobContent is null) + { + throw new ArgumentNullException(nameof(blobContent)); + } + var containerClient = new BlobContainerClient(blobContainerUri, new DefaultAzureCredential()); - var blobClient = containerClient.GetBlobClient(blobName); + BlobClient blobClient = containerClient.GetBlobClient(blobName); return await UploadIfNotExistsAsync(blobClient, blobContent, logger, configureOptions); } @@ -180,7 +215,11 @@ public static async Task UploadIfNotExistsAsync( /// Thrown when the or the is null. public static async Task UploadIfNotExistsAsync(BlobClient blobClient, BinaryData blobContent, ILogger logger) { - return await UploadIfNotExistsAsync(blobClient, blobContent, logger, configureOptions: null); + return await UploadIfNotExistsAsync( + blobClient ?? throw new ArgumentNullException(nameof(blobClient)), + blobContent ?? throw new ArgumentNullException(nameof(blobContent)), + logger, + configureOptions: null); } /// @@ -236,7 +275,7 @@ public static async Task UploadIfNotExistsAsync( return (createdByUs: false, originalContent.Content); } - logger.LogDebug("Uploading Azure Blob '{BlobName}' to container '{ContainerName}'", client.Name, client.BlobContainerName); + logger.LogTrace("Uploading Azure Blob '{BlobName}' to container '{ContainerName}'", client.Name, client.BlobContainerName); await client.UploadAsync(newContent); return (createdByUs: true, originalData: null); @@ -250,14 +289,14 @@ public async ValueTask DisposeAsync() { if (!_createdByUs && _originalData != null && _options.OnTeardown.Content != OnTeardownBlob.DeleteIfExisted) { - _logger.LogDebug("Reverting Azure Blob '{BlobName}' original content in container '{ContainerName}'", _blobClient.Name, _blobClient.BlobContainerName); - await _blobClient.UploadAsync(_originalData, overwrite: true); + _logger.LogDebug("Reverting Azure Blob '{BlobName}' original content in container '{ContainerName}'", Client.Name, Client.BlobContainerName); + await Client.UploadAsync(_originalData, overwrite: true); } if (_createdByUs || _options.OnTeardown.Content is OnTeardownBlob.DeleteIfExisted) { - _logger.LogDebug("Deleting Azure Blob '{BlobName}' from container '{ContainerName}'", _blobClient.Name, _blobClient.BlobContainerName); - await _blobClient.DeleteIfExistsAsync(); + _logger.LogTrace("Deleting Azure Blob '{BlobName}' from container '{ContainerName}'", Client.Name, Client.BlobContainerName); + await Client.DeleteIfExistsAsync(); } } } diff --git a/src/Arcus.Testing.Tests.Integration/Storage/TemporaryBlobContainerTests.cs b/src/Arcus.Testing.Tests.Integration/Storage/TemporaryBlobContainerTests.cs index 79d83df5..5f6d2145 100644 --- a/src/Arcus.Testing.Tests.Integration/Storage/TemporaryBlobContainerTests.cs +++ b/src/Arcus.Testing.Tests.Integration/Storage/TemporaryBlobContainerTests.cs @@ -263,8 +263,12 @@ private async Task WhenTempContainerCreatedAsync( Action configureOptions = null) { TemporaryBlobContainer temp = configureOptions is null - ? await TemporaryBlobContainer.CreateIfNotExistsAsync(context.StorageAccount.Name, client.Name, Logger) - : await TemporaryBlobContainer.CreateIfNotExistsAsync(context.StorageAccount.Name, client.Name, Logger, configureOptions); + ? Bogus.Random.Bool() + ? await TemporaryBlobContainer.CreateIfNotExistsAsync(context.StorageAccount.Name, client.Name, Logger) + : await TemporaryBlobContainer.CreateIfNotExistsAsync(client, Logger) + : Bogus.Random.Bool() + ? await TemporaryBlobContainer.CreateIfNotExistsAsync(context.StorageAccount.Name, client.Name, Logger, configureOptions) + : await TemporaryBlobContainer.CreateIfNotExistsAsync(client, Logger, configureOptions); Assert.Equal(client.Name, temp.Name); Assert.Equal(client.Name, temp.Client.Name); diff --git a/src/Arcus.Testing.Tests.Integration/Storage/TemporaryBlobFileTests.cs b/src/Arcus.Testing.Tests.Integration/Storage/TemporaryBlobFileTests.cs index 3c64d58a..de87f905 100644 --- a/src/Arcus.Testing.Tests.Integration/Storage/TemporaryBlobFileTests.cs +++ b/src/Arcus.Testing.Tests.Integration/Storage/TemporaryBlobFileTests.cs @@ -163,11 +163,16 @@ private async Task WhenBlobUploadedAsync( blobName ??= $"test-{Guid.NewGuid():N}"; blobContent ??= BinaryData.FromBytes(Bogus.Random.Bytes(100)); - var temp = configureOptions is null - ? await TemporaryBlobFile.UploadIfNotExistsAsync(client.Uri, blobName, blobContent, Logger) - : await TemporaryBlobFile.UploadIfNotExistsAsync(client.Uri, blobName, blobContent, Logger, configureOptions); + TemporaryBlobFile temp = configureOptions is null + ? Bogus.Random.Bool() + ? await TemporaryBlobFile.UploadIfNotExistsAsync(client.Uri, blobName, blobContent, Logger) + : await TemporaryBlobFile.UploadIfNotExistsAsync(client.GetBlobClient(blobName), blobContent, Logger) + : Bogus.Random.Bool() + ? await TemporaryBlobFile.UploadIfNotExistsAsync(client.Uri, blobName, blobContent, Logger, configureOptions) + : await TemporaryBlobFile.UploadIfNotExistsAsync(client.GetBlobClient(blobName), blobContent, Logger, configureOptions); Assert.Equal(blobName, temp.Name); + Assert.Equal(client.Name, temp.ContainerName); Assert.Equal(blobName, temp.Client.Name); Assert.Equal(client.Name, temp.Client.BlobContainerName);