diff --git a/src/BuslyCLI.Console/BuslyCLI.Console.csproj b/src/BuslyCLI.Console/BuslyCLI.Console.csproj
index b9b9783..8d3ec80 100644
--- a/src/BuslyCLI.Console/BuslyCLI.Console.csproj
+++ b/src/BuslyCLI.Console/BuslyCLI.Console.csproj
@@ -33,9 +33,11 @@
+
+
diff --git a/src/BuslyCLI.Console/Config/AzureStorageQueuesTransportConfig.cs b/src/BuslyCLI.Console/Config/AzureStorageQueuesTransportConfig.cs
new file mode 100644
index 0000000..2548d80
--- /dev/null
+++ b/src/BuslyCLI.Console/Config/AzureStorageQueuesTransportConfig.cs
@@ -0,0 +1,6 @@
+namespace BuslyCLI.Config;
+
+public class AzureStorageQueuesTransportConfig : ITransportConfig
+{
+ public string ConnectionString { get; set; }
+}
\ No newline at end of file
diff --git a/src/BuslyCLI.Console/Config/TransportConfig.cs b/src/BuslyCLI.Console/Config/TransportConfig.cs
index 7bc8da4..980ca7b 100644
--- a/src/BuslyCLI.Console/Config/TransportConfig.cs
+++ b/src/BuslyCLI.Console/Config/TransportConfig.cs
@@ -9,7 +9,7 @@ public class TransportConfig
public RabbitmqTransportConfig RabbitmqTransportConfig { get; set; }
public AmazonsqsTransportConfig AmazonsqsTransportConfig { get; set; }
public AzureServiceBusTransportConfig AzureServiceBusTransportConfig { get; set; }
-
+ public AzureStorageQueuesTransportConfig AzureStorageQueuesTransportConfig { get; set; }
public SqlServerTransportConfig SqlServerTransportConfig { get; set; }
// Helper property to unify config access:
@@ -18,5 +18,6 @@ public class TransportConfig
?? (ITransportConfig)RabbitmqTransportConfig
?? (ITransportConfig)AmazonsqsTransportConfig
?? (ITransportConfig)AzureServiceBusTransportConfig
+ ?? (ITransportConfig)AzureStorageQueuesTransportConfig
?? SqlServerTransportConfig;
}
\ No newline at end of file
diff --git a/src/BuslyCLI.Console/Config/Validators/AzureStorageQueuesTransportConfigValidator.cs b/src/BuslyCLI.Console/Config/Validators/AzureStorageQueuesTransportConfigValidator.cs
new file mode 100644
index 0000000..553e7a0
--- /dev/null
+++ b/src/BuslyCLI.Console/Config/Validators/AzureStorageQueuesTransportConfigValidator.cs
@@ -0,0 +1,12 @@
+using FluentValidation;
+
+namespace BuslyCLI.Config.Validators;
+
+public class AzureStorageQueuesTransportConfigValidator : AbstractValidator
+{
+ public AzureStorageQueuesTransportConfigValidator()
+ {
+ RuleFor(x => x.ConnectionString)
+ .NotEmpty();
+ }
+}
\ No newline at end of file
diff --git a/src/BuslyCLI.Console/DependencyInjection/ServiceCollectionExtensions.cs b/src/BuslyCLI.Console/DependencyInjection/ServiceCollectionExtensions.cs
index d1c350e..9e98f4c 100644
--- a/src/BuslyCLI.Console/DependencyInjection/ServiceCollectionExtensions.cs
+++ b/src/BuslyCLI.Console/DependencyInjection/ServiceCollectionExtensions.cs
@@ -35,6 +35,7 @@ private static IServiceCollection AddYamlDeserializer(this IServiceCollection se
{ "rabbitmq-transport-config", typeof(RabbitmqTransportConfig) },
{ "amazonsqs-transport-config", typeof(AmazonsqsTransportConfig) },
{ "azure-service-bus-transport-config", typeof(AzureServiceBusTransportConfig) },
+ { "azure-storage-queues-transport-config", typeof(AzureStorageQueuesTransportConfig) },
{ "sql-server-transport-config", typeof(SqlServerTransportConfig) }
};
diff --git a/src/BuslyCLI.Console/Factories/RawEndpointFactory.cs b/src/BuslyCLI.Console/Factories/RawEndpointFactory.cs
index a70cc49..c9125ba 100644
--- a/src/BuslyCLI.Console/Factories/RawEndpointFactory.cs
+++ b/src/BuslyCLI.Console/Factories/RawEndpointFactory.cs
@@ -30,6 +30,8 @@ private TransportDefinition CreateTransport(TransportConfig transportConfig)
return CreateRabbitMQTransport(rabbitmqTransportConfig);
case AzureServiceBusTransportConfig azureServiceBusTransportConfig:
return CreateAzureServiceBusTransport(azureServiceBusTransportConfig.ConnectionString);
+ case AzureStorageQueuesTransportConfig azureStorageQueuesTransportConfig:
+ return CreateAzureStorageQueuesTransport(azureStorageQueuesTransportConfig.ConnectionString);
case AmazonsqsTransportConfig amazonSqsTransportConfig:
return CreateAmazonSQSTransport(amazonSqsTransportConfig);
case SqlServerTransportConfig sqlServerTransportConfig:
@@ -45,6 +47,13 @@ private TransportDefinition CreateTransport(TransportConfig transportConfig)
}
}
+ private TransportDefinition CreateAzureStorageQueuesTransport(string connectionString)
+ {
+ var transport = new AzureStorageQueueTransport(connectionString);
+ transport.MessageWrapperSerializationDefinition = new SystemJsonSerializer();
+ return transport;
+ }
+
private TransportDefinition CreateSqlServerTransport(SqlServerTransportConfig sqlServerTransportConfig)
{
return new SqlServerTransport(sqlServerTransportConfig.ConnectionString);
diff --git a/tests/BuslyCLI.Console.Tests/BuslyCLI.Console.Tests.csproj b/tests/BuslyCLI.Console.Tests/BuslyCLI.Console.Tests.csproj
index 89a262b..37f9afa 100644
--- a/tests/BuslyCLI.Console.Tests/BuslyCLI.Console.Tests.csproj
+++ b/tests/BuslyCLI.Console.Tests/BuslyCLI.Console.Tests.csproj
@@ -24,6 +24,7 @@
+
diff --git a/tests/BuslyCLI.Console.Tests/EndToEnd/AzureStorageQueues/AzureStorageQueuesEndToEndTestBase.cs b/tests/BuslyCLI.Console.Tests/EndToEnd/AzureStorageQueues/AzureStorageQueuesEndToEndTestBase.cs
new file mode 100644
index 0000000..0b9b1ae
--- /dev/null
+++ b/tests/BuslyCLI.Console.Tests/EndToEnd/AzureStorageQueues/AzureStorageQueuesEndToEndTestBase.cs
@@ -0,0 +1,21 @@
+using Testcontainers.Azurite;
+
+namespace BuslyCLI.Console.Tests.EndToEnd.AzureStorageQueues;
+
+[TestFixture]
+public abstract class AzureStorageQueuesEndToEndTestBase : SingletonTestFixtureBase
+{
+ protected AzuriteContainer AzuriteContainer => Container;
+
+ protected override AzuriteContainer CreateContainer()
+ {
+ return new AzuriteBuilder()
+ .WithCommand("--skipApiVersionCheck")
+ .Build();
+ }
+
+ protected override async Task StartContainerAsync(AzuriteContainer container)
+ {
+ await container.StartAsync();
+ }
+}
\ No newline at end of file
diff --git a/tests/BuslyCLI.Console.Tests/EndToEnd/AzureStorageQueues/SendCommandAzureStorageQueuesEndToEndTests.cs b/tests/BuslyCLI.Console.Tests/EndToEnd/AzureStorageQueues/SendCommandAzureStorageQueuesEndToEndTests.cs
new file mode 100644
index 0000000..e1ddd9d
--- /dev/null
+++ b/tests/BuslyCLI.Console.Tests/EndToEnd/AzureStorageQueues/SendCommandAzureStorageQueuesEndToEndTests.cs
@@ -0,0 +1,119 @@
+using System.Text;
+using System.Text.Json;
+using BuslyCLI.Console.Tests.EndToEnd.Infrastructure;
+using BuslyCLI.Console.Tests.TestHelpers;
+using BuslyCLI.DependencyInjection;
+using BuslyCLI.Spectre;
+using Microsoft.Extensions.DependencyInjection;
+using Spectre.Console.Cli.Extensions.DependencyInjection;
+using Spectre.Console.Cli.Testing;
+
+namespace BuslyCLI.Console.Tests.EndToEnd.AzureStorageQueues;
+
+[TestFixture]
+public class SendCommandAzureStorageQueuesEndToEndTests : AzureStorageQueuesEndToEndTestBase
+{
+ [SetUp]
+ public void Setup()
+ {
+ var registrations = new ServiceCollection();
+ registrations.AddBuslyCLIServices();
+ using var registrar = new DependencyInjectionRegistrar(registrations);
+ _sut = new CommandAppTester(registrar);
+ _sut.Configure(AppConfiguration.GetSpectreCommandConfiguration());
+ }
+
+ private CommandAppTester _sut;
+
+ private readonly JsonSerializerOptions _jsonObjectOptions =
+ new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, WriteIndented = true };
+
+ [Test]
+ public async Task ShouldSendCommand()
+ {
+ await RunWithTestEndpoint(async testEndpoint =>
+ {
+ // Arrange
+ await testEndpoint.StartEndpoint();
+ var messageBody = new { OrderNumber = Guid.NewGuid() };
+ var json = JsonSerializer.Serialize(messageBody, _jsonObjectOptions);
+ var yamlFile = $"""
+ ---
+ current-transport: local-azure-storage-queues
+ transports:
+ - name: local-azure-storage-queues
+ azure-storage-queues-transport-config:
+ connection-string: {Container.GetConnectionString()}
+ """;
+ using var configFile = new TestableNServiceBusConfigurationFile(yamlFile);
+
+ // Act
+ var result = _sut.Run(
+ "command",
+ "send",
+ "--content-type", "application/json",
+ "--enclosed-message-type", "MessageContracts.Commands.CreateOrder",
+ "--destination-endpoint", testEndpoint.EndpointName,
+ "--message-body", json,
+ "--config", configFile.FilePath);
+
+ // Assert
+ Assert.That(result.ExitCode, Is.EqualTo(0));
+ var message = testEndpoint.TryReceiveMessage();
+ Assert.That(message.Headers["NServiceBus.EnclosedMessageTypes"],
+ Is.EqualTo("MessageContracts.Commands.CreateOrder"));
+ Assert.That(message.Headers["NServiceBus.ContentType"], Is.EqualTo("application/json"));
+ Assert.That(Encoding.UTF8.GetString(message.Body.Span), Is.EqualTo(json));
+ });
+ }
+
+ [Test]
+ public async Task ShouldPublishEvent()
+ {
+ await RunWithTestEndpoint(async testEndpoint =>
+ {
+ // Arrange
+ await testEndpoint.StartEndpoint();
+ await testEndpoint.Subscribe("MessageContracts.Events.OrderCreated");
+ var messageBody = new { OrderNumber = Guid.NewGuid() };
+ var json = JsonSerializer.Serialize(messageBody, _jsonObjectOptions);
+ var yamlFile = $"""
+ ---
+ current-transport: local-azure-storage-queues
+ transports:
+ - name: local-azure-storage-queues
+ azure-storage-queues-transport-config:
+ connection-string: {Container.GetConnectionString()}
+ """;
+ using var configFile = new TestableNServiceBusConfigurationFile(yamlFile);
+
+ // Act
+ var result = _sut.Run(
+ "event",
+ "publish",
+ "--content-type", "application/json",
+ "--enclosed-message-type", "MessageContracts.Events.OrderCreated",
+ "--message-body", json,
+ "--config", configFile.FilePath);
+
+ // Assert
+ Assert.That(result.ExitCode, Is.EqualTo(0));
+ var message = testEndpoint.TryReceiveMessage();
+ Assert.That(message.Headers["NServiceBus.EnclosedMessageTypes"],
+ Is.EqualTo("MessageContracts.Events.OrderCreated"));
+ Assert.That(message.Headers["NServiceBus.ContentType"], Is.EqualTo("application/json"));
+ Assert.That(Encoding.UTF8.GetString(message.Body.Span), Is.EqualTo(json));
+ });
+ }
+
+ // Test Endpoint
+ // Example of how to wait for and get messages
+ // https://github.com/Particular/NServiceBus.RabbitMQ/blob/dba627a5a2c50519d7a2466efe3f76c8d5c8828d/src/NServiceBus.Transport.RabbitMQ.Tests/RabbitMqContext.cs#L41
+ private async Task RunWithTestEndpoint(Func testAction)
+ {
+ var testEndpoint = await new TestEndpointFactory().CreateAzureStorageQueuesTestEndpoint(Container.GetConnectionString());
+
+ await testAction(testEndpoint);
+ await testEndpoint.ShutDownAndCleanUp();
+ }
+}
\ No newline at end of file
diff --git a/tests/BuslyCLI.Console.Tests/EndToEnd/Infrastructure/ITestEndpointFactory.cs b/tests/BuslyCLI.Console.Tests/EndToEnd/Infrastructure/ITestEndpointFactory.cs
index 6144c12..ca849f3 100644
--- a/tests/BuslyCLI.Console.Tests/EndToEnd/Infrastructure/ITestEndpointFactory.cs
+++ b/tests/BuslyCLI.Console.Tests/EndToEnd/Infrastructure/ITestEndpointFactory.cs
@@ -1,6 +1,7 @@
using Amazon.Runtime;
using Amazon.SimpleNotificationService;
using Amazon.SQS;
+using NServiceBus.Settings;
using NServiceBus.Transport;
namespace BuslyCLI.Console.Tests.EndToEnd.Infrastructure;
@@ -67,6 +68,7 @@ public async Task CreateAzureServiceBusTestEndpoint(string endpoin
private static async Task InternalCreateTestEndpoint(string endpointName,
TransportDefinition transport)
{
+
var hostSettings = new HostSettings(
endpointName,
endpointName,
@@ -76,7 +78,8 @@ private static async Task InternalCreateTestEndpoint(string endpoi
TestContext.Out.WriteLine("Critical error: " + exception);
},
// TODO: This needs to be false for "Azure Service Bus Emulator" tests to pass
- transport is not AzureServiceBusTransport);
+ transport is not AzureServiceBusTransport
+ );
var infrastructure = await transport.Initialize(hostSettings, [
new ReceiveSettings(
@@ -97,4 +100,13 @@ public async Task CreateSqlServerTestEndpoint(string sqlConnection
var transport = new SqlServerTransport(sqlConnectionString);
return await InternalCreateTestEndpoint(name, transport);
}
+
+ public async Task CreateAzureStorageQueuesTestEndpoint(string connectionString)
+ {
+ var name = GenerateUniqueEndpointName();
+ var transport = new AzureStorageQueueTransport(connectionString);
+ transport.MessageWrapperSerializationDefinition = new SystemJsonSerializer();
+
+ return await InternalCreateTestEndpoint(name, transport);
+ }
}
\ No newline at end of file
diff --git a/tests/BuslyCLI.Console.Tests/EndToEnd/SqlServer/SqlServerEndToEndTestBase.cs b/tests/BuslyCLI.Console.Tests/EndToEnd/SqlServer/SqlServerEndToEndTestBase.cs
index 23f4b75..09a89f0 100644
--- a/tests/BuslyCLI.Console.Tests/EndToEnd/SqlServer/SqlServerEndToEndTestBase.cs
+++ b/tests/BuslyCLI.Console.Tests/EndToEnd/SqlServer/SqlServerEndToEndTestBase.cs
@@ -1,5 +1,4 @@
-using Microsoft.Data.SqlClient;
-using Testcontainers.MsSql;
+using Testcontainers.MsSql;
namespace BuslyCLI.Console.Tests.EndToEnd.SqlServer;
diff --git a/website/docs/transports/azure-storage-queues.md b/website/docs/transports/azure-storage-queues.md
new file mode 100644
index 0000000..d0bc085
--- /dev/null
+++ b/website/docs/transports/azure-storage-queues.md
@@ -0,0 +1,40 @@
+# Azure Storage Queues
+
+The **Azure Storage Queues Transport** is used to communicate to Azure Storage Queues. It is suitable for development, testing, and production environments.
+
+## Configuration
+
+To use the Azure Storage Queues Transport, define it under `transports` and reference it as `current-transport`.
+
+### Example
+
+```yaml
+current-transport: local-azure-service-bus
+
+transports:
+ - name: local-azure-service-bus
+ azure-storage-queues-transport-config:
+ connection-string: Endpoint=amqp://127.0.0.1:32799/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=SAS_KEY_VALUE;UseDevelopmentEmulator=true
+```
+
+---
+
+## `azure-storage-queues-transport-config` Fields
+
+| Field | Required | Type | Default | Description |
+| ------------------- | -------- | ------ | ------- | ------------------------------------------ |
+| `connection-string` | **Yes** | string | — | Connection string to Azure Storage Queues. |
+
+---
+
+## Field Details
+
+### `connection-string` (required)
+
+A standard conection string for Azure Storage Queues.
+
+Examples:
+
+```yaml
+connection-string: UseDevelopmentStorage=true
+```