From 861709fa9fb52a2d63100e6dd3e86fd75f06d484 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 22 Sep 2026 14:47:18 +0000 Subject: [PATCH 1/3] Initial plan From 85b8f45d3da6b158fd6aa517ccc6f5da2fd52dd0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 22 Sep 2026 14:50:32 +0000 Subject: [PATCH 2/3] Refactor audit tests to use spy endpoints Co-authored-by: afprtclr <208591451+afprtclr@users.noreply.github.com> --- .../When_a_new_endpoint_is_detected.cs | 40 +++++++++++----- ...importing_a_message_resolved_by_a_retry.cs | 48 +++++++++++++++---- ...terceptMessagesDestinedToServiceControl.cs | 24 ---------- .../InterceptedMessagesScenarioContext.cs | 11 ----- 4 files changed, 67 insertions(+), 56 deletions(-) delete mode 100644 src/ServiceControl.Audit.AcceptanceTests/TestSupport/InterceptMessagesDestinedToServiceControl.cs delete mode 100644 src/ServiceControl.Audit.AcceptanceTests/TestSupport/InterceptedMessagesScenarioContext.cs diff --git a/src/ServiceControl.Audit.AcceptanceTests/Monitoring/When_a_new_endpoint_is_detected.cs b/src/ServiceControl.Audit.AcceptanceTests/Monitoring/When_a_new_endpoint_is_detected.cs index 1b00fe60b5..98d9471627 100644 --- a/src/ServiceControl.Audit.AcceptanceTests/Monitoring/When_a_new_endpoint_is_detected.cs +++ b/src/ServiceControl.Audit.AcceptanceTests/Monitoring/When_a_new_endpoint_is_detected.cs @@ -1,32 +1,50 @@ namespace ServiceControl.Audit.AcceptanceTests.Monitoring { + using System.Collections.Concurrent; using System.Linq; using System.Threading.Tasks; using AcceptanceTesting.EndpointTemplates; + using Contracts.EndpointControl; using NServiceBus; using NServiceBus.AcceptanceTesting; + using NServiceBus.AcceptanceTesting.Customization; using NUnit.Framework; - using TestSupport; - using Conventions = NServiceBus.AcceptanceTesting.Customization.Conventions; class When_a_new_endpoint_is_detected : AcceptanceTest { [Test] public async Task Should_notify_service_control() { - CustomConfiguration = endpointConfiguration => - { - endpointConfiguration.Pipeline.Register(typeof(InterceptMessagesDestinedToServiceControl), - "Intercepts messages destined to ServiceControl"); - }; + SetSettings = settings => settings.ServiceControlQueueAddress = Conventions.EndpointNamingConvention(typeof(ServiceControlSpy)); - var context = await Define() + var context = await Define() + .WithEndpoint() .WithEndpoint(b => b.When((bus, c) => bus.SendLocal(new MyMessage()))) - .Done(c => c.SentRegisterEndpointCommands.Any()) + .Done(c => c.SentRegisterEndpointCommands.Any(command => command.Endpoint.Name == Conventions.EndpointNamingConvention(typeof(Receiver)))) .Run(); - var command = context.SentRegisterEndpointCommands.Single(); - Assert.That(command.Endpoint.Name, Is.EqualTo(Conventions.EndpointNamingConvention(typeof(Receiver)))); + Assert.That(context.SentRegisterEndpointCommands.Select(command => command.Endpoint.Name), + Does.Contain(Conventions.EndpointNamingConvention(typeof(Receiver)))); + } + + public class Context : ScenarioContext + { + public ConcurrentBag SentRegisterEndpointCommands { get; } = []; + } + + public class ServiceControlSpy : EndpointConfigurationBuilder + { + public ServiceControlSpy() => EndpointSetup(); + + [Handler] + public class RegisterNewEndpointHandler(Context testContext) : IHandleMessages + { + public Task Handle(RegisterNewEndpoint message, IMessageHandlerContext context) + { + testContext.SentRegisterEndpointCommands.Add(message); + return Task.CompletedTask; + } + } } public class Receiver : EndpointConfigurationBuilder diff --git a/src/ServiceControl.Audit.AcceptanceTests/Recoverability/When_importing_a_message_resolved_by_a_retry.cs b/src/ServiceControl.Audit.AcceptanceTests/Recoverability/When_importing_a_message_resolved_by_a_retry.cs index b839836e8c..711597be5f 100644 --- a/src/ServiceControl.Audit.AcceptanceTests/Recoverability/When_importing_a_message_resolved_by_a_retry.cs +++ b/src/ServiceControl.Audit.AcceptanceTests/Recoverability/When_importing_a_message_resolved_by_a_retry.cs @@ -1,31 +1,30 @@ -namespace ServiceControl.Audit.AcceptanceTests.Recoverability +namespace ServiceControl.Audit.AcceptanceTests.Recoverability { using System; + using System.Collections.Concurrent; using System.Threading.Tasks; using AcceptanceTesting; using AcceptanceTesting.EndpointTemplates; using Audit.Auditing.MessagesView; using Audit.Monitoring; + using Contracts.EndpointControl; using NServiceBus; using NServiceBus.AcceptanceTesting; + using NServiceBus.AcceptanceTesting.Customization; using NUnit.Framework; - using TestSupport; class When_importing_a_message_resolved_by_a_retry : AcceptanceTest { [Test] public async Task Should_set_status_to_resolved() { - CustomConfiguration = endpointConfiguration => - { - endpointConfiguration.Pipeline.Register(typeof(InterceptMessagesDestinedToServiceControl), - "Intercepts messages destined to ServiceControl"); - }; + SetSettings = settings => settings.ServiceControlQueueAddress = Conventions.EndpointNamingConvention(typeof(ServiceControlSpy)); MessagesView auditedMessage = null; var messageId = Guid.NewGuid().ToString(); - await Define() + var context = await Define() + .WithEndpoint() .WithEndpoint(b => b.When(s => { var options = new SendOptions(); @@ -37,15 +36,44 @@ await Define() })) .Done(async c => { + if (!c.SentRegisterEndpointCommands.Any(command => command.Endpoint.Name == Conventions.EndpointNamingConvention(typeof(Receiver)))) + { + return false; + } + var result = await this.TryGetSingle("/api/messages", m => m.MessageId == messageId); auditedMessage = result; - return result; + return result && c.SentRegisterEndpointCommands.Any(command => command.Endpoint.Name == Conventions.EndpointNamingConvention(typeof(Receiver))); }) .Run(); - Assert.That(auditedMessage.Status, Is.EqualTo(MessageStatus.ResolvedSuccessfully)); + using (Assert.EnterMultipleScope()) + { + Assert.That(auditedMessage.Status, Is.EqualTo(MessageStatus.ResolvedSuccessfully)); + Assert.That(context.SentRegisterEndpointCommands, Has.Some.Matches(command => command.Endpoint.Name == Conventions.EndpointNamingConvention(typeof(Receiver)))); + } + } + + public class Context : ScenarioContext + { + public ConcurrentBag SentRegisterEndpointCommands { get; } = []; + } + + public class ServiceControlSpy : EndpointConfigurationBuilder + { + public ServiceControlSpy() => EndpointSetup(); + + [Handler] + public class RegisterNewEndpointHandler(Context testContext) : IHandleMessages + { + public Task Handle(RegisterNewEndpoint message, IMessageHandlerContext context) + { + testContext.SentRegisterEndpointCommands.Add(message); + return Task.CompletedTask; + } + } } public class Receiver : EndpointConfigurationBuilder diff --git a/src/ServiceControl.Audit.AcceptanceTests/TestSupport/InterceptMessagesDestinedToServiceControl.cs b/src/ServiceControl.Audit.AcceptanceTests/TestSupport/InterceptMessagesDestinedToServiceControl.cs deleted file mode 100644 index 9d605d44ca..0000000000 --- a/src/ServiceControl.Audit.AcceptanceTests/TestSupport/InterceptMessagesDestinedToServiceControl.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace ServiceControl.Audit.AcceptanceTests.TestSupport -{ - using System; - using System.Threading.Tasks; - using Contracts.EndpointControl; - using NServiceBus.AcceptanceTesting; - using NServiceBus.Pipeline; - - class InterceptMessagesDestinedToServiceControl(ScenarioContext scenarioContext) : Behavior - { - public override Task Invoke(IOutgoingLogicalMessageContext context, Func next) - { - var interceptingContext = scenarioContext as InterceptedMessagesScenarioContext; - switch (context.Message.Instance) - { - case RegisterNewEndpoint newEndpoint: - interceptingContext?.SentRegisterEndpointCommands.Add(newEndpoint); - return Task.CompletedTask; - default: - return next(); - } - } - } -} \ No newline at end of file diff --git a/src/ServiceControl.Audit.AcceptanceTests/TestSupport/InterceptedMessagesScenarioContext.cs b/src/ServiceControl.Audit.AcceptanceTests/TestSupport/InterceptedMessagesScenarioContext.cs deleted file mode 100644 index ea94e537d9..0000000000 --- a/src/ServiceControl.Audit.AcceptanceTests/TestSupport/InterceptedMessagesScenarioContext.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace ServiceControl.Audit.AcceptanceTests.TestSupport -{ - using System.Collections.Concurrent; - using Contracts.EndpointControl; - using NServiceBus.AcceptanceTesting; - - public class InterceptedMessagesScenarioContext : ScenarioContext - { - public ConcurrentBag SentRegisterEndpointCommands { get; } = []; - } -} \ No newline at end of file From 4c7c3bcb6659e44568f71506770446e17ecc268c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 22 Sep 2026 14:56:28 +0000 Subject: [PATCH 3/3] Handle audit spy endpoint startup probes Co-authored-by: afprtclr <208591451+afprtclr@users.noreply.github.com> --- .../Monitoring/When_a_new_endpoint_is_detected.cs | 5 +++-- .../When_importing_a_message_resolved_by_a_retry.cs | 11 +++++++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/ServiceControl.Audit.AcceptanceTests/Monitoring/When_a_new_endpoint_is_detected.cs b/src/ServiceControl.Audit.AcceptanceTests/Monitoring/When_a_new_endpoint_is_detected.cs index 98d9471627..275ab2e9d5 100644 --- a/src/ServiceControl.Audit.AcceptanceTests/Monitoring/When_a_new_endpoint_is_detected.cs +++ b/src/ServiceControl.Audit.AcceptanceTests/Monitoring/When_a_new_endpoint_is_detected.cs @@ -7,8 +7,8 @@ using Contracts.EndpointControl; using NServiceBus; using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTesting.Customization; using NUnit.Framework; + using Conventions = NServiceBus.AcceptanceTesting.Customization.Conventions; class When_a_new_endpoint_is_detected : AcceptanceTest { @@ -18,7 +18,8 @@ public async Task Should_notify_service_control() SetSettings = settings => settings.ServiceControlQueueAddress = Conventions.EndpointNamingConvention(typeof(ServiceControlSpy)); var context = await Define() - .WithEndpoint() + // The audit instance probes the primary queue on startup with an empty message the spy cannot deserialize. + .WithEndpoint(b => b.DoNotFailOnErrorMessages()) .WithEndpoint(b => b.When((bus, c) => bus.SendLocal(new MyMessage()))) .Done(c => c.SentRegisterEndpointCommands.Any(command => command.Endpoint.Name == Conventions.EndpointNamingConvention(typeof(Receiver)))) .Run(); diff --git a/src/ServiceControl.Audit.AcceptanceTests/Recoverability/When_importing_a_message_resolved_by_a_retry.cs b/src/ServiceControl.Audit.AcceptanceTests/Recoverability/When_importing_a_message_resolved_by_a_retry.cs index 711597be5f..c429727c27 100644 --- a/src/ServiceControl.Audit.AcceptanceTests/Recoverability/When_importing_a_message_resolved_by_a_retry.cs +++ b/src/ServiceControl.Audit.AcceptanceTests/Recoverability/When_importing_a_message_resolved_by_a_retry.cs @@ -2,6 +2,7 @@ namespace ServiceControl.Audit.AcceptanceTests.Recoverability { using System; using System.Collections.Concurrent; + using System.Linq; using System.Threading.Tasks; using AcceptanceTesting; using AcceptanceTesting.EndpointTemplates; @@ -10,8 +11,8 @@ namespace ServiceControl.Audit.AcceptanceTests.Recoverability using Contracts.EndpointControl; using NServiceBus; using NServiceBus.AcceptanceTesting; - using NServiceBus.AcceptanceTesting.Customization; using NUnit.Framework; + using Conventions = NServiceBus.AcceptanceTesting.Customization.Conventions; class When_importing_a_message_resolved_by_a_retry : AcceptanceTest { @@ -24,7 +25,8 @@ public async Task Should_set_status_to_resolved() var messageId = Guid.NewGuid().ToString(); var context = await Define() - .WithEndpoint() + // The audit instance probes the primary queue on startup with an empty message the spy cannot deserialize. + .WithEndpoint(b => b.DoNotFailOnErrorMessages()) .WithEndpoint(b => b.When(s => { var options = new SendOptions(); @@ -36,7 +38,8 @@ public async Task Should_set_status_to_resolved() })) .Done(async c => { - if (!c.SentRegisterEndpointCommands.Any(command => command.Endpoint.Name == Conventions.EndpointNamingConvention(typeof(Receiver)))) + var receiverRegistered = c.SentRegisterEndpointCommands.Any(command => command.Endpoint.Name == Conventions.EndpointNamingConvention(typeof(Receiver))); + if (!receiverRegistered) { return false; } @@ -45,7 +48,7 @@ public async Task Should_set_status_to_resolved() auditedMessage = result; - return result && c.SentRegisterEndpointCommands.Any(command => command.Endpoint.Name == Conventions.EndpointNamingConvention(typeof(Receiver))); + return result && receiverRegistered; }) .Run();