From f8b60467b9bbb23392e045afa6286c0b42791a97 Mon Sep 17 00:00:00 2001 From: Ramon Smits Date: Wed, 9 Sep 2026 16:56:31 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20Keep=20index=20building=20out=20?= =?UTF-8?q?of=20the=20retention=20window=20in=20the=20audit=20retention=20?= =?UTF-8?q?tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The two second retention window started at ingestion, while the fresh database's indexes were still being built, and on a busy runner that alone took longer than the window, so the query that should see the document before it expires found nothing. The indexes are now built before ingestion, and the fixed four second sleep is replaced by polling, so the test takes as long as expiration actually needs. --- .../RetentionTests.cs | 63 +++++++++++++------ 1 file changed, 45 insertions(+), 18 deletions(-) diff --git a/src/ServiceControl.Audit.Persistence.Tests.RavenDB/RetentionTests.cs b/src/ServiceControl.Audit.Persistence.Tests.RavenDB/RetentionTests.cs index 97512f2a85..2d850fa6cd 100644 --- a/src/ServiceControl.Audit.Persistence.Tests.RavenDB/RetentionTests.cs +++ b/src/ServiceControl.Audit.Persistence.Tests.RavenDB/RetentionTests.cs @@ -2,8 +2,10 @@ { using System; using System.Collections.Generic; + using System.Threading; using System.Threading.Tasks; using Auditing; + using Auditing.MessagesView; using NServiceBus; using NUnit.Framework; using SagaAudit; @@ -16,8 +18,8 @@ public override Task Setup() { SetSettings = s => { - s.AuditRetentionPeriod = TimeSpan.FromSeconds(2); - s.PersisterSpecificSettings["ExpirationProcessTimerInSeconds"] = 3.ToString(); + s.AuditRetentionPeriod = RetentionPeriod; + s.PersisterSpecificSettings["ExpirationProcessTimerInSeconds"] = 1.ToString(); }; return base.Setup(); } @@ -25,27 +27,30 @@ public override Task Setup() [Test] public async Task AuditMessageRetention() { + await WaitForIndexesToBeBuilt(); + var message = MakeMessage("MyMessageId"); await IngestProcessedMessagesAudits(message); - var queryResultBeforeExpiration = await DataStore.QueryMessages("MyMessageId", new PagingInfo(), new SortInfo("Id", "asc"), cancellationToken: TestContext.CurrentContext.CancellationToken); + var queryResultBeforeExpiration = await QueryMessages(TestContext.CurrentContext.CancellationToken); - await Task.Delay(4000); + Assert.That(queryResultBeforeExpiration.Results, Has.Count.EqualTo(1)); + Assert.That(queryResultBeforeExpiration.Results[0].MessageId, Is.EqualTo("MyMessageId")); - var queryResultAfterExpiration = await DataStore.QueryMessages("MyMessageId", new PagingInfo(), new SortInfo("Id", "asc"), cancellationToken: TestContext.CurrentContext.CancellationToken); + var queryResultAfterExpiration = await WaitUntil(QueryMessages, result => result.Results.Count == 0); - Assert.That(queryResultBeforeExpiration.Results, Has.Count.EqualTo(1)); - using (Assert.EnterMultipleScope()) - { - Assert.That(queryResultBeforeExpiration.Results[0].MessageId, Is.EqualTo("MyMessageId")); - Assert.That(queryResultAfterExpiration.Results.Count, Is.EqualTo(0)); - } + Assert.That(queryResultAfterExpiration.Results, Is.Empty); + + Task>> QueryMessages(CancellationToken cancellationToken) => + DataStore.QueryMessages("MyMessageId", new PagingInfo(), new SortInfo("Id", "asc"), cancellationToken: cancellationToken); } [Test] public async Task SagaSnapshotRetention() { + await WaitForIndexesToBeBuilt(); + var sagaId = Guid.NewGuid(); var otherSagaId = Guid.NewGuid(); @@ -55,20 +60,42 @@ await IngestSagaAudits( new SagaSnapshot { SagaId = sagaId } ); - var queryResultBeforeExpiration = await DataStore.QuerySagaHistoryById(sagaId, TestContext.CurrentContext.CancellationToken); + var queryResultBeforeExpiration = await QuerySagaHistory(TestContext.CurrentContext.CancellationToken); - await Task.Delay(4000); + Assert.That(queryResultBeforeExpiration.Results, Is.Not.Null); + Assert.That(queryResultBeforeExpiration.Results.Changes, Has.Count.EqualTo(2)); - var queryResultAfterExpiration = await DataStore.QuerySagaHistoryById(sagaId, TestContext.CurrentContext.CancellationToken); + var queryResultAfterExpiration = await WaitUntil(QuerySagaHistory, result => result.Results == null); - Assert.That(queryResultBeforeExpiration.Results, Is.Not.Null); - using (Assert.EnterMultipleScope()) + Assert.That(queryResultAfterExpiration.Results, Is.Null); + + Task> QuerySagaHistory(CancellationToken cancellationToken) => + DataStore.QuerySagaHistoryById(sagaId, cancellationToken); + } + + // The retention window starts at ingestion, so building the indexes of the fresh database must + // not happen inside it: on a busy runner that alone has taken longer than the window. + Task WaitForIndexesToBeBuilt() => configuration.CompleteDBOperation(); + + // Polls instead of sleeping for a fixed time, so the test takes as long as expiration actually + // needs and still tolerates a slow expiration pass. + static async Task WaitUntil(Func> query, Func condition) + { + var cancellationToken = TestContext.CurrentContext.CancellationToken; + var deadline = DateTime.UtcNow + RetentionPeriod + TimeSpan.FromSeconds(15); + var result = await query(cancellationToken); + + while (!condition(result) && DateTime.UtcNow < deadline) { - Assert.That(queryResultBeforeExpiration.Results.Changes, Has.Count.EqualTo(2)); - Assert.That(queryResultAfterExpiration.Results, Is.Null); + await Task.Delay(250, cancellationToken); + result = await query(cancellationToken); } + + return result; } + static readonly TimeSpan RetentionPeriod = TimeSpan.FromSeconds(3); + ProcessedMessage MakeMessage( string messageId = null, MessageIntent intent = MessageIntent.Send,