Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ The `TemporaryBlobContainer` provides a solution when the integration test requi
using Arcus.Testing;

await using var container = await TemporaryBlobContainer.CreateIfNotExistsAsync(
"<account-name", "<container-name>", logger);
"<account-name", "<container-name>", logger, cancellationToken);

// Interact with the container during the lifetime of the fixture.
BlobContainerClient client = container.Client;
Expand All @@ -42,7 +42,7 @@ using Arcus.Testing;
await using TemporaryBlobContainer container = ...

BlobClient client = await container.UpsertBlobFileAsync(
"<blob-name>", BinaryData.FromString("<blob-content>"));
"<blob-name>", BinaryData.FromString("<blob-content>"), cancellationToken);
```

:::praise
Expand Down Expand Up @@ -109,10 +109,11 @@ The `TemporaryBlobFile` provides a solution when the integration test requires d
using Arcus.Testing;

await using var file = await TemporaryBlobFile.UpsertFileAsync(
blobContainerUri: new Uri("<blob-container-uri">),
blobName: "<blob-name>",
blobContent: BinaryData.FromString("<blob-content">),
logger: logger);
new Uri("<blob-container-uri">),
"<blob-name>",
BinaryData.FromString("<blob-content">),
logger,
cancellationToken);

// Interact with the blob during the lifetime of the fixture.
BlobClient client = file.Client;
Expand Down Expand Up @@ -345,4 +346,4 @@ await using var file = await TemporaryShareFile.UpsertFileAsync(
```

</TabItem>
</Tabs>
</Tabs>
147 changes: 130 additions & 17 deletions src/Arcus.Testing.Storage.Blob/TemporaryBlobContainer.cs

Large diffs are not rendered by default.

96 changes: 79 additions & 17 deletions src/Arcus.Testing.Storage.Blob/TemporaryBlobFile.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Azure;
using Azure.Identity;
using Azure.Storage.Blobs;
using Azure.Storage.Blobs.Models;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using LogLevel = Microsoft.Extensions.Logging.LogLevel;

