From 981f849796512a549b12cfe2e00c4b7d17c67bd7 Mon Sep 17 00:00:00 2001 From: Aaron Stannard Date: Sat, 9 May 2026 08:41:02 -0500 Subject: [PATCH] fix(tests): resolve passivation test race condition and missing DI registrations Two issues prevented reliable CI on the passivation-abort integration tests: 1. Race condition in New_user_message_during_passivation_aborts_shutdown: AwaitAssertAsync was gating only on _fakeChatClient.CallCount == 1 (LLM called), then immediately checking TurnCount. Because the in-memory journal persists asynchronously, TurnCount could still be 0 at the moment CallCount first reaches 1. The fix polls with a witness probe until both CallCount == 1 and TurnCount == 1 are true before the final subscriber re-join. 2. Missing DI registrations in LlmSessionTestBase: EffectivePolicyDefaults (required by ReminderManagerActor) and BackgroundJobDefinitionStore (required by BackgroundJobManagerActor) were not registered, causing actor initialization failures on every test in every LlmSessionTestBase subclass. Added both to ConfigureServices so the actors start cleanly across all session integration tests. --- .../Sessions/LlmSessionIntegrationTests.cs | 26 +++++++++++++++---- .../Sessions/LlmSessionTestBase.cs | 3 +++ 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/src/Netclaw.Actors.Tests/Sessions/LlmSessionIntegrationTests.cs b/src/Netclaw.Actors.Tests/Sessions/LlmSessionIntegrationTests.cs index 7436518f..41e2b293 100644 --- a/src/Netclaw.Actors.Tests/Sessions/LlmSessionIntegrationTests.cs +++ b/src/Netclaw.Actors.Tests/Sessions/LlmSessionIntegrationTests.cs @@ -1417,11 +1417,27 @@ await sessionManager.Ask(new JoinSession }, TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken); Assert.Equal(sessionId, ack.SessionId); - await AwaitAssertAsync(() => - { - Assert.Equal(1, _fakeChatClient.CallCount); - return Task.CompletedTask; - }, TimeSpan.FromSeconds(3), TimeSpan.FromMilliseconds(100), cancellationToken: TestContext.Current.CancellationToken); + + // Poll until the turn is fully persisted. Checking CallCount alone is not + // sufficient — the in-memory journal persists asynchronously, so TurnCount + // can still be 0 at the moment CallCount first reaches 1. CallCount is + // intentionally NOT asserted inside the retry loop: if retries push it above + // the expected value, a strict equality check would loop forever rather than + // failing fast. Assert it once after the loop, when the actor is idle. + // JoinSession is idempotent for the same subscriber (Dictionary keyed by + // IActorRef), so repeated calls with the witness probe are safe. + var witness = CreateTestProbe("passivation-witness"); + await AwaitAssertAsync(async () => + { + var peek = await sessionManager.Ask(new JoinSession + { + SessionId = sessionId, + Subscriber = witness, + Filter = OutputFilter.TextOnly + }, TimeSpan.FromSeconds(1), cancellationToken: TestContext.Current.CancellationToken); + Assert.Equal(1, peek.TurnCount); + }, TimeSpan.FromSeconds(5), TimeSpan.FromMilliseconds(100), cancellationToken: TestContext.Current.CancellationToken); + Assert.Equal(1, _fakeChatClient.CallCount); var rejoined = await sessionManager.Ask(new JoinSession { diff --git a/src/Netclaw.Actors.Tests/Sessions/LlmSessionTestBase.cs b/src/Netclaw.Actors.Tests/Sessions/LlmSessionTestBase.cs index 4a9eba9e..59e6c83f 100644 --- a/src/Netclaw.Actors.Tests/Sessions/LlmSessionTestBase.cs +++ b/src/Netclaw.Actors.Tests/Sessions/LlmSessionTestBase.cs @@ -9,6 +9,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Netclaw.Actors.Hosting; +using Netclaw.Actors.Jobs; using Netclaw.Configuration; namespace Netclaw.Actors.Tests.Sessions; @@ -30,6 +31,8 @@ protected sealed override void ConfigureServices(HostBuilderContext context, ISe { services.AddSingleton(new FakeCapabilityResolver()); services.AddTestNetclawPaths(); + services.AddSingleton(SecurityPolicyDefaults.Resolve(null)); + services.AddSingleton(); ConfigureSessionServices(services); services.AddLlmSessionCompositeRecords(); }