Files
lmxopcua/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Historian/LocalDbReplicationPolicyTests.cs
T
Joseph Doherty e3dcb180c3 fix(runtime): Primary verdict published from host start, cache boot never adopts malformed bytes, strict drain-gate predicate, HostAbortedException passthrough, seed only Up members
Review R1:
- M1 (#609): DriverHostActor.StopChild releases the instance's resilience state before evicting the hub.
- m2 (#597): DriverHostActor publishes ShouldServiceAsPrimary() to IDriverPrimaryRoleSource at start and on
  cluster membership changes (deduped), so the native-ack gate denies in a pair's boot window.
- m4 (#601): ApplyCachedArtifact returns false on empty/malformed bytes; the cached revision is not adopted,
  Direct mode goes Stale (no RUNNING FROM CACHE), FetchAndCache lands Steady-with-no-revision and a
  re-dispatch of the same revision really applies.
- m3 (#603): LocalDbReplicationPolicy splits into CapturesChanges (PeerAddress||ApiKey, registration) and
  SharesRowsWithPeer (ApiKey && (PeerAddress||SyncListenPort>0), alarm drain gate).
- m5 (#618): Program.cs catch-all filters HostAbortedException (design-time tooling).
- m6 (Cluster-002): ClusterRoleInfo seeds only Up/WeaklyUp members.

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
2026-10-08 07:47:34 -04:00

68 lines
3.3 KiB
C#

using Microsoft.Extensions.Configuration;
using Shouldly;
using Xunit;
namespace ZB.MOM.WW.OtOpcUa.Runtime.Tests.Historian;
/// <summary>
/// #603 + review R1 m3: the two "is this node's LocalDb replicated" predicates — permissive for table
/// registration (<see cref="LocalDbReplicationPolicy.CapturesChanges"/>), strict for the alarm drain gate
/// (<see cref="LocalDbReplicationPolicy.SharesRowsWithPeer"/>).
/// </summary>
public sealed class LocalDbReplicationPolicyTests
{
private const string Peer = "LocalDb:Replication:PeerAddress";
private const string Key = "LocalDb:Replication:ApiKey";
private const string Port = "LocalDb:SyncListenPort";
private static IConfiguration Config(params (string Key, string? Value)[] pairs) =>
new ConfigurationBuilder()
.AddInMemoryCollection(pairs.Select(p => new KeyValuePair<string, string?>(p.Key, p.Value)))
.Build();
/// <summary>Registration: PeerAddress OR ApiKey counts; the listen port alone and blank values do not.</summary>
[Theory]
[InlineData(Peer, "http://peer:9001", true)]
[InlineData(Key, "k", true)]
[InlineData(Port, "9001", false)]
[InlineData(Key, " ", false)]
public void CapturesChanges_follows_the_PeerAddress_or_ApiKey_rule(string key, string value, bool expected)
{
LocalDbReplicationPolicy.CapturesChanges(Config((key, value))).ShouldBe(expected);
}
/// <summary>Drain gate: ApiKey AND (PeerAddress OR SyncListenPort &gt; 0).</summary>
[Theory]
[InlineData("http://peer:9001", "k", null, true)] // dialling half of a pair
[InlineData(null, "k", "9001", true)] // listening half of a pair
[InlineData("http://peer:9001", "k", "9001", true)] // both
[InlineData(null, "k", null, false)] // key, no path: shares nothing (review R1 m3)
[InlineData(null, "k", "0", false)] // port 0 = no listener
[InlineData(null, "k", "nope", false)] // unparseable port = no listener
[InlineData("http://peer:9001", null, "9001", false)] // paths, no key: both auth directions fail closed
[InlineData(null, " ", "9001", false)] // blank key
public void SharesRowsWithPeer_needs_a_key_and_a_sync_path(string? peer, string? key, string? port, bool expected)
{
LocalDbReplicationPolicy.SharesRowsWithPeer(Config((Peer, peer), (Key, key), (Port, port))).ShouldBe(expected);
}
/// <summary>Every node the gate treats as sharing rows also captures — never a gated node with no capture.</summary>
[Theory]
[InlineData("http://peer:9001", "k", null)]
[InlineData(null, "k", "9001")]
public void A_node_sharing_rows_always_captures(string? peer, string? key, string? port)
{
var config = Config((Peer, peer), (Key, key), (Port, port));
LocalDbReplicationPolicy.SharesRowsWithPeer(config).ShouldBeTrue();
LocalDbReplicationPolicy.CapturesChanges(config).ShouldBeTrue();
}
/// <summary>No replication keys at all is an unpaired node under both predicates.</summary>
[Fact]
public void Both_predicates_are_false_for_an_empty_configuration()
{
LocalDbReplicationPolicy.CapturesChanges(Config()).ShouldBeFalse();
LocalDbReplicationPolicy.SharesRowsWithPeer(Config()).ShouldBeFalse();
}
}