fix(comm): site delete disposes the site's cached gRPC channels via RemoveSiteAsync (plan R2-02 T13)

This commit is contained in:
Joseph Doherty
2026-07-13 10:22:56 -04:00
parent b7f8632db8
commit 4e228110ec
2 changed files with 59 additions and 0 deletions
@@ -5,8 +5,10 @@ using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using NSubstitute;
using ZB.MOM.WW.ScadaBridge.Communication;
using ZB.MOM.WW.ScadaBridge.Communication.Grpc;
using NSubstitute.ExceptionExtensions;
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Instances;
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Sites;
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Schemas;
using ZB.MOM.WW.ScadaBridge.Commons.Entities.SecuredWrites;
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Scripts;
@@ -3599,6 +3601,51 @@ public class ManagementActorTests : TestKit, IDisposable
ExpectMsg<ManagementUnauthorized>(TimeSpan.FromSeconds(5));
}
// ── R2 T13: site delete disposes the site's cached gRPC channels (N8) ──
[Fact]
public void DeleteSite_DisposesTheSitesCachedGrpcChannels()
{
var siteRepo = Substitute.For<ISiteRepository>();
siteRepo.GetSiteByIdAsync(7, Arg.Any<CancellationToken>())
.Returns(new Site("Seven", "site-7") { Id = 7 });
siteRepo.GetInstancesBySiteIdAsync(7, Arg.Any<CancellationToken>())
.Returns((IReadOnlyList<Instance>)new List<Instance>());
_services.AddScoped(_ => siteRepo);
var factory = new TrackingGrpcFactory();
var cached = (TrackingClient)factory.GetOrCreate("site-7", "http://node-a:8083");
_services.AddSingleton<SiteStreamGrpcClientFactory>(factory);
var actor = CreateActor();
actor.Tell(Envelope(new DeleteSiteCommand(7), "Administrator"));
ExpectMsg<ManagementSuccess>(TimeSpan.FromSeconds(5));
AwaitAssert(() =>
{
Assert.True(cached.Disposed);
Assert.Null(factory.TryGet("site-7", "http://node-a:8083"));
}, TimeSpan.FromSeconds(5));
}
private sealed class TrackingClient : SiteStreamGrpcClient
{
public TrackingClient(string endpoint) : base(endpoint) { }
public bool Disposed { get; private set; }
public override void Dispose() => Disposed = true;
public override ValueTask DisposeAsync()
{
Disposed = true;
return ValueTask.CompletedTask;
}
}
private sealed class TrackingGrpcFactory : SiteStreamGrpcClientFactory
{
public TrackingGrpcFactory() : base(NullLoggerFactory.Instance) { }
protected override SiteStreamGrpcClient CreateClient(string grpcEndpoint) => new TrackingClient(grpcEndpoint);
}
/// <summary>
/// Records remote-query relays for the actor-dispatch tests. The browse /
/// search / verify / cert-trust methods on <see cref="CommunicationService"/>