using Akka.Actor;
using Akka.Cluster.Tools.PublishSubscribe;
using Akka.TestKit.Xunit2;
using Microsoft.Extensions.Logging.Abstractions;
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Services;
using ZB.MOM.WW.ScadaBridge.Commons.Messages.Communication;
namespace ZB.MOM.WW.ScadaBridge.Host.Tests;
///
/// ScadaBridge#100 item 3: the central cache-invalidation bus publishes typed invalidations on
/// one topic, and the subscriber hands the peer's (never its own) to every handler.
///
public class CentralCacheInvalidationBusTests : TestKit
{
private sealed class RecordingHandler : ICentralCacheInvalidationHandler
{
public List Applied { get; } = new();
public void Apply(ICentralCacheInvalidation invalidation)
{
lock (Applied) Applied.Add(invalidation);
}
}
private sealed class ThrowingHandler : ICentralCacheInvalidationHandler
{
public void Apply(ICentralCacheInvalidation invalidation) => throw new InvalidOperationException("boom");
}
private readonly CentralCacheInvalidationBus _bus = new(NullLogger.Instance);
/// Publishing wraps the invalidation with this node's origin id on the shared topic.
[Fact]
public void Publish_SendsAnEnvelopeOnTheSharedTopic()
{
var mediator = CreateTestProbe();
_bus.SetMediator(mediator.Ref);
_bus.Publish(new RoleMappingsInvalidated());
var publish = mediator.ExpectMsg();
Assert.Equal("central-cache-invalidation", publish.Topic);
var envelope = Assert.IsType(publish.Message);
Assert.Equal(_bus.OriginId, envelope.OriginId);
Assert.IsType(envelope.Invalidation);
mediator.ExpectNoMsg(TimeSpan.FromMilliseconds(100)); // only PSK also uses the legacy topic
}
/// Before the mediator exists publishing is a silent no-op.
[Fact]
public void Publish_WithoutAMediator_DoesNotThrow()
{
_bus.Publish(new ExternalDefinitionsInvalidated());
}
/// The peer's invalidation reaches every handler; this node's own broadcast is skipped.
[Fact]
public void Subscriber_AppliesPeerInvalidations_AndSkipsItsOwn()
{
var handler = new RecordingHandler();
var subscriber = Sys.ActorOf(Props.Create(() => new CentralCacheInvalidationSubscriber(
_bus, new ICentralCacheInvalidationHandler[] { new ThrowingHandler(), handler })));
subscriber.Tell(new CentralCacheInvalidationEnvelope(_bus.OriginId, new RoleMappingsInvalidated()));
subscriber.Tell(new CentralCacheInvalidationEnvelope("peer", new ExternalDefinitionsInvalidated()));
subscriber.Tell(new CentralCacheInvalidationEnvelope("peer", new InboundApiMethodInvalidated("M")));
AwaitAssert(() =>
{
lock (handler.Applied)
{
// A throwing handler ahead of it does not stop delivery.
Assert.Equal(
new ICentralCacheInvalidation[] { new ExternalDefinitionsInvalidated(), new InboundApiMethodInvalidated("M") },
handler.Applied);
}
});
}
}