namespace Arcus.Testing
{
Expand Down Expand Up @@ -101,19 +104,20 @@
/// <para>⚡ Uses <see cref="DefaultAzureCredential"/> to authenticate with Azure Blob Storage.</para>
/// <para>⚡ File will be deleted (if new) or reverted (if existing) when the <see cref="TemporaryBlobFile"/> is disposed.</para>
/// </remarks>
/// <param name="blobClient">The Azure Blob client to interact with Azure Blob Storage.</param>
/// <param name="blobContainerUri">
/// <para>The <see cref="BlobContainerClient.Uri" /> referencing the blob container that includes the name of the account and the name of the container.</para>
/// <para>This is likely to be similar to <a href="">https://{account_name}.blob.core.windows.net/{container_name}</a>.</para>
/// </param>
/// <param name="blobName">The name of the blob to upload.</param>
/// <param name="blobContent">The content of the blob to upload.</param>
/// <param name="logger">The logger to write diagnostic messages during the upload process.</param>
/// <exception cref="ArgumentNullException">Thrown when the <paramref name="blobClient"/> or the <paramref name="blobContent"/> is <c>null</c>.</exception>
public static async Task<TemporaryBlobFile> UpsertFileAsync(BlobClient blobClient, BinaryData blobContent, ILogger logger)
/// <exception cref="ArgumentException">Thrown when the <paramref name="blobContainerUri"/> or the <paramref name="blobName"/> is blank.</exception>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="blobContainerUri"/> or the <paramref name="blobContent"/> is <c>null</c>.</exception>
/// <exception cref="RequestFailedException">Thrown when the interaction with Azure failed.</exception>
[Obsolete("Will be removed in v3, please use the " + nameof(UpsertFileAsync) + " overload instead that provides cancellation token support", DiagnosticId = ObsoleteDefaults.DiagnosticId)]

Check warning on line 117 in src/Arcus.Testing.Storage.Blob/TemporaryBlobFile.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Do not forget to remove this deprecated code someday.

See more on https://sonarcloud.io/project/issues?id=arcus-azure_arcus.testing&issues=AZyOnD5Sj1_gHa98x_9P&open=AZyOnD5Sj1_gHa98x_9P&pullRequest=508
public static Task<TemporaryBlobFile> UpsertFileAsync(Uri blobContainerUri, string blobName, BinaryData blobContent, ILogger logger)
{
ArgumentNullException.ThrowIfNull(blobClient);
ArgumentNullException.ThrowIfNull(blobContent);
logger ??= NullLogger.Instance;

(bool createdByUs, BinaryData originalData) = await EnsureBlobContentCreatedAsync(blobClient, blobContent, logger).ConfigureAwait(false);

return new TemporaryBlobFile(blobClient, createdByUs, originalData, logger);
return UpsertFileAsync(blobContainerUri, blobName, blobContent, logger, CancellationToken.None);
}

/// <summary>
Expand All @@ -130,36 +134,94 @@
/// <param name="blobName">The name of the blob to upload.</param>
/// <param name="blobContent">The content of the blob to upload.</param>
/// <param name="logger">The logger to write diagnostic messages during the upload process.</param>
/// <param name="cancellationToken">The optional token to propagate notifications that the operation should be cancelled.</param>
/// <exception cref="ArgumentException">Thrown when the <paramref name="blobContainerUri"/> or the <paramref name="blobName"/> is blank.</exception>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="blobContainerUri"/> or the <paramref name="blobContent"/> is <c>null</c>.</exception>
public static Task<TemporaryBlobFile> UpsertFileAsync(Uri blobContainerUri, string blobName, BinaryData blobContent, ILogger logger)
/// <exception cref="RequestFailedException">Thrown when the interaction with Azure failed.</exception>
public static Task<TemporaryBlobFile> UpsertFileAsync(
Uri blobContainerUri,
string blobName,
BinaryData blobContent,
ILogger logger,
CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(blobContainerUri);
ArgumentException.ThrowIfNullOrWhiteSpace(blobName);
cancellationToken.ThrowIfCancellationRequested();

var containerClient = new BlobContainerClient(blobContainerUri, new DefaultAzureCredential());
BlobClient blobClient = containerClient.GetBlobClient(blobName);

return UpsertFileAsync(blobClient, blobContent, logger);
return UpsertFileAsync(blobClient, blobContent, logger, cancellationToken);
}

/// <summary>
/// Creates a new or replaces an existing Azure Blob file in an Azure Blob container.
/// </summary>
/// <remarks>
/// <para>⚡ Uses <see cref="DefaultAzureCredential"/> to authenticate with Azure Blob Storage.</para>
/// <para>⚡ File will be deleted (if new) or reverted (if existing) when the <see cref="TemporaryBlobFile"/> is disposed.</para>
/// </remarks>
/// <param name="blobClient">The Azure Blob client to interact with Azure Blob Storage.</param>
/// <param name="blobContent">The content of the blob to upload.</param>
/// <param name="logger">The logger to write diagnostic messages during the upload process.</param>
/// <exception cref="ArgumentNullException">Thrown when the <paramref name="blobClient"/> or the <paramref name="blobContent"/> is <c>null</c>.</exception>
/// <exception cref="RequestFailedException">Thrown when the interaction with Azure failed.</exception>
[Obsolete("Will be removed in v3, please use the " + nameof(UpsertFileAsync) + " overload instead that provides cancellation token support", DiagnosticId = ObsoleteDefaults.DiagnosticId)]

Check warning on line 170 in src/Arcus.Testing.Storage.Blob/TemporaryBlobFile.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Do not forget to remove this deprecated code someday.

See more on https://sonarcloud.io/project/issues?id=arcus-azure_arcus.testing&issues=AZyOnD5Sj1_gHa98x_9Q&open=AZyOnD5Sj1_gHa98x_9Q&pullRequest=508
public static Task<TemporaryBlobFile> UpsertFileAsync(BlobClient blobClient, BinaryData blobContent, ILogger logger)
{
return UpsertFileAsync(blobClient, blobContent, logger, CancellationToken.None);
}

/// <summary>
/// Creates a new or replaces an existing Azure Blob file in an Azure Blob container.
/// </summary>
/// <remarks>
/// <para>⚡ Uses <see cref="DefaultAzureCredential"/> to authenticate with Azure Blob Storage.</para>
/// <para>⚡ File will be deleted (if new) or reverted (if existing) when the <see cref="TemporaryBlobFile"/> is disposed.</para>
/// </remarks>
/// <param name="blobClient">The Azure Blob client to interact with Azure Blob Storage.</param>
/// <param name="blobContent">The content of the blob to upload.</param>
/// <param name="logger">The logger to write diagnostic messages during the upload process.</param>
/// <param name="cancellationToken">The optional token to propagate notifications that the operation should be cancelled.</param>
/// <exception cref="ArgumentNullException">Thrown when the <paramref name="blobClient"/> or the <paramref name="blobContent"/> is <c>null</c>.</exception>
/// <exception cref="RequestFailedException">Thrown when the interaction with Azure failed.</exception>
public static async Task<TemporaryBlobFile> UpsertFileAsync(
BlobClient blobClient,
BinaryData blobContent,
ILogger logger,
CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(blobClient);
ArgumentNullException.ThrowIfNull(blobContent);
cancellationToken.ThrowIfCancellationRequested();
logger ??= NullLogger.Instance;

(bool createdByUs, BinaryData originalData) =
await EnsureBlobContentCreatedAsync(blobClient, blobContent, logger, cancellationToken).ConfigureAwait(false);

return new TemporaryBlobFile(blobClient, createdByUs, originalData, logger);
}

private static async Task<(bool createdByUs, BinaryData originalData)> EnsureBlobContentCreatedAsync(
BlobClient client,
BinaryData newContent,
ILogger logger)
ILogger logger,
CancellationToken cancellationToken)
{
if (await client.ExistsAsync().ConfigureAwait(false))
cancellationToken.ThrowIfCancellationRequested();
if (await client.ExistsAsync(cancellationToken).ConfigureAwait(false))
Comment thread
stijnmoreels marked this conversation as resolved.
{
BlobDownloadResult originalContent = await client.DownloadContentAsync().ConfigureAwait(false);
BlobDownloadResult originalContent = await client.DownloadContentAsync(cancellationToken).ConfigureAwait(false);

logger.LogSetupReplaceFile(client.Name, client.AccountName, client.BlobContainerName);
await client.UploadAsync(newContent, overwrite: true).ConfigureAwait(false);
await client.UploadAsync(newContent, overwrite: true, cancellationToken).ConfigureAwait(false);

return (createdByUs: false, originalContent.Content);
}

logger.LogSetupUploadNewFile(client.Name, client.AccountName, client.BlobContainerName);
await client.UploadAsync(newContent).ConfigureAwait(false);
await client.UploadAsync(newContent, cancellationToken).ConfigureAwait(false);

return (createdByUs: true, originalData: null);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
namespace Arcus.Testing.Tests.Integration.Configuration
using Azure.Core;
using Azure.Identity;

namespace Arcus.Testing.Tests.Integration.Configuration
{
public class ServicePrincipal
{
Expand All @@ -19,6 +22,11 @@ public ServicePrincipal(string tenantId, string clientId, string clientSecret)
public string ClientSecret { get; }

public bool IsDefault => TenantId == "default" && ClientId == "default" && ClientSecret == "default";

public TokenCredential GetCredential()
{
return IsDefault ? new DefaultAzureCredential() : new ClientSecretCredential(TenantId, ClientId, ClientSecret);
}
}

public static partial class TestConfigExtensions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public sealed class TemporaryManagedIdentityConnection : IDisposable
/// </summary>
public TemporaryManagedIdentityConnection()
{
var configuration = TestConfig.Create();
var configuration = IntegrationTest.Configuration;
var logger = NullLogger.Instance;

ServicePrincipal servicePrincipal = configuration.GetServicePrincipal();
Expand Down
16 changes: 14 additions & 2 deletions src/Arcus.Testing.Tests.Integration/IntegrationTest.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System;
using Arcus.Testing.Tests.Integration.Configuration;
using Azure.Core;
using Bogus;
using Microsoft.Extensions.Logging;
using Xunit;
Expand All @@ -22,17 +24,27 @@ protected IntegrationTest(ITestOutputHelper outputWriter)
});

Logger = new XunitTestLogger(outputWriter);
}

static IntegrationTest()
{
Configuration = TestConfig.Create(options =>
{
options.AddOptionalJsonFile("appsettings.default.json")
.AddOptionalJsonFile("appsettings.local.json");
});
Credential = Configuration.GetServicePrincipal().GetCredential();
}

/// <summary>
/// Gets the current configuration loaded for this integration test suite.
/// Gets the shared configuration loaded for this integration test suite.
/// </summary>
public static TestConfig Configuration { get; }

/// <summary>
/// Gets the shared credential loaded for this integration test suite to authenticate with Azure resources.
/// </summary>
protected TestConfig Configuration { get; }
public static TokenCredential Credential { get; }

/// <summary>
/// Gets the logger to write diagnostic messages during the integration test execution.
Expand Down
Loading
Loading