using Microsoft.Extensions.Configuration;
using Shouldly;
using Xunit;
namespace ZB.MOM.WW.OtOpcUa.Runtime.Tests.Historian;
///
/// #603 + review R1 m3: the two "is this node's LocalDb replicated" predicates — permissive for table
/// registration (), strict for the alarm drain gate
/// ().
///
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(p.Key, p.Value)))
.Build();
/// Registration: PeerAddress OR ApiKey counts; the listen port alone and blank values do not.
[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);
}
/// Drain gate: ApiKey AND (PeerAddress OR SyncListenPort > 0).
[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);
}
/// Every node the gate treats as sharing rows also captures — never a gated node with no capture.
[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();
}
/// No replication keys at all is an unpaired node under both predicates.
[Fact]
public void Both_predicates_are_false_for_an_empty_configuration()
{
LocalDbReplicationPolicy.CapturesChanges(Config()).ShouldBeFalse();
LocalDbReplicationPolicy.SharesRowsWithPeer(Config()).ShouldBeFalse();
}
}