89d2becba9
Removes the #96 compatibility path that the unified central-cache-invalidation topic (#100 item 3, 153609ef; compat3a31dd2d) carried for one release: - CentralCacheInvalidationBus no longer publishes a bare SitePskInvalidated on site-psk-invalidation; LegacySitePskTopic constant deleted. - CentralCacheInvalidationSubscriber no longer subscribes to that topic or handles a bare SitePskInvalidated; it applies envelopes only. central-cache-invalidation behaviour is unchanged (one envelope per invalidation, origin filtering, per-handler fault isolation). Gate met:8e32f353(carries153609efand3a31dd2d) runs on every central node - DEV vd03 (record581e25ca) and QA vq03/vq04 (record8ba5d5d7). Mixed-version / rolling-upgrade constraint: a central node built before153609eflistens only on site-psk-invalidation and no longer receives PSK invalidations from a newer peer (only its TTL and the site-rejection refresh bound the staleness). Mixed pairs across that boundary are no longer supported; central pairs are stopped and started together anyway. Documented in Component-Host.md and Component-Communication.md. Tests: the legacy publish/subscribe tests are replaced by PskRotation_OnOneNode_ReachesThePeersCaches_ThroughTheUnifiedTopic (node A publishes, node B's PSK cache and secret-resolver cache are invalidated, and nothing else is published) and a bare-message-ignored test with an envelope positive control. The two-node cluster test now asserts the PSK invalidation arrives exactly once and the publisher applies nothing. Claude-Session: https://claude.ai/code/session_01GT67ACmpMR7b9LGRPLijMi
81 lines
3.3 KiB
C#
81 lines
3.3 KiB
C#
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;
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
public class CentralCacheInvalidationBusTests : TestKit
|
|
{
|
|
private sealed class RecordingHandler : ICentralCacheInvalidationHandler
|
|
{
|
|
public List<ICentralCacheInvalidation> 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<CentralCacheInvalidationBus>.Instance);
|
|
|
|
/// <summary>Publishing wraps the invalidation with this node's origin id on the shared topic.</summary>
|
|
[Fact]
|
|
public void Publish_SendsAnEnvelopeOnTheSharedTopic()
|
|
{
|
|
var mediator = CreateTestProbe();
|
|
_bus.SetMediator(mediator.Ref);
|
|
|
|
_bus.Publish(new RoleMappingsInvalidated());
|
|
|
|
var publish = mediator.ExpectMsg<Publish>();
|
|
Assert.Equal("central-cache-invalidation", publish.Topic);
|
|
var envelope = Assert.IsType<CentralCacheInvalidationEnvelope>(publish.Message);
|
|
Assert.Equal(_bus.OriginId, envelope.OriginId);
|
|
Assert.IsType<RoleMappingsInvalidated>(envelope.Invalidation);
|
|
mediator.ExpectNoMsg(TimeSpan.FromMilliseconds(100)); // one topic, one publish (#105)
|
|
}
|
|
|
|
/// <summary>Before the mediator exists publishing is a silent no-op.</summary>
|
|
[Fact]
|
|
public void Publish_WithoutAMediator_DoesNotThrow()
|
|
{
|
|
_bus.Publish(new ExternalDefinitionsInvalidated());
|
|
}
|
|
|
|
/// <summary>The peer's invalidation reaches every handler; this node's own broadcast is skipped.</summary>
|
|
[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);
|
|
}
|
|
});
|
|
}
|
|
}
|