Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(sb): add initial servicebus test components #201

Open
wants to merge 37 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
c07d3e9
pr-fix: correct merge w/ 'main'
stijnmoreels Aug 2, 2024
6b26d6d
Merge branch 'main' of https://github.com/stijnmoreels/arcus.testing
stijnmoreels Aug 2, 2024
0de9e56
Merge remote-tracking branch 'upstream/main'
stijnmoreels Aug 26, 2024
cbfd3d9
Merge remote-tracking branch 'upstream/main'
stijnmoreels Sep 6, 2024
cc62c4b
Merge remote-tracking branch 'upstream/main'
stijnmoreels Sep 13, 2024
7ad43ef
Merge remote-tracking branch 'upstream/main'
stijnmoreels Sep 23, 2024
483f449
Merge remote-tracking branch 'upstream/main'
stijnmoreels Sep 27, 2024
b496a00
Merge remote-tracking branch 'upstream/main'
stijnmoreels Oct 10, 2024
4d8b635
feat: add initial service-bus test components
stijnmoreels Oct 11, 2024
d996ec8
pr-add: provide net6.0 support
stijnmoreels Oct 14, 2024
be250d4
pr-fix: add missing xml comments
stijnmoreels Oct 14, 2024
2b8b374
pr-fix: add missing service-bus ns in appsettings
stijnmoreels Oct 14, 2024
46183ae
pr-fix: fallback w/ invalid BinaryData implementation
stijnmoreels Oct 17, 2024
3f70712
pr-fix: add service-bus namespace to test variables
stijnmoreels Oct 17, 2024
914b937
Merge branch 'main' into feature/add-servicebus-components
stijnmoreels Dec 20, 2024
782c766
Apply suggestions from code review
stijnmoreels Dec 20, 2024
57f368c
Apply suggestions from code review
stijnmoreels Dec 20, 2024
2add5e0
pr-fix: revert df unit test changes
stijnmoreels Dec 20, 2024
adc7467
pr-fix: use v1.13.1 az identity
stijnmoreels Dec 20, 2024
7d6075b
pr-add: az service bus namespace to deployment component
stijnmoreels Dec 20, 2024
6056d28
pr-fix: remove zone-redundant availability features
stijnmoreels Dec 20, 2024
abf80ed
pr-fix: disable zone redudant availability zones
stijnmoreels Dec 20, 2024
26ef680
pr-fix: correct cosmos db parameter setting
stijnmoreels Dec 20, 2024
b8e8170
pr-add: message handling in temp queue
stijnmoreels Jan 1, 2025
364901b
pr-add: az service bus topic sub rule test fixture
stijnmoreels Jan 1, 2025
1c0996a
pr-add: service bus message filter
stijnmoreels Jan 10, 2025
aa1cda6
pr-add: feature docs for functionality
stijnmoreels Jan 10, 2025
5c0ef1d
pr-fix: correct ctor update in unit tests
stijnmoreels Jan 10, 2025
04fc75a
temp commit
stijnmoreels Jan 13, 2025
ec0a678
Merge branch 'main' into feature/add-servicebus-components
stijnmoreels Jan 13, 2025
bb1a296
Merge branch 'feature/add-servicebus-components' of https://github.co…
stijnmoreels Jan 13, 2025
ea7e17d
pr-fix: add messaging project to solution
stijnmoreels Jan 13, 2025
bf8c666
pr-fix: complete merge w/ 'main'
stijnmoreels Jan 13, 2025
d217e06
pr-fix: disable nuget warnings as errors
stijnmoreels Jan 14, 2025
4abda9f
pr-fix: add warning when matches both custom message filters
stijnmoreels Jan 14, 2025
5ecc401
pr-add: missing arg check unit tests
stijnmoreels Jan 14, 2025
ad55717
pr-add: missing randomization topic sub creation options
stijnmoreels Jan 14, 2025
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
7 changes: 3 additions & 4 deletions build/variables/test.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
variables:
Arcus.Testing.ResourceGroup.Name: 'arcus-testing-dev-we-rg'
Arcus.Testing.DataFactory.Name: 'arcus-testing-adf'

