using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.Core.Authorization; namespace ZB.MOM.WW.OtOpcUa.Core.Tests.Authorization; [Trait("Category", "Unit")] public sealed class PermissionTrieCacheTests { private static PermissionTrie Trie(string cluster, long generation) => new() { ClusterId = cluster, GenerationId = generation, }; [Fact] public void GetTrie_Empty_ReturnsNull() { new PermissionTrieCache().GetTrie("c1").ShouldBeNull(); } [Fact] public void Install_ThenGet_RoundTrips() { var cache = new PermissionTrieCache(); cache.Install(Trie("c1", 5)); cache.GetTrie("c1")!.GenerationId.ShouldBe(5); cache.CurrentGenerationId("c1").ShouldBe(5); } [Fact] public void NewGeneration_BecomesCurrent() { var cache = new PermissionTrieCache(); cache.Install(Trie("c1", 1)); cache.Install(Trie("c1", 2)); cache.CurrentGenerationId("c1").ShouldBe(2); cache.GetTrie("c1", 1).ShouldNotBeNull("prior generation retained for in-flight requests"); cache.GetTrie("c1", 2).ShouldNotBeNull(); } [Fact] public void OutOfOrder_Install_DoesNotDowngrade_Current() { var cache = new PermissionTrieCache(); cache.Install(Trie("c1", 3)); cache.Install(Trie("c1", 1)); // late-arriving older generation cache.CurrentGenerationId("c1").ShouldBe(3, "older generation must not become current"); cache.GetTrie("c1", 1).ShouldNotBeNull("but older is still retrievable by explicit lookup"); } [Fact] public void Invalidate_DropsCluster() { var cache = new PermissionTrieCache(); cache.Install(Trie("c1", 1)); cache.Install(Trie("c2", 1)); cache.Invalidate("c1"); cache.GetTrie("c1").ShouldBeNull(); cache.GetTrie("c2").ShouldNotBeNull("sibling cluster unaffected"); } [Fact] public void Prune_RetainsMostRecent() { var cache = new PermissionTrieCache(); for (var g = 1L; g <= 5; g++) cache.Install(Trie("c1", g)); cache.Prune("c1", keepLatest: 2); cache.GetTrie("c1", 5).ShouldNotBeNull(); cache.GetTrie("c1", 4).ShouldNotBeNull(); cache.GetTrie("c1", 3).ShouldBeNull(); cache.GetTrie("c1", 1).ShouldBeNull(); } [Fact] public void Prune_LessThanKeep_IsNoOp() { var cache = new PermissionTrieCache(); cache.Install(Trie("c1", 1)); cache.Install(Trie("c1", 2)); cache.Prune("c1", keepLatest: 10); cache.CachedTrieCount.ShouldBe(2); } [Fact] public void ClusterIsolation() { var cache = new PermissionTrieCache(); cache.Install(Trie("c1", 1)); cache.Install(Trie("c2", 9)); cache.CurrentGenerationId("c1").ShouldBe(1); cache.CurrentGenerationId("c2").ShouldBe(9); } }