diff --git a/src/BuslyCLI.Console/Commands/NsbTimeout/SendTimeout.cs b/src/BuslyCLI.Console/Commands/NsbTimeout/SendTimeout.cs new file mode 100644 index 00000000..6c46ea18 --- /dev/null +++ b/src/BuslyCLI.Console/Commands/NsbTimeout/SendTimeout.cs @@ -0,0 +1,78 @@ +using System.Text; +using BuslyCLI.Config; +using BuslyCLI.Config.Transports; +using BuslyCLI.Infrastructure.Factories; +using NServiceBus.DelayedDelivery; +using NServiceBus.Routing; +using NServiceBus.Transport; +using Spectre.Console; +using Spectre.Console.Cli; + +namespace BuslyCLI.Commands.NsbTimeout; + +public class SendTimeout(IAnsiConsole console, IRawEndpointFactory rawEndpointFactory, INServiceBusConfiguration nServiceBusConfiguration) : AsyncCommand +{ + private static readonly HashSet UnsupportedTransportTypes = + [ + typeof(SqlServerTransportConfig), + typeof(PostgreSqlTransportConfig), + typeof(AzureStorageQueuesTransportConfig) + ]; + + protected override async Task ExecuteAsync(CommandContext context, SendTimeoutCommandSettings settings, CancellationToken cancellationToken) + { + var config = await nServiceBusConfiguration.GetValidatedConfigurationAsync(settings.Config.Path); + + if (UnsupportedTransportTypes.Contains(config.CurrentTransportConfig.Config.GetType())) + { + console.MarkupLine($"[red]Error:[/] The [bold]{config.CurrentTransportConfig.Config.GetType().Name.Replace("Config", "")}[/] transport does not support sending timeouts."); + console.MarkupLine("This transport relies on an in-process poller to forward deferred messages, which is incompatible with the CLI's fire-and-forget execution model."); + console.MarkupLine("For details see: [link]https://tragiccode.com/busly-cli/docs/cli-reference/timeout/send[/]"); + return 1; + } + + var rawEndpoint = await rawEndpointFactory.CreateRawSendOnlyEndpoint(Constants.DefaultOriginatingEndpoint, config.CurrentTransportConfig); + // TODO: Validate body is valid json/xml + var headers = new Dictionary + { + ["NServiceBus.OriginatingEndpoint"] = Constants.DefaultOriginatingEndpoint, + ["NServiceBus.OriginatingMachine"] = Environment.MachineName, + ["NServiceBus.ConversationId"] = Guid.NewGuid().ToString(), + ["NServiceBus.CorrelationId"] = Guid.NewGuid().ToString(), + ["NServiceBus.MessageIntent"] = Constants.NServiceBus.CommandMessageIntent, + ["NServiceBus.ContentType"] = settings.ContentType, + ["NServiceBus.EnclosedMessageTypes"] = settings.EnclosedMessageType + }; + var message = new OutgoingMessage( + Guid.NewGuid().ToString(), + headers, + Encoding.ASCII.GetBytes(settings.MessageBody) + ); + + var dispatchProperties = new DispatchProperties(); + + if (settings.DoNotDeliverBefore is not null) + { + dispatchProperties.DoNotDeliverBefore = new DoNotDeliverBefore(settings.DoNotDeliverBefore.Value); + } + else if (settings.DelayDeliveryWith is not null) + { + dispatchProperties.DelayDeliveryWith = new DelayDeliveryWith(settings.DelayDeliveryWith.Value); + } + + var transportOperation = new TransportOperation( + message, + new UnicastAddressTag(settings.DestinationEndpoint), + dispatchProperties + ); + + await rawEndpoint.Dispatch( + new TransportOperations(transportOperation), + new TransportTransaction(), + cancellationToken); + + await rawEndpoint.ShutDownAndCleanUp(); + + return 0; + } +} \ No newline at end of file diff --git a/src/BuslyCLI.Console/Commands/NsbTimeout/SendTimeoutCommandSettings.cs b/src/BuslyCLI.Console/Commands/NsbTimeout/SendTimeoutCommandSettings.cs new file mode 100644 index 00000000..6b144326 --- /dev/null +++ b/src/BuslyCLI.Console/Commands/NsbTimeout/SendTimeoutCommandSettings.cs @@ -0,0 +1,38 @@ +using System.ComponentModel; +using Spectre.Console; +using Spectre.Console.Cli; + +namespace BuslyCLI.Commands.NsbTimeout; + +public class SendTimeoutCommandSettings : CommonCommandSettings +{ + [CommandOption("--do-not-deliver-before ")] + [Description("Allows specifying a date before which the delivery should not occur, using ISO-8601 format (YYYY-MM-DDTHH:mm:ssZ)")] + public DateTime? DoNotDeliverBefore { get; init; } + + [CommandOption("--delay-delivery-with ")] + // ([days.]hh:mm:ss[.fffffff]) + [Description("Specifies the delay before the timeout is delivered, using a TimeSpan format")] + public TimeSpan? DelayDeliveryWith { get; init; } + + public override ValidationResult Validate() + { + var baseResult = base.Validate(); + if (baseResult.Successful == false) return baseResult; + // Neither provided + if (DelayDeliveryWith is null && DoNotDeliverBefore is null) + { + return ValidationResult.Error( + "You must specify either --do-not-deliver-before or --delay-delivery-with."); + } + + // Both provided + if (DelayDeliveryWith is not null && DoNotDeliverBefore is not null) + { + return ValidationResult.Error( + "--do-not-deliver-before and --delay-delivery-with cannot be used together."); + } + + return ValidationResult.Success(); + } +} \ No newline at end of file diff --git a/src/BuslyCLI.Console/Infrastructure/AppConfiguration.cs b/src/BuslyCLI.Console/Infrastructure/AppConfiguration.cs index 0fdcca0a..e2f0a9c8 100644 --- a/src/BuslyCLI.Console/Infrastructure/AppConfiguration.cs +++ b/src/BuslyCLI.Console/Infrastructure/AppConfiguration.cs @@ -2,6 +2,7 @@ using BuslyCLI.Commands.Demo; using BuslyCLI.Commands.NsbCommand; using BuslyCLI.Commands.NsbEvent; +using BuslyCLI.Commands.NsbTimeout; using BuslyCLI.Commands.Transport; using Spectre.Console; using Spectre.Console.Cli; @@ -48,6 +49,13 @@ public static Action GetSpectreCommandConfiguration() .WithAlias("p") .WithDescription("Publish an event to subscribing endpoints."); }); + config.AddBranch("timeout", timeout => + { + timeout.SetDescription("Operations related to NServiceBus timeouts."); + timeout.AddCommand("send") + .WithAlias("s") + .WithDescription("Send a timeout message to an endpoint."); + }); config.AddBranch("demo", demo => { demo.SetDescription("Demo mode for the busly quick start guide."); diff --git a/src/BuslyCLI.Console/Infrastructure/Endpoints/RawEndpoint.cs b/src/BuslyCLI.Console/Infrastructure/Endpoints/RawEndpoint.cs index 8fb15dba..021b45cb 100644 --- a/src/BuslyCLI.Console/Infrastructure/Endpoints/RawEndpoint.cs +++ b/src/BuslyCLI.Console/Infrastructure/Endpoints/RawEndpoint.cs @@ -76,12 +76,6 @@ public Task OnError(ErrorContext errorContext, CancellationTo return Task.FromResult(ErrorHandleResult.Handled); } - public IncomingMessage TryReceiveMessageWithTimeout() - { - if (_receivedMessages.TryTake(out var incomingMessage, IncomingMessageTimeout)) return incomingMessage; - throw new TimeoutException($"The message did not arrive within {IncomingMessageTimeout.TotalSeconds} seconds."); - } - public IncomingMessage TryReceiveMessage() { if (_receivedMessages.TryTake(out var incomingMessage, IncomingMessageTimeout)) return incomingMessage; diff --git a/tests/BuslyCLI.Console.Tests/BuslyCLI.Console.Tests.csproj b/tests/BuslyCLI.Console.Tests/BuslyCLI.Console.Tests.csproj index ceb87ecc..afb6e04f 100644 --- a/tests/BuslyCLI.Console.Tests/BuslyCLI.Console.Tests.csproj +++ b/tests/BuslyCLI.Console.Tests/BuslyCLI.Console.Tests.csproj @@ -48,10 +48,6 @@ - - - - diff --git a/tests/BuslyCLI.Console.Tests/Commands/NsbTimeout/SendTimeoutTests.cs b/tests/BuslyCLI.Console.Tests/Commands/NsbTimeout/SendTimeoutTests.cs new file mode 100644 index 00000000..ee1bdf3d --- /dev/null +++ b/tests/BuslyCLI.Console.Tests/Commands/NsbTimeout/SendTimeoutTests.cs @@ -0,0 +1,99 @@ +using BuslyCLI.Console.Tests.TestHelpers; + +namespace BuslyCLI.Console.Tests.Commands.NsbTimeout; + +public class SendTimeoutTests : CommandTestBase +{ + [Test] + public void ShouldOutputAnErrorWhenTransportIsSqlServer() + { + // Arrange + var yamlFile = """ + --- + current-transport: local-sql-server + transports: + - name: local-sql-server + sql-server-transport-config: + connection-string: Server=localhost;Database=test; + """; + using var configFile = new TestableNServiceBusConfigurationFile(yamlFile); + + // Act + var result = Sut.Run( + "timeout", "send", + "--content-type", "application/json", + "--enclosed-message-type", "MessageContracts.Timeouts.OrderTimeout", + "--destination-endpoint", "Sales", + "--message-body", "{}", + "--delay-delivery-with", "00:00:01", + "--config", configFile.FilePath); + + // Assert + Assert.That(result.ExitCode, Is.EqualTo(1)); + Assert.That(result.Output, Does.Contain("SqlServerTransport")); + Assert.That(result.Output, Does.Contain("does not support sending timeouts")); + Assert.That(result.Output, Does.Contain("https://tragiccode.com/busly-cli/docs/cli-reference/timeout/send")); + } + + [Test] + public void ShouldOutputAnErrorWhenTransportIsPostgreSql() + { + // Arrange + var yamlFile = """ + --- + current-transport: local-postgresql + transports: + - name: local-postgresql + postgre-sql-transport-config: + connection-string: Host=localhost;Database=test; + """; + using var configFile = new TestableNServiceBusConfigurationFile(yamlFile); + + // Act + var result = Sut.Run( + "timeout", "send", + "--content-type", "application/json", + "--enclosed-message-type", "MessageContracts.Timeouts.OrderTimeout", + "--destination-endpoint", "Sales", + "--message-body", "{}", + "--delay-delivery-with", "00:00:01", + "--config", configFile.FilePath); + + // Assert + Assert.That(result.ExitCode, Is.EqualTo(1)); + Assert.That(result.Output, Does.Contain("PostgreSqlTransport")); + Assert.That(result.Output, Does.Contain("does not support sending timeouts")); + Assert.That(result.Output, Does.Contain("https://tragiccode.com/busly-cli/docs/cli-reference/timeout/send")); + } + + [Test] + public void ShouldOutputAnErrorWhenTransportIsAzureStorageQueues() + { + // Arrange + var yamlFile = """ + --- + current-transport: local-azure-storage-queues + transports: + - name: local-azure-storage-queues + azure-storage-queues-transport-config: + connection-string: UseDevelopmentStorage=true + """; + using var configFile = new TestableNServiceBusConfigurationFile(yamlFile); + + // Act + var result = Sut.Run( + "timeout", "send", + "--content-type", "application/json", + "--enclosed-message-type", "MessageContracts.Timeouts.OrderTimeout", + "--destination-endpoint", "Sales", + "--message-body", "{}", + "--delay-delivery-with", "00:00:01", + "--config", configFile.FilePath); + + // Assert + Assert.That(result.ExitCode, Is.EqualTo(1)); + Assert.That(result.Output, Does.Contain("AzureStorageQueuesTransport")); + Assert.That(result.Output, Does.Contain("does not support sending timeouts")); + Assert.That(result.Output, Does.Contain("https://tragiccode.com/busly-cli/docs/cli-reference/timeout/send")); + } +} \ No newline at end of file diff --git a/tests/BuslyCLI.Console.Tests/EndToEnd/AmazonSQS/PublishEventCommandAmazonSqsEndToEndTests.cs b/tests/BuslyCLI.Console.Tests/EndToEnd/AmazonSQS/PublishEventCommandAmazonSqsEndToEndTests.cs new file mode 100644 index 00000000..5551a1cf --- /dev/null +++ b/tests/BuslyCLI.Console.Tests/EndToEnd/AmazonSQS/PublishEventCommandAmazonSqsEndToEndTests.cs @@ -0,0 +1,41 @@ +using System.Text.Json; +using BuslyCLI.Console.Tests.TestHelpers; + +namespace BuslyCLI.Console.Tests.EndToEnd.AmazonSQS; + +[TestFixture] +public class PublishEventCommandAmazonSqsEndToEndTests : AmazonSqsEndToEndTestBase +{ + [Test] + public async Task ShouldPublishEvent() + { + // Arrange + await TestEndpoint.Subscribe("MessageContracts.Events.OrderCreated"); + var messageBody = new { OrderNumber = Guid.NewGuid() }; + + var json = JsonSerializer.Serialize(messageBody, _jsonObjectOptions); + var yamlFile = $""" + --- + current-transport: local-amazonsqs + transports: + - name: local-amazonsqs + amazonsqs-transport-config: + service-url: {Container.GetConnectionString()} + region-name: us-east-1 + """; + 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)); + AssertMessageReceived(TestEndpoint.TryReceiveMessage(), "MessageContracts.Events.OrderCreated", json); + } +} \ No newline at end of file diff --git a/tests/BuslyCLI.Console.Tests/EndToEnd/AmazonSQS/SendCommandAmazonSqsEndToEndTests.cs b/tests/BuslyCLI.Console.Tests/EndToEnd/AmazonSQS/SendCommandAmazonSqsEndToEndTests.cs index 24682e3b..13ffca86 100644 --- a/tests/BuslyCLI.Console.Tests/EndToEnd/AmazonSQS/SendCommandAmazonSqsEndToEndTests.cs +++ b/tests/BuslyCLI.Console.Tests/EndToEnd/AmazonSQS/SendCommandAmazonSqsEndToEndTests.cs @@ -39,36 +39,4 @@ public async Task ShouldSendCommand() AssertMessageReceived(TestEndpoint.TryReceiveMessage(), "MessageContracts.Commands.CreateOrder", json); } - [Test] - public async Task ShouldPublishEvent() - { - // Arrange - await TestEndpoint.Subscribe("MessageContracts.Events.OrderCreated"); - var messageBody = new { OrderNumber = Guid.NewGuid() }; - - var json = JsonSerializer.Serialize(messageBody, _jsonObjectOptions); - var yamlFile = $""" - --- - current-transport: local-amazonsqs - transports: - - name: local-amazonsqs - amazonsqs-transport-config: - service-url: {Container.GetConnectionString()} - region-name: us-east-1 - """; - 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)); - AssertMessageReceived(TestEndpoint.TryReceiveMessage(), "MessageContracts.Events.OrderCreated", json); - } } \ No newline at end of file diff --git a/tests/BuslyCLI.Console.Tests/EndToEnd/AmazonSQS/SendTimeoutCommandAmazonSqsEndToEndTests.cs b/tests/BuslyCLI.Console.Tests/EndToEnd/AmazonSQS/SendTimeoutCommandAmazonSqsEndToEndTests.cs new file mode 100644 index 00000000..e3115946 --- /dev/null +++ b/tests/BuslyCLI.Console.Tests/EndToEnd/AmazonSQS/SendTimeoutCommandAmazonSqsEndToEndTests.cs @@ -0,0 +1,76 @@ +using System.Text.Json; +using BuslyCLI.Console.Tests.TestHelpers; + +namespace BuslyCLI.Console.Tests.EndToEnd.AmazonSQS; + +[TestFixture] +public class SendTimeoutCommandAmazonSqsEndToEndTests : AmazonSqsEndToEndTestBase +{ + [Test] + public async Task ShouldSendTimeoutWithDelayDeliveryWith() + { + // Arrange + var messageBody = new { OrderNumber = Guid.NewGuid() }; + + var json = JsonSerializer.Serialize(messageBody, _jsonObjectOptions); + var yamlFile = $""" + --- + current-transport: local-amazonsqs + transports: + - name: local-amazonsqs + amazonsqs-transport-config: + service-url: {Container.GetConnectionString()} + region-name: us-east-1 + """; + using var configFile = new TestableNServiceBusConfigurationFile(yamlFile); + + // Act + var result = Sut.Run( + "timeout", + "send", + "--content-type", "application/json", + "--enclosed-message-type", "MessageContracts.Timeouts.OrderTimeout", + "--destination-endpoint", TestEndpoint.EndpointName, + "--message-body", json, + "--delay-delivery-with", "00:00:01", + "--config", configFile.FilePath); + + // Assert + Assert.That(result.ExitCode, Is.EqualTo(0)); + AssertMessageReceived(TestEndpoint.TryReceiveMessage(), "MessageContracts.Timeouts.OrderTimeout", json); + } + + [Test] + public async Task ShouldSendTimeoutWithDoNotDeliverBefore() + { + // Arrange + var messageBody = new { OrderNumber = Guid.NewGuid() }; + + var json = JsonSerializer.Serialize(messageBody, _jsonObjectOptions); + var yamlFile = $""" + --- + current-transport: local-amazonsqs + transports: + - name: local-amazonsqs + amazonsqs-transport-config: + service-url: {Container.GetConnectionString()} + region-name: us-east-1 + """; + using var configFile = new TestableNServiceBusConfigurationFile(yamlFile); + + // Act + var result = Sut.Run( + "timeout", + "send", + "--content-type", "application/json", + "--enclosed-message-type", "MessageContracts.Timeouts.OrderTimeout", + "--destination-endpoint", TestEndpoint.EndpointName, + "--message-body", json, + "--do-not-deliver-before", "2020-01-01T00:00:00Z", + "--config", configFile.FilePath); + + // Assert + Assert.That(result.ExitCode, Is.EqualTo(0)); + AssertMessageReceived(TestEndpoint.TryReceiveMessage(), "MessageContracts.Timeouts.OrderTimeout", json); + } +} \ No newline at end of file diff --git a/tests/BuslyCLI.Console.Tests/EndToEnd/AzureServiceBus/PublishEventCommandAzureServiceBusEndToEndTests.cs b/tests/BuslyCLI.Console.Tests/EndToEnd/AzureServiceBus/PublishEventCommandAzureServiceBusEndToEndTests.cs new file mode 100644 index 00000000..cfc10018 --- /dev/null +++ b/tests/BuslyCLI.Console.Tests/EndToEnd/AzureServiceBus/PublishEventCommandAzureServiceBusEndToEndTests.cs @@ -0,0 +1,41 @@ +using System.Text.Json; +using BuslyCLI.Console.Tests.TestHelpers; + +namespace BuslyCLI.Console.Tests.EndToEnd.AzureServiceBus; + +[TestFixture] +public class PublishEventCommandAzureServiceBusEndToEndTests : AzureServiceBusEndToEndTestBase +{ + [Test] + public async Task ShouldPublishEvent() + { + // Arrange + await TestEndpoint.Subscribe("MessageContracts.Events.OrderCreated"); + var messageBody = new { OrderNumber = Guid.NewGuid() }; + + var json = JsonSerializer.Serialize(messageBody, _jsonObjectOptions); + + var yamlFile = $""" + --- + current-transport: local-azure-service-bus + transports: + - name: local-azure-service-bus + azure-service-bus-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)); + AssertMessageReceived(TestEndpoint.TryReceiveMessage(), "MessageContracts.Events.OrderCreated", json); + } +} \ No newline at end of file diff --git a/tests/BuslyCLI.Console.Tests/EndToEnd/AzureServiceBus/SendCommandAzureServiceBusEndToEndTests.cs b/tests/BuslyCLI.Console.Tests/EndToEnd/AzureServiceBus/SendCommandAzureServiceBusEndToEndTests.cs index fe8b5f88..5cbbf836 100644 --- a/tests/BuslyCLI.Console.Tests/EndToEnd/AzureServiceBus/SendCommandAzureServiceBusEndToEndTests.cs +++ b/tests/BuslyCLI.Console.Tests/EndToEnd/AzureServiceBus/SendCommandAzureServiceBusEndToEndTests.cs @@ -38,36 +38,4 @@ public async Task ShouldSendCommand() AssertMessageReceived(TestEndpoint.TryReceiveMessage(), "MessageContracts.Commands.CreateOrder", json); } - [Test] - public async Task ShouldPublishEvent() - { - // Arrange - await TestEndpoint.Subscribe("MessageContracts.Events.OrderCreated"); - var messageBody = new { OrderNumber = Guid.NewGuid() }; - - var json = JsonSerializer.Serialize(messageBody, _jsonObjectOptions); - - var yamlFile = $""" - --- - current-transport: local-azure-service-bus - transports: - - name: local-azure-service-bus - azure-service-bus-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)); - AssertMessageReceived(TestEndpoint.TryReceiveMessage(), "MessageContracts.Events.OrderCreated", json); - } } \ No newline at end of file diff --git a/tests/BuslyCLI.Console.Tests/EndToEnd/AzureServiceBus/SendTimeoutCommandAzureServiceBusEndToEndTests.cs b/tests/BuslyCLI.Console.Tests/EndToEnd/AzureServiceBus/SendTimeoutCommandAzureServiceBusEndToEndTests.cs new file mode 100644 index 00000000..73ab61e3 --- /dev/null +++ b/tests/BuslyCLI.Console.Tests/EndToEnd/AzureServiceBus/SendTimeoutCommandAzureServiceBusEndToEndTests.cs @@ -0,0 +1,74 @@ +using System.Text.Json; +using BuslyCLI.Console.Tests.TestHelpers; + +namespace BuslyCLI.Console.Tests.EndToEnd.AzureServiceBus; + +[TestFixture] +public class SendTimeoutCommandAzureServiceBusEndToEndTests : AzureServiceBusEndToEndTestBase +{ + [Test] + public async Task ShouldSendTimeoutWithDelayDeliveryWith() + { + // Arrange + var messageBody = new { OrderNumber = Guid.NewGuid() }; + + var json = JsonSerializer.Serialize(messageBody, _jsonObjectOptions); + var yamlFile = $""" + --- + current-transport: local-azure-service-bus + transports: + - name: local-azure-service-bus + azure-service-bus-transport-config: + connection-string: {Container.GetConnectionString()} + """; + using var configFile = new TestableNServiceBusConfigurationFile(yamlFile); + + // Act + var result = Sut.Run( + "timeout", + "send", + "--content-type", "application/json", + "--enclosed-message-type", "MessageContracts.Timeouts.OrderTimeout", + "--destination-endpoint", TestEndpoint.EndpointName, + "--message-body", json, + "--delay-delivery-with", "00:00:01", + "--config", configFile.FilePath); + + // Assert + Assert.That(result.ExitCode, Is.EqualTo(0)); + AssertMessageReceived(TestEndpoint.TryReceiveMessage(), "MessageContracts.Timeouts.OrderTimeout", json); + } + + [Test] + public async Task ShouldSendTimeoutWithDoNotDeliverBefore() + { + // Arrange + var messageBody = new { OrderNumber = Guid.NewGuid() }; + + var json = JsonSerializer.Serialize(messageBody, _jsonObjectOptions); + var yamlFile = $""" + --- + current-transport: local-azure-service-bus + transports: + - name: local-azure-service-bus + azure-service-bus-transport-config: + connection-string: {Container.GetConnectionString()} + """; + using var configFile = new TestableNServiceBusConfigurationFile(yamlFile); + + // Act + var result = Sut.Run( + "timeout", + "send", + "--content-type", "application/json", + "--enclosed-message-type", "MessageContracts.Timeouts.OrderTimeout", + "--destination-endpoint", TestEndpoint.EndpointName, + "--message-body", json, + "--do-not-deliver-before", "2020-01-01T00:00:00Z", + "--config", configFile.FilePath); + + // Assert + Assert.That(result.ExitCode, Is.EqualTo(0)); + AssertMessageReceived(TestEndpoint.TryReceiveMessage(), "MessageContracts.Timeouts.OrderTimeout", json); + } +} \ No newline at end of file diff --git a/tests/BuslyCLI.Console.Tests/EndToEnd/AzureStorageQueues/PublishEventCommandAzureStorageQueuesEndToEndTests.cs b/tests/BuslyCLI.Console.Tests/EndToEnd/AzureStorageQueues/PublishEventCommandAzureStorageQueuesEndToEndTests.cs new file mode 100644 index 00000000..5d0367cf --- /dev/null +++ b/tests/BuslyCLI.Console.Tests/EndToEnd/AzureStorageQueues/PublishEventCommandAzureStorageQueuesEndToEndTests.cs @@ -0,0 +1,39 @@ +using System.Text.Json; +using BuslyCLI.Console.Tests.TestHelpers; + +namespace BuslyCLI.Console.Tests.EndToEnd.AzureStorageQueues; + +[TestFixture] +public class PublishEventCommandAzureStorageQueuesEndToEndTests : AzureStorageQueuesEndToEndTestBase +{ + [Test] + public async Task ShouldPublishEvent() + { + // Arrange + 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)); + AssertMessageReceived(TestEndpoint.TryReceiveMessage(), "MessageContracts.Events.OrderCreated", json); + } +} \ 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 index 2129fd27..2de88fd1 100644 --- a/tests/BuslyCLI.Console.Tests/EndToEnd/AzureStorageQueues/SendCommandAzureStorageQueuesEndToEndTests.cs +++ b/tests/BuslyCLI.Console.Tests/EndToEnd/AzureStorageQueues/SendCommandAzureStorageQueuesEndToEndTests.cs @@ -37,34 +37,4 @@ public async Task ShouldSendCommand() AssertMessageReceived(TestEndpoint.TryReceiveMessage(), "MessageContracts.Commands.CreateOrder", json); } - [Test] - public async Task ShouldPublishEvent() - { - // Arrange - 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)); - AssertMessageReceived(TestEndpoint.TryReceiveMessage(), "MessageContracts.Events.OrderCreated", json); - } } \ No newline at end of file diff --git a/tests/BuslyCLI.Console.Tests/EndToEnd/AzureStorageQueues/SendTimeoutCommandAzureStorageQueuesEndToEndTests.cs b/tests/BuslyCLI.Console.Tests/EndToEnd/AzureStorageQueues/SendTimeoutCommandAzureStorageQueuesEndToEndTests.cs new file mode 100644 index 00000000..e8efbbdb --- /dev/null +++ b/tests/BuslyCLI.Console.Tests/EndToEnd/AzureStorageQueues/SendTimeoutCommandAzureStorageQueuesEndToEndTests.cs @@ -0,0 +1,72 @@ +using System.Text.Json; +using BuslyCLI.Console.Tests.TestHelpers; + +namespace BuslyCLI.Console.Tests.EndToEnd.AzureStorageQueues; + +[TestFixture] +public class SendTimeoutCommandAzureStorageQueuesEndToEndTests : AzureStorageQueuesEndToEndTestBase +{ + [Test] + public async Task ShouldReturnErrorWhenSendingTimeoutWithDelayDeliveryWith() + { + // Arrange + 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( + "timeout", + "send", + "--content-type", "application/json", + "--enclosed-message-type", "MessageContracts.Timeouts.OrderTimeout", + "--destination-endpoint", TestEndpoint.EndpointName, + "--message-body", json, + "--delay-delivery-with", "00:00:01", + "--config", configFile.FilePath); + + // Assert + Assert.That(result.ExitCode, Is.EqualTo(1)); + Assert.That(TestEndpoint.TryReceiveMessage(), Is.Null); + } + + [Test] + public async Task ShouldReturnErrorWhenSendingTimeoutWithDoNotDeliverBefore() + { + // Arrange + 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( + "timeout", + "send", + "--content-type", "application/json", + "--enclosed-message-type", "MessageContracts.Timeouts.OrderTimeout", + "--destination-endpoint", TestEndpoint.EndpointName, + "--message-body", json, + "--do-not-deliver-before", "2020-01-01T00:00:00Z", + "--config", configFile.FilePath); + + // Assert + Assert.That(result.ExitCode, Is.EqualTo(1)); + Assert.That(TestEndpoint.TryReceiveMessage(), Is.Null); + } +} \ No newline at end of file diff --git a/tests/BuslyCLI.Console.Tests/EndToEnd/Learning/PublishEventCommandLearningEndToEndTests.cs b/tests/BuslyCLI.Console.Tests/EndToEnd/Learning/PublishEventCommandLearningEndToEndTests.cs new file mode 100644 index 00000000..59d2014e --- /dev/null +++ b/tests/BuslyCLI.Console.Tests/EndToEnd/Learning/PublishEventCommandLearningEndToEndTests.cs @@ -0,0 +1,39 @@ +using System.Text.Json; +using BuslyCLI.Console.Tests.TestHelpers; + +namespace BuslyCLI.Console.Tests.EndToEnd.Learning; + +[TestFixture] +public class PublishEventCommandLearningEndToEndTests : LearningEndToEndTestBase +{ + [Test] + public async Task ShouldPublishEvent() + { + // Arrange + await TestEndpoint.Subscribe("MessageContracts.Events.OrderCreated"); + var messageBody = new { OrderNumber = Guid.NewGuid() }; + var json = JsonSerializer.Serialize(messageBody, _jsonObjectOptions); + var yamlFile = $""" + --- + current-transport: local-learning + transports: + - name: local-learning + learning-transport-config: + storage-directory: ./.learningtransport + """; + 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)); + AssertMessageReceived(TestEndpoint.TryReceiveMessage(), "MessageContracts.Events.OrderCreated", json); + } +} \ No newline at end of file diff --git a/tests/BuslyCLI.Console.Tests/EndToEnd/Learning/SendCommandEndToEndLearningTests.cs b/tests/BuslyCLI.Console.Tests/EndToEnd/Learning/SendCommandEndToEndLearningTests.cs index 342bfd96..5f0e85da 100644 --- a/tests/BuslyCLI.Console.Tests/EndToEnd/Learning/SendCommandEndToEndLearningTests.cs +++ b/tests/BuslyCLI.Console.Tests/EndToEnd/Learning/SendCommandEndToEndLearningTests.cs @@ -37,34 +37,4 @@ public async Task ShouldSendCommand() AssertMessageReceived(TestEndpoint.TryReceiveMessage(), "MessageContracts.Commands.CreateOrder", json); } - [Test] - public async Task ShouldPublishEvent() - { - // Arrange - await TestEndpoint.Subscribe("MessageContracts.Events.OrderCreated"); - var messageBody = new { OrderNumber = Guid.NewGuid() }; - var json = JsonSerializer.Serialize(messageBody, _jsonObjectOptions); - var yamlFile = $""" - --- - current-transport: local-learning - transports: - - name: local-learning - learning-transport-config: - storage-directory: ./.learningtransport - """; - 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)); - AssertMessageReceived(TestEndpoint.TryReceiveMessage(), "MessageContracts.Events.OrderCreated", json); - } } \ No newline at end of file diff --git a/tests/BuslyCLI.Console.Tests/EndToEnd/Learning/SendTimeoutCommandLearningEndToEndTests.cs b/tests/BuslyCLI.Console.Tests/EndToEnd/Learning/SendTimeoutCommandLearningEndToEndTests.cs new file mode 100644 index 00000000..99e51068 --- /dev/null +++ b/tests/BuslyCLI.Console.Tests/EndToEnd/Learning/SendTimeoutCommandLearningEndToEndTests.cs @@ -0,0 +1,72 @@ +using System.Text.Json; +using BuslyCLI.Console.Tests.TestHelpers; + +namespace BuslyCLI.Console.Tests.EndToEnd.Learning; + +[TestFixture] +public class SendTimeoutCommandLearningEndToEndTests : LearningEndToEndTestBase +{ + [Test] + public async Task ShouldSendTimeoutWithDelayDeliveryWith() + { + // Arrange + var messageBody = new { OrderNumber = Guid.NewGuid() }; + var json = JsonSerializer.Serialize(messageBody, _jsonObjectOptions); + var yamlFile = $""" + --- + current-transport: local-learning + transports: + - name: local-learning + learning-transport-config: + storage-directory: ./.learningtransport + """; + using var configFile = new TestableNServiceBusConfigurationFile(yamlFile); + + // Act + var result = Sut.Run( + "timeout", + "send", + "--content-type", "application/json", + "--enclosed-message-type", "MessageContracts.Timeouts.OrderTimeout", + "--destination-endpoint", TestEndpoint.EndpointName, + "--message-body", json, + "--delay-delivery-with", "00:00:01", + "--config", configFile.FilePath); + + // Assert + Assert.That(result.ExitCode, Is.EqualTo(0)); + AssertMessageReceived(TestEndpoint.TryReceiveMessage(), "MessageContracts.Timeouts.OrderTimeout", json); + } + + [Test] + public async Task ShouldSendTimeoutWithDoNotDeliverBefore() + { + // Arrange + var messageBody = new { OrderNumber = Guid.NewGuid() }; + var json = JsonSerializer.Serialize(messageBody, _jsonObjectOptions); + var yamlFile = $""" + --- + current-transport: local-learning + transports: + - name: local-learning + learning-transport-config: + storage-directory: ./.learningtransport + """; + using var configFile = new TestableNServiceBusConfigurationFile(yamlFile); + + // Act + var result = Sut.Run( + "timeout", + "send", + "--content-type", "application/json", + "--enclosed-message-type", "MessageContracts.Timeouts.OrderTimeout", + "--destination-endpoint", TestEndpoint.EndpointName, + "--message-body", json, + "--do-not-deliver-before", "2020-01-01T00:00:00Z", + "--config", configFile.FilePath); + + // Assert + Assert.That(result.ExitCode, Is.EqualTo(0)); + AssertMessageReceived(TestEndpoint.TryReceiveMessage(), "MessageContracts.Timeouts.OrderTimeout", json); + } +} \ No newline at end of file diff --git a/tests/BuslyCLI.Console.Tests/EndToEnd/PostgreSql/PublishEventCommandPostgreSqlEndToEndTests.cs b/tests/BuslyCLI.Console.Tests/EndToEnd/PostgreSql/PublishEventCommandPostgreSqlEndToEndTests.cs new file mode 100644 index 00000000..d7a51cad --- /dev/null +++ b/tests/BuslyCLI.Console.Tests/EndToEnd/PostgreSql/PublishEventCommandPostgreSqlEndToEndTests.cs @@ -0,0 +1,39 @@ +using System.Text.Json; +using BuslyCLI.Console.Tests.TestHelpers; + +namespace BuslyCLI.Console.Tests.EndToEnd.PostgreSql; + +[TestFixture] +public class PublishEventCommandPostgreSqlEndToEndTests : PostgreSqlEndToEndTestBase +{ + [Test] + public async Task ShouldPublishEvent() + { + // Arrange + await TestEndpoint.Subscribe("MessageContracts.Events.OrderCreated"); + var messageBody = new { OrderNumber = Guid.NewGuid() }; + var json = JsonSerializer.Serialize(messageBody, _jsonObjectOptions); + var yamlFile = $""" + --- + current-transport: local-postgre-sql + transports: + - name: local-postgre-sql + postgre-sql-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)); + AssertMessageReceived(TestEndpoint.TryReceiveMessage(), "MessageContracts.Events.OrderCreated", json); + } +} \ No newline at end of file diff --git a/tests/BuslyCLI.Console.Tests/EndToEnd/PostgreSql/SendCommandPostgreSqlEndToEndTests.cs b/tests/BuslyCLI.Console.Tests/EndToEnd/PostgreSql/SendCommandPostgreSqlEndToEndTests.cs index a4475578..9c040cee 100644 --- a/tests/BuslyCLI.Console.Tests/EndToEnd/PostgreSql/SendCommandPostgreSqlEndToEndTests.cs +++ b/tests/BuslyCLI.Console.Tests/EndToEnd/PostgreSql/SendCommandPostgreSqlEndToEndTests.cs @@ -38,34 +38,4 @@ public async Task ShouldSendCommand() AssertMessageReceived(TestEndpoint.TryReceiveMessage(), "MessageContracts.Commands.CreateOrder", json); } - [Test] - public async Task ShouldPublishEvent() - { - // Arrange - await TestEndpoint.Subscribe("MessageContracts.Events.OrderCreated"); - var messageBody = new { OrderNumber = Guid.NewGuid() }; - var json = JsonSerializer.Serialize(messageBody, _jsonObjectOptions); - var yamlFile = $""" - --- - current-transport: local-postgre-sql - transports: - - name: local-postgre-sql - postgre-sql-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)); - AssertMessageReceived(TestEndpoint.TryReceiveMessage(), "MessageContracts.Events.OrderCreated", json); - } } \ No newline at end of file diff --git a/tests/BuslyCLI.Console.Tests/EndToEnd/PostgreSql/SendTimeoutCommandPostgreSqlEndToEndTests.cs b/tests/BuslyCLI.Console.Tests/EndToEnd/PostgreSql/SendTimeoutCommandPostgreSqlEndToEndTests.cs new file mode 100644 index 00000000..5c72c48b --- /dev/null +++ b/tests/BuslyCLI.Console.Tests/EndToEnd/PostgreSql/SendTimeoutCommandPostgreSqlEndToEndTests.cs @@ -0,0 +1,72 @@ +using System.Text.Json; +using BuslyCLI.Console.Tests.TestHelpers; + +namespace BuslyCLI.Console.Tests.EndToEnd.PostgreSql; + +[TestFixture] +public class SendTimeoutCommandPostgreSqlEndToEndTests : PostgreSqlEndToEndTestBase +{ + [Test] + public async Task ShouldReturnErrorWhenSendingTimeoutWithDelayDeliveryWith() + { + // Arrange + var messageBody = new { OrderNumber = Guid.NewGuid() }; + var json = JsonSerializer.Serialize(messageBody, _jsonObjectOptions); + var yamlFile = $""" + --- + current-transport: local-postgre-sql + transports: + - name: local-postgre-sql + postgre-sql-transport-config: + connection-string: {Container.GetConnectionString()} + """; + using var configFile = new TestableNServiceBusConfigurationFile(yamlFile); + + // Act + var result = Sut.Run( + "timeout", + "send", + "--content-type", "application/json", + "--enclosed-message-type", "MessageContracts.Timeouts.OrderTimeout", + "--destination-endpoint", TestEndpoint.EndpointName, + "--message-body", json, + "--delay-delivery-with", "00:00:01", + "--config", configFile.FilePath); + + // Assert + Assert.That(result.ExitCode, Is.EqualTo(1)); + Assert.That(TestEndpoint.TryReceiveMessage(), Is.Null); + } + + [Test] + public async Task ShouldReturnErrorWhenSendingTimeoutWithDoNotDeliverBefore() + { + // Arrange + var messageBody = new { OrderNumber = Guid.NewGuid() }; + var json = JsonSerializer.Serialize(messageBody, _jsonObjectOptions); + var yamlFile = $""" + --- + current-transport: local-postgre-sql + transports: + - name: local-postgre-sql + postgre-sql-transport-config: + connection-string: {Container.GetConnectionString()} + """; + using var configFile = new TestableNServiceBusConfigurationFile(yamlFile); + + // Act + var result = Sut.Run( + "timeout", + "send", + "--content-type", "application/json", + "--enclosed-message-type", "MessageContracts.Timeouts.OrderTimeout", + "--destination-endpoint", TestEndpoint.EndpointName, + "--message-body", json, + "--do-not-deliver-before", "2020-01-01T00:00:00Z", + "--config", configFile.FilePath); + + // Assert + Assert.That(result.ExitCode, Is.EqualTo(1)); + Assert.That(TestEndpoint.TryReceiveMessage(), Is.Null); + } +} \ No newline at end of file diff --git a/tests/BuslyCLI.Console.Tests/EndToEnd/RabbitMQ/PublishEventCommandRabbitMqEndToEndTests.cs b/tests/BuslyCLI.Console.Tests/EndToEnd/RabbitMQ/PublishEventCommandRabbitMqEndToEndTests.cs new file mode 100644 index 00000000..741fa885 --- /dev/null +++ b/tests/BuslyCLI.Console.Tests/EndToEnd/RabbitMQ/PublishEventCommandRabbitMqEndToEndTests.cs @@ -0,0 +1,41 @@ +using System.Text.Json; +using BuslyCLI.Console.Tests.TestHelpers; + +namespace BuslyCLI.Console.Tests.EndToEnd.RabbitMQ; + +[TestFixture] +public class PublishEventCommandRabbitMqEndToEndTests : RabbitMqEndToEndTestBase +{ + [Test] + public async Task ShouldPublishEvent() + { + // Arrange + await TestEndpoint.Subscribe("MessageContracts.Events.OrderCreated"); + var messageBody = new { OrderNumber = Guid.NewGuid() }; + var json = JsonSerializer.Serialize(messageBody, _jsonObjectOptions); + var yamlFile = $""" + --- + current-transport: local-rabbitmq + transports: + - name: local-rabbitmq + rabbitmq-transport-config: + amqp-connection-string: {Container.GetConnectionString()} + management-api: + url: http://{Container.Hostname}:{Container.GetMappedPublicPort(15672)} + """; + 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)); + AssertMessageReceived(TestEndpoint.TryReceiveMessage(), "MessageContracts.Events.OrderCreated", json); + } +} \ No newline at end of file diff --git a/tests/BuslyCLI.Console.Tests/EndToEnd/RabbitMQ/SendCommandRabbitMqEndToEndTests.cs b/tests/BuslyCLI.Console.Tests/EndToEnd/RabbitMQ/SendCommandRabbitMqEndToEndTests.cs index c80bc8e8..cabd4249 100644 --- a/tests/BuslyCLI.Console.Tests/EndToEnd/RabbitMQ/SendCommandRabbitMqEndToEndTests.cs +++ b/tests/BuslyCLI.Console.Tests/EndToEnd/RabbitMQ/SendCommandRabbitMqEndToEndTests.cs @@ -39,36 +39,4 @@ public async Task ShouldSendCommand() AssertMessageReceived(TestEndpoint.TryReceiveMessage(), "MessageContracts.Commands.CreateOrder", json); } - [Test] - public async Task ShouldPublishEvent() - { - // Arrange - await TestEndpoint.Subscribe("MessageContracts.Events.OrderCreated"); - var messageBody = new { OrderNumber = Guid.NewGuid() }; - var json = JsonSerializer.Serialize(messageBody, _jsonObjectOptions); - var yamlFile = $""" - --- - current-transport: local-rabbitmq - transports: - - name: local-rabbitmq - rabbitmq-transport-config: - amqp-connection-string: {Container.GetConnectionString()} - management-api: - url: http://{Container.Hostname}:{Container.GetMappedPublicPort(15672)} - """; - 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)); - AssertMessageReceived(TestEndpoint.TryReceiveMessage(), "MessageContracts.Events.OrderCreated", json); - } } \ No newline at end of file diff --git a/tests/BuslyCLI.Console.Tests/EndToEnd/RabbitMQ/SendTimeoutCommandRabbitMqEndToEndTests.cs b/tests/BuslyCLI.Console.Tests/EndToEnd/RabbitMQ/SendTimeoutCommandRabbitMqEndToEndTests.cs new file mode 100644 index 00000000..bd585d8a --- /dev/null +++ b/tests/BuslyCLI.Console.Tests/EndToEnd/RabbitMQ/SendTimeoutCommandRabbitMqEndToEndTests.cs @@ -0,0 +1,76 @@ +using System.Text.Json; +using BuslyCLI.Console.Tests.TestHelpers; + +namespace BuslyCLI.Console.Tests.EndToEnd.RabbitMQ; + +[TestFixture] +public class SendTimeoutCommandRabbitMqEndToEndTests : RabbitMqEndToEndTestBase +{ + [Test] + public async Task ShouldSendTimeoutWithDelayDeliveryWith() + { + // Arrange + var messageBody = new { OrderNumber = Guid.NewGuid() }; + var json = JsonSerializer.Serialize(messageBody, _jsonObjectOptions); + var yamlFile = $""" + --- + current-transport: local-rabbitmq + transports: + - name: local-rabbitmq + rabbitmq-transport-config: + amqp-connection-string: {Container.GetConnectionString()} + management-api: + url: http://{Container.Hostname}:{Container.GetMappedPublicPort(15672)} + """; + using var configFile = new TestableNServiceBusConfigurationFile(yamlFile); + + // Act + var result = Sut.Run( + "timeout", + "send", + "--content-type", "application/json", + "--enclosed-message-type", "MessageContracts.Timeouts.OrderTimeout", + "--destination-endpoint", TestEndpoint.EndpointName, + "--message-body", json, + "--delay-delivery-with", "00:00:01", + "--config", configFile.FilePath); + + // Assert + Assert.That(result.ExitCode, Is.EqualTo(0)); + AssertMessageReceived(TestEndpoint.TryReceiveMessage(), "MessageContracts.Timeouts.OrderTimeout", json); + } + + [Test] + public async Task ShouldSendTimeoutWithDoNotDeliverBefore() + { + // Arrange + var messageBody = new { OrderNumber = Guid.NewGuid() }; + var json = JsonSerializer.Serialize(messageBody, _jsonObjectOptions); + var yamlFile = $""" + --- + current-transport: local-rabbitmq + transports: + - name: local-rabbitmq + rabbitmq-transport-config: + amqp-connection-string: {Container.GetConnectionString()} + management-api: + url: http://{Container.Hostname}:{Container.GetMappedPublicPort(15672)} + """; + using var configFile = new TestableNServiceBusConfigurationFile(yamlFile); + + // Act + var result = Sut.Run( + "timeout", + "send", + "--content-type", "application/json", + "--enclosed-message-type", "MessageContracts.Timeouts.OrderTimeout", + "--destination-endpoint", TestEndpoint.EndpointName, + "--message-body", json, + "--do-not-deliver-before", "2020-01-01T00:00:00Z", + "--config", configFile.FilePath); + + // Assert + Assert.That(result.ExitCode, Is.EqualTo(0)); + AssertMessageReceived(TestEndpoint.TryReceiveMessage(), "MessageContracts.Timeouts.OrderTimeout", json); + } +} \ No newline at end of file diff --git a/tests/BuslyCLI.Console.Tests/EndToEnd/SqlServer/PublishEventCommandSqlServerEndToEndTests.cs b/tests/BuslyCLI.Console.Tests/EndToEnd/SqlServer/PublishEventCommandSqlServerEndToEndTests.cs new file mode 100644 index 00000000..0cd6c1f7 --- /dev/null +++ b/tests/BuslyCLI.Console.Tests/EndToEnd/SqlServer/PublishEventCommandSqlServerEndToEndTests.cs @@ -0,0 +1,39 @@ +using System.Text.Json; +using BuslyCLI.Console.Tests.TestHelpers; + +namespace BuslyCLI.Console.Tests.EndToEnd.SqlServer; + +[TestFixture] +public class PublishEventCommandSqlServerEndToEndTests : SqlServerEndToEndTestBase +{ + [Test] + public async Task ShouldPublishEvent() + { + // Arrange + await TestEndpoint.Subscribe("MessageContracts.Events.OrderCreated"); + var messageBody = new { OrderNumber = Guid.NewGuid() }; + var json = JsonSerializer.Serialize(messageBody, _jsonObjectOptions); + var yamlFile = $""" + --- + current-transport: local-sql-server + transports: + - name: local-sql-server + sql-server-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)); + AssertMessageReceived(TestEndpoint.TryReceiveMessage(), "MessageContracts.Events.OrderCreated", json); + } +} \ No newline at end of file diff --git a/tests/BuslyCLI.Console.Tests/EndToEnd/SqlServer/SendCommandSqlServerEndToEndTests.cs b/tests/BuslyCLI.Console.Tests/EndToEnd/SqlServer/SendCommandSqlServerEndToEndTests.cs index d9dd784e..fcdb6fec 100644 --- a/tests/BuslyCLI.Console.Tests/EndToEnd/SqlServer/SendCommandSqlServerEndToEndTests.cs +++ b/tests/BuslyCLI.Console.Tests/EndToEnd/SqlServer/SendCommandSqlServerEndToEndTests.cs @@ -37,34 +37,4 @@ public async Task ShouldSendCommand() AssertMessageReceived(TestEndpoint.TryReceiveMessage(), "MessageContracts.Commands.CreateOrder", json); } - [Test] - public async Task ShouldPublishEvent() - { - // Arrange - await TestEndpoint.Subscribe("MessageContracts.Events.OrderCreated"); - var messageBody = new { OrderNumber = Guid.NewGuid() }; - var json = JsonSerializer.Serialize(messageBody, _jsonObjectOptions); - var yamlFile = $""" - --- - current-transport: local-sql-server - transports: - - name: local-sql-server - sql-server-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)); - AssertMessageReceived(TestEndpoint.TryReceiveMessage(), "MessageContracts.Events.OrderCreated", json); - } } \ No newline at end of file diff --git a/tests/BuslyCLI.Console.Tests/EndToEnd/SqlServer/SendTimeoutCommandSqlServerEndToEndTests.cs b/tests/BuslyCLI.Console.Tests/EndToEnd/SqlServer/SendTimeoutCommandSqlServerEndToEndTests.cs new file mode 100644 index 00000000..4026a77b --- /dev/null +++ b/tests/BuslyCLI.Console.Tests/EndToEnd/SqlServer/SendTimeoutCommandSqlServerEndToEndTests.cs @@ -0,0 +1,72 @@ +using System.Text.Json; +using BuslyCLI.Console.Tests.TestHelpers; + +namespace BuslyCLI.Console.Tests.EndToEnd.SqlServer; + +[TestFixture] +public class SendTimeoutCommandSqlServerEndToEndTests : SqlServerEndToEndTestBase +{ + [Test] + public async Task ShouldReturnErrorWhenSendingTimeoutWithDelayDeliveryWith() + { + // Arrange + var messageBody = new { OrderNumber = Guid.NewGuid() }; + var json = JsonSerializer.Serialize(messageBody, _jsonObjectOptions); + var yamlFile = $""" + --- + current-transport: local-sql-server + transports: + - name: local-sql-server + sql-server-transport-config: + connection-string: {Container.GetConnectionString()} + """; + using var configFile = new TestableNServiceBusConfigurationFile(yamlFile); + + // Act + var result = Sut.Run( + "timeout", + "send", + "--content-type", "application/json", + "--enclosed-message-type", "MessageContracts.Timeouts.OrderTimeout", + "--destination-endpoint", TestEndpoint.EndpointName, + "--message-body", json, + "--delay-delivery-with", "00:00:01", + "--config", configFile.FilePath); + + // Assert + Assert.That(result.ExitCode, Is.EqualTo(1)); + Assert.That(TestEndpoint.TryReceiveMessage(), Is.Null); + } + + [Test] + public async Task ShouldReturnErrorWhenSendingTimeoutWithDoNotDeliverBefore() + { + // Arrange + var messageBody = new { OrderNumber = Guid.NewGuid() }; + var json = JsonSerializer.Serialize(messageBody, _jsonObjectOptions); + var yamlFile = $""" + --- + current-transport: local-sql-server + transports: + - name: local-sql-server + sql-server-transport-config: + connection-string: {Container.GetConnectionString()} + """; + using var configFile = new TestableNServiceBusConfigurationFile(yamlFile); + + // Act + var result = Sut.Run( + "timeout", + "send", + "--content-type", "application/json", + "--enclosed-message-type", "MessageContracts.Timeouts.OrderTimeout", + "--destination-endpoint", TestEndpoint.EndpointName, + "--message-body", json, + "--do-not-deliver-before", "2020-01-01T00:00:00Z", + "--config", configFile.FilePath); + + // Assert + Assert.That(result.ExitCode, Is.EqualTo(1)); + Assert.That(TestEndpoint.TryReceiveMessage(), Is.Null); + } +} \ No newline at end of file diff --git a/website/docs/cli-reference/cli-reference.md b/website/docs/cli-reference/cli-reference.md index 4da41839..1aba0307 100644 --- a/website/docs/cli-reference/cli-reference.md +++ b/website/docs/cli-reference/cli-reference.md @@ -8,6 +8,7 @@ This document contains information about all commands and settings available in - `busly transport set` - `busly command send` - `busly event publish` +- `busly timeout send` ## Global Options diff --git a/website/docs/cli-reference/timeout/index.mdx b/website/docs/cli-reference/timeout/index.mdx new file mode 100644 index 00000000..7fc20e95 --- /dev/null +++ b/website/docs/cli-reference/timeout/index.mdx @@ -0,0 +1,7 @@ +import DocCardList from "@theme/DocCardList"; + +# busly timeout + +Use `busly timeout` to send timeouts to NServiceBus endpoints. + + \ No newline at end of file diff --git a/website/docs/cli-reference/timeout/send.md b/website/docs/cli-reference/timeout/send.md new file mode 100644 index 00000000..7c46bb68 --- /dev/null +++ b/website/docs/cli-reference/timeout/send.md @@ -0,0 +1,40 @@ +# busly timeout send + +Send a timeout message to an endpoint. + +## Usage + +``` +busly timeout send +``` + +## Options + +| Option | Description | +| ------------------------------- | ----------------------------------------------------------------------------------------------------------------- | +| `-c`, `--content-type` | The fully qualified .NET type name of the enclosed message (ex: Ordering.Commands.CreateOrder ) | +| `-e`, `--enclosed-message-type` | The type of serialization used for the message | +| `-m`, `--message-body` | The content of the message body | +| `-d`, `--destination-endpoint` | The destination endpoint to send a message to | +| `--do-not-deliver-before` | Allows specifying a date before which the delivery should not occur, using ISO-8601 format (YYYY-MM-DDTHH:mm:ssZ) | +| `--delay-delivery-with` | Specifies the delay before the timeout is delivered, using a TimeSpan format | + +## Transport Support + +Not all transports support sending timeouts. Transports that delegate delayed delivery to the broker work fine. Transports that rely on an in-process poller to forward deferred messages are not compatible with the CLI's fire-and-forget execution model — the process exits before the poller has a chance to dispatch the message. + +| Transport | Supported | Reason | +| ---------------------- | --------- | ------------------------------------------------------------------- | +| Learning | ✅ | Broker-side delayed delivery | +| RabbitMQ | ✅ | Broker-side delayed delivery via TTL | +| Azure Service Bus | ✅ | Broker-side scheduled messages | +| Amazon SQS | ✅ | Native SQS message delay | +| Azure Storage Queues | ❌ | Requires an in-process poller; exits before messages become due | +| PostgreSQL | ❌ | Requires an in-process poller; send-only endpoints not supported | +| SQL Server | ❌ | Requires an in-process poller; send-only endpoints not supported | + +## Examples + +``` + +``` \ No newline at end of file diff --git a/website/docs/introduction/quick-start.mdx b/website/docs/introduction/quick-start.mdx index 82791026..05ce6a20 100644 --- a/website/docs/introduction/quick-start.mdx +++ b/website/docs/introduction/quick-start.mdx @@ -222,10 +222,70 @@ docker run --rm ` --network host ` -v "$HOME/.busly-cli/config.yaml:/app/config.yaml" ` tragiccode/busly-cli ` - event publish ` + --message-body ('{"OrderNumber":"3f2d6c8a-b7a2-4c3f-9c3e-12ab45ef6789"}'-replace '"', '\"') ` + --config ./config.yaml +``` + + + + +## Send a Timeout + + + + +```bash +busly timeout send \ + --content-type 'text/json' \ + --enclosed-message-type "Messages.Timeouts.OrderTimeout" \ + --destination-endpoint "BuslyCLI.DemoEndpoint" \ + --message-body '{"OrderNumber":"3f2d6c8a-b7a2-4c3f-9c3e-12ab45ef6789"}' \ + --delay-delivery-with "00:00:05" +``` + + + + +```bash +busly timeout send ` + --content-type 'text/json' ` + --enclosed-message-type "Messages.Timeouts.OrderTimeout" ` + --destination-endpoint "BuslyCLI.DemoEndpoint" ` + --message-body ('{"OrderNumber":"3f2d6c8a-b7a2-4c3f-9c3e-12ab45ef6789"}'-replace '"', '\"') ` + --delay-delivery-with "00:00:05" +``` + + + + +```bash +docker run --rm \ + --network host \ + -v "$HOME/.busly-cli/config.yaml:/app/config.yaml" \ + tragiccode/busly-cli \ + timeout send \ + --content-type "text/json" \ + --enclosed-message-type "Messages.Timeouts.OrderTimeout" \ + --destination-endpoint "BuslyCLI.DemoEndpoint" \ + --message-body '{"OrderNumber":"3f2d6c8a-b7a2-4c3f-9c3e-12ab45ef6789"}' \ + --delay-delivery-with "00:00:05" \ + --config ./config.yaml +``` + + + + +```bash +docker run --rm ` + --network host ` + -v "$HOME/.busly-cli/config.yaml:/app/config.yaml" ` + tragiccode/busly-cli ` + timeout send ` --content-type "text/json" ` - --enclosed-message-type "Messages.Events.OrderPlaced" ` + --enclosed-message-type "Messages.Timeouts.OrderTimeout" ` + --destination-endpoint "BuslyCLI.DemoEndpoint" ` --message-body ('{"OrderNumber":"3f2d6c8a-b7a2-4c3f-9c3e-12ab45ef6789"}'-replace '"', '\"') ` + --delay-delivery-with "00:00:05" ` --config ./config.yaml ``` diff --git a/website/docs/transports/azure-storage-queues.md b/website/docs/transports/azure-storage-queues.md index d0bc085e..90c4aea3 100644 --- a/website/docs/transports/azure-storage-queues.md +++ b/website/docs/transports/azure-storage-queues.md @@ -38,3 +38,9 @@ Examples: ```yaml connection-string: UseDevelopmentStorage=true ``` + +## Limitations + +### Timeout Send Not Supported + +The `timeout send` command is not supported with the Azure Storage Queues transport. Native delayed delivery is disabled in busly's transport configuration because it requires an in-process background poller (`NativeDelayedDeliveryProcessor`) to forward due messages from an Azure Table to the destination queue. Because busly exits immediately after dispatching a message, the poller is stopped before it can forward the deferred message. Enabling native delayed delivery for send-only endpoints also requires a dedicated poison queue and would still not resolve the fundamental timing incompatibility. diff --git a/website/docs/transports/postgre-sql.md b/website/docs/transports/postgre-sql.md index 8f723cfa..eb0ba8df 100644 --- a/website/docs/transports/postgre-sql.md +++ b/website/docs/transports/postgre-sql.md @@ -38,3 +38,9 @@ Examples: ```yaml connection-string: Data Source=(local);Initial Catalog=Ordering;Integrated Security=SSPI;Application Name=Busly-CLI;TrustServerCertificate=true ``` + +## Limitations + +### Timeout Send Not Supported + +The `timeout send` command is not supported with the PostgreSQL transport. PostgreSQL implements delayed delivery using a database table polled by an in-process background worker. Because busly exits immediately after dispatching a message, the poller is stopped before it can forward the deferred message to the destination queue. Additionally, the transport explicitly blocks delayed delivery from send-only endpoints. diff --git a/website/docs/transports/sql-server.md b/website/docs/transports/sql-server.md index c6101e37..8e462c4c 100644 --- a/website/docs/transports/sql-server.md +++ b/website/docs/transports/sql-server.md @@ -38,3 +38,9 @@ Examples: ```yaml connection-string: Data Source=(local);Initial Catalog=Ordering;Integrated Security=SSPI;Application Name=Busly-CLI;TrustServerCertificate=true ``` + +## Limitations + +### Timeout Send Not Supported + +The `timeout send` command is not supported with the SQL Server transport. SQL Server implements delayed delivery using a database table polled by an in-process background worker. Because busly exits immediately after dispatching a message, the poller is stopped before it can forward the deferred message to the destination queue. Additionally, the transport explicitly blocks delayed delivery from send-only endpoints.