Arcus.Testing.DataFactory.Name: 'arcus-testing-adf'
Arcus.Testing.Cosmos.MongoDb.Name: 'arcus-testing-cosmos-mongo'
Arcus.Testing.Cosmos.MongoDb.DatabaseName: 'arcus-testing-cosmos-mongo-db'
Arcus.Testing.Cosmos.NoSql.Name: 'arcus-testing-cosmos-nosql'
Arcus.Testing.Cosmos.NoSql.DatabaseName: 'arcus-testing-cosmos-nosql-db'

Arcus.Testing.Cosmos.NoSql.DatabaseName: 'arcus-testing-cosmos-nosql-db'
Arcus.Testing.KeyVault.Name: 'arcus-testing-kv'
Arcus.Testing.ServiceBus.Namespace: 'arcus-messaging-servicebus'
Arcus.Testing.StorageAccount.Name: 'arcustestingstorage'
Arcus.Testing.StorageAccount.Key.SecretName: 'Arcus-Testing-StorageAccount-Key'
71 changes: 71 additions & 0 deletions docs/preview/02-Features/05-Messaging/01-servicebus.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';

# Service bus
The `Arcus.Testing.Messaging.ServiceBus` package provides test fixtures related to Azure Service Bus. By using the common testing practice 'clean environment', it provides a temporary Topic (subscription) and queue.

## Installation
The following functionality is available when installing this package:

```powershell
PM> Install-Package -Name Arcus.Testing.Messaging.ServiceBus
```

<Tabs groupId="messaging-systems">
<TabItem value="topic" label="Topic" default>

## Temporary topic
The `TemporaryTopic` provides a solution when the integration test requires an Azure Service Bus topic during the test run. A topic is created upon the setup of the test fixture and is deleted again when the test fixture is disposed.

> ✨ Only when the test fixture was responsible for creating the topic, will the topic be deleted upon the fixture's disposal. This follows the 'clean environment' testing principle that describes that after the test run, the same state should be achieved as before the test run.

```csharp
using Arcus.Testing;

await using var topic = await TemporaryTopic.CreateIfNotExistsAsync(
"<fully-qualified-namespace>", "<topic-name>", logger);
```

> ⚡ Uses by default the [`DefaultAzureCredential`](https://learn.microsoft.com/en-us/dotnet/api/azure.identity.defaultazurecredential) but other type of authentication mechanisms are supported with overloads.

Adding subscriptions to the topic can also be done via the test fixture. It always makes sure that any added subscriptions are deleted again afterwards.

```csharp
using Arcus.Testing;

await using TemporaryTopic topic = ...

await topic.AddSubscriptionAsync("<subscription-name>");
```

### Temporary topic subscription
While topic subscriptions can be added to a topic via the `AddSubscription` on the `TemporaryTopic` test fixture, the library also supports adding such subscriptions directly with a dedicated fixture.

```csharp
using Arcus.Testing;

await var subscription = await TemporaryTopicSubscription.CreateIfNotExistsAsync(
"<fully-qualified-namespace>", "<topic-name>", "<subscription-name>", logger);
```

> ⚠️ Make sure that there is an Azure Service Bus topic available at the time of setting up this test fixture, otherwise an exception will be thrown.

</TabItem>
<TabItem value="queue" label="Queue">

## Temporary queue
The `TemporaryQueue` provides a solution when the integration test requires an Azure Service Bus queue during the test run. A queue is created upon the setup of the test fixture and is deleted again when the test fixture is disposed.

> ✨ Only when the test fixture was responsible for creating the queue, will the queue be deleted upon the fixture's disposal. This follows the 'clean environment' testing principle that describes that after the test run, the same state should be achieved as before the test run.

```csharp
using Arcus.Testing;

await using var queue = await TemporaryQueue.CreateIfNotExistsAsync(
"<fully-qualified-namespace>", "<queue-name>", logger);
```

> ⚡ Uses by default the [`DefaultAzureCredential`](https://learn.microsoft.com/en-us/dotnet/api/azure.identity.defaultazurecredential) but other type of authentication mechanisms are supported with overloads.

</TabItem>
</Tabs>
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>net8.0;net6.0</TargetFrameworks>
<Authors>Arcus</Authors>
<Company>Arcus</Company>
<Description>Provides messaging capabilities for Azure Service Bus during Arcus testing</Description>
<Copyright>Copyright (c) Arcus</Copyright>
<PackageProjectUrl>https://github.com/arcus-azure/arcus.testing</PackageProjectUrl>
<RepositoryUrl>https://github.com/arcus-azure/arcus.testing</RepositoryUrl>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<PackageIcon>icon.png</PackageIcon>
<PackageReadmeFile>README.md</PackageReadmeFile>
<RepositoryType>Git</RepositoryType>
<PackageTags>Azure;Testing;Messaging;ServiceBus</PackageTags>
<PackageId>Arcus.Testing.Messaging.ServiceBus</PackageId>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>

<ItemGroup>
<None Include="..\..\README.md" Pack="true" PackagePath="\" />
<None Include="..\..\LICENSE" Pack="true" PackagePath="\" />
<None Include="..\..\docs\static\img\icon.png" Pack="true" PackagePath="\" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Azure.Identity" Version="[1.13.0,2.0.0)" />
<PackageReference Include="Azure.Messaging.ServiceBus" Version="[7.18.2,8.0.0)" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Arcus.Testing.Core\Arcus.Testing.Core.csproj" />
</ItemGroup>

</Project>
160 changes: 160 additions & 0 deletions src/Arcus.Testing.Messaging.ServiceBus/TemporaryQueue.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
using System;
using System.Threading.Tasks;
using Azure.Identity;
using Azure.Messaging.ServiceBus.Administration;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;

namespace Arcus.Testing.Messaging.ServiceBus
{
/// <summary>
/// Represents a temporary Azure Service Bus queue that will be deleted when the instance is disposed.
/// </summary>
public class TemporaryQueue : IAsyncDisposable
{
private readonly ServiceBusAdministrationClient _client;
private readonly string _serviceBusNamespace;
private readonly bool _createdByUs;
private readonly ILogger _logger;

private TemporaryQueue(
ServiceBusAdministrationClient client,
string serviceBusNamespace,
string queueName,
bool createdByUs,
ILogger logger)
{
ArgumentNullException.ThrowIfNull(client);

_client = client;
_serviceBusNamespace = serviceBusNamespace;
_createdByUs = createdByUs;
_logger = logger;

Name = queueName;
}

/// <summary>
/// Gets the name of the Azure Service Bus queue that is possibly created by the test fixture.
/// </summary>
public string Name { get; }

/// <summary>
/// Creates a new instance of the <see cref="TemporaryQueue"/> which creates a new Azure Service Bus queue if it doesn't exist yet.
/// </summary>
/// <param name="fullyQualifiedNamespace">
/// The fully qualified Service Bus namespace to connect to. This is likely to be similar to <c>{yournamespace}.servicebus.windows.net</c>.
/// </param>
/// <param name="queueName">The name of the Azure Service Bus queue that should be created.</param>
/// <param name="logger">The logger to write diagnostic messages during the lifetime of the Azure Service Bus queue.</param>
/// <exception cref="ArgumentException">
/// Thrown when the <paramref name="fullyQualifiedNamespace"/> or the <paramref name="queueName"/> is blank.
/// </exception>
public static async Task<TemporaryQueue> CreateIfNotExistsAsync(string fullyQualifiedNamespace, string queueName, ILogger logger)
{
return await CreateIfNotExistsAsync(fullyQualifiedNamespace, queueName, logger, configureOptions: null);
}

/// <summary>
/// Creates a new instance of the <see cref="TemporaryQueue"/> which creates a new Azure Service Bus queue if it doesn't exist yet.
/// </summary>
/// <param name="fullyQualifiedNamespace">
/// The fully qualified Service Bus namespace to connect to. This is likely to be similar to <c>{yournamespace}.servicebus.windows.net</c>.
/// </param>
/// <param name="queueName">The name of the Azure Service Bus queue that should be created.</param>
/// <param name="logger">The logger to write diagnostic messages during the lifetime of the Azure Service Bus queue.</param>
/// <param name="configureOptions">
/// The function to configure the additional options that describes how the Azure Service Bus queue should be created.
/// </param>
/// <exception cref="ArgumentException">
/// Thrown when the <paramref name="fullyQualifiedNamespace"/> or the <paramref name="queueName"/> is blank.
/// </exception>
public static async Task<TemporaryQueue> CreateIfNotExistsAsync(
string fullyQualifiedNamespace,
string queueName,
ILogger logger,
Action<CreateQueueOptions> configureOptions)
{
if (string.IsNullOrWhiteSpace(fullyQualifiedNamespace))
{
throw new ArgumentException(
"Requires a non-blank fully-qualified Azure Service bus namespace to set up a temporary queue", nameof(fullyQualifiedNamespace));
}

var client = new ServiceBusAdministrationClient(fullyQualifiedNamespace, new DefaultAzureCredential());
return await CreateIfNotExistsAsync(client, queueName, logger, configureOptions);
}

/// <summary>
/// Creates a new instance of the <see cref="TemporaryQueue"/> which creates a new Azure Service Bus queue if it doesn't exist yet.
/// </summary>
/// <param name="adminClient">The administration client to interact with the Azure Service Bus resource where the topic should be created.</param>
/// <param name="queueName">The name of the Azure Service Bus queue that should be created.</param>
/// <param name="logger">The logger to write diagnostic messages during the lifetime of the Azure Service Bus queue.</param>
/// <exception cref="ArgumentNullException">Thrown when the <paramref name="adminClient"/> is <c>null</c>.</exception>
/// <exception cref="ArgumentException">Thrown when the <paramref name="queueName"/> is blank.</exception>
public static async Task<TemporaryQueue> CreateIfNotExistsAsync(ServiceBusAdministrationClient adminClient, string queueName, ILogger logger)
{
return await CreateIfNotExistsAsync(adminClient, queueName, logger, configureOptions: null);
}

/// <summary>
/// Creates a new instance of the <see cref="TemporaryQueue"/> which creates a new Azure Service Bus queue if it doesn't exist yet.
/// </summary>
/// <param name="adminClient">The administration client to interact with the Azure Service Bus resource where the topic should be created.</param>
/// <param name="queueName">The name of the Azure Service Bus queue that should be created.</param>
/// <param name="logger">The logger to write diagnostic messages during the lifetime of the Azure Service Bus queue.</param>
/// <param name="configureOptions">
/// The function to configure the additional options that describes how the Azure Service Bus queue should be created.
/// </param>
/// <exception cref="ArgumentNullException">Thrown when the <paramref name="adminClient"/> is <c>null</c>.</exception>
/// <exception cref="ArgumentException">Thrown when the <paramref name="queueName"/> is blank.</exception>
public static async Task<TemporaryQueue> CreateIfNotExistsAsync(
ServiceBusAdministrationClient adminClient,
string queueName,
ILogger logger,
Action<CreateQueueOptions> configureOptions)
{
ArgumentNullException.ThrowIfNull(adminClient);
logger ??= NullLogger.Instance;

if (string.IsNullOrWhiteSpace(queueName))
{
throw new ArgumentException(
"Requires a non-blank Azure Service bus queue name to set up a temporary queue", nameof(queueName));
}

var options = new CreateQueueOptions(queueName);
configureOptions?.Invoke(options);

NamespaceProperties properties = await adminClient.GetNamespacePropertiesAsync();
string serviceBusNamespace = properties.Name;

if (await adminClient.QueueExistsAsync(options.Name))
{
logger.LogTrace("[Test:Setup] Use already existing Azure Service Bus queue '{QueueName}' in namespace '{Namespace}'", options.Name, serviceBusNamespace);
return new TemporaryQueue(adminClient, serviceBusNamespace, options.Name, createdByUs: false, logger);
}

logger.LogTrace("[Test:Setup] Create new Azure Service Bus queue '{Queue}' in namespace '{Namespace}'", options.Name, serviceBusNamespace);
await adminClient.CreateQueueAsync(options);

return new TemporaryQueue(adminClient, serviceBusNamespace, options.Name, createdByUs: true, logger);
}

/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources asynchronously.
/// </summary>
/// <returns>A task that represents the asynchronous dispose operation.</returns>
public async ValueTask DisposeAsync()
{
if (_createdByUs && await _client.QueueExistsAsync(Name))
{
_logger.LogTrace("[Test:Teardown] Delete Azure Service Bus queue '{QueueName}' in namespace '{Namespace}'", Name, _serviceBusNamespace);
await _client.DeleteQueueAsync(Name);
}

GC.SuppressFinalize(this);
}
}
}
Loading
Loading