From 616e0a8f9b5d8cff49e9afcf147b8cc7f21005cf Mon Sep 17 00:00:00 2001 From: Ramon Smits Date: Wed, 9 Sep 2026 16:58:31 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20Stop=20the=20dismissed=20custom?= =?UTF-8?q?=20check=20test=20racing=20the=20endpoint's=20next=20report?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The test waited to observe the check gone from the API after dismissing it, but the endpoint reports every second, so the check is only ever absent for an instant and the poll regularly missed it. Six of the last nineteen Windows-Raven failures were this test stuck on that step. It now waits for the dismissal to be processed, observed through the CustomCheckDeleted domain event, and then for a report stamped after it, which proves the same thing without depending on timing. --- ...hen_a_failing_custom_check_is_dismissed.cs | 27 +++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/src/ServiceControl.AcceptanceTests/Monitoring/CustomChecks/When_a_failing_custom_check_is_dismissed.cs b/src/ServiceControl.AcceptanceTests/Monitoring/CustomChecks/When_a_failing_custom_check_is_dismissed.cs index 29c462b580..2a1967cd6b 100644 --- a/src/ServiceControl.AcceptanceTests/Monitoring/CustomChecks/When_a_failing_custom_check_is_dismissed.cs +++ b/src/ServiceControl.AcceptanceTests/Monitoring/CustomChecks/When_a_failing_custom_check_is_dismissed.cs @@ -7,6 +7,8 @@ namespace ServiceControl.AcceptanceTests.Monitoring.CustomChecks using AcceptanceTesting; using AcceptanceTesting.EndpointTemplates; using Contracts.CustomChecks; + using Infrastructure.DomainEvents; + using Microsoft.Extensions.DependencyInjection; using NServiceBus; using NServiceBus.AcceptanceTesting; using NServiceBus.CustomChecks; @@ -23,6 +25,8 @@ public async Task Should_come_back_while_the_check_is_still_failing() CustomCheckView dismissed = null; CustomCheckView returned = null; + CustomizeHostBuilder = hostBuilder => hostBuilder.Services.AddSingleton, DismissalObserver>(); + await Define() .WithEndpoint() .Do("Wait for the check to report a failure", async ctx => @@ -36,16 +40,13 @@ await Define() }) .Do("Dismiss it from the page", async _ => await this.Delete($"/api/customchecks/{WithoutPrefix(dismissed.Id)}")) - .Do("Wait until it has gone", async _ => - { - var checks = await this.TryGetMany("/api/customchecks"); - - return checks.Items.All(check => check.CustomCheckId != CheckId); - }) - .Do("Wait for the next report from the endpoint", async _ => + .Do("Wait until the dismissal has been processed", ctx => Task.FromResult(ctx.DismissedAt != null)) + // The endpoint reports every second, so the check is only ever absent for an instant and a + // poll cannot be expected to catch it. A report from after the dismissal proves the same thing. + .Do("Wait for a report from after the dismissal", async ctx => { var checks = await this.TryGetMany("/api/customchecks", - check => check.CustomCheckId == CheckId && check.Status == CheckStatus.Fail); + check => check.CustomCheckId == CheckId && check.Status == CheckStatus.Fail && check.ReportedAt > ctx.DismissedAt); returned = checks.HasResult ? checks.Items.Single() : null; @@ -67,6 +68,16 @@ static string WithoutPrefix(string id) => class Context : ScenarioContext, ISequenceContext { public int Step { get; set; } + public DateTime? DismissedAt { get; set; } + } + + class DismissalObserver(Context context) : IDomainHandler + { + public Task Handle(ServiceControl.CustomChecks.CustomCheckDeleted domainEvent, CancellationToken cancellationToken = default) + { + context.DismissedAt = DateTime.UtcNow; + return Task.CompletedTask; + } } public class Checked : EndpointConfigurationBuilder