using System; using System.Threading; using System.Threading.Tasks; using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Host.Backend; using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Host.Backend.Galaxy; using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Shared.Contracts; using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.TestSupport; namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Host.Tests { /// /// Live smoke against the Galaxy ZB repository. Skipped when ZB is unreachable so /// CI / dev boxes without an AVEVA install still pass. Exercises the ported /// + against the same /// SQL the v1 Host uses, proving the lift is byte-for-byte equivalent at the /// DiscoverHierarchyResponse shape. /// /// /// Since PR 36, skip logic is delegated to /// so operators see exactly why a test skipped ("ZB db not found" vs "SQL Server /// unreachable") instead of a silent return. /// [Trait("Category", "LiveGalaxy")] public sealed class GalaxyRepositoryLiveSmokeTests { private static GalaxyRepositoryOptions DevZbOptions() => new() { ConnectionString = "Server=localhost;Database=ZB;Integrated Security=True;TrustServerCertificate=True;Encrypt=False;Connect Timeout=2;", CommandTimeoutSeconds = 10, }; private static async Task RepositorySkipReasonAsync() { using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(4)); var report = await AvevaPrerequisites.CheckRepositoryOnlyAsync( DevZbOptions().ConnectionString, cts.Token); return report.SkipReason; } private static async Task ZbReachableAsync() { // Legacy silent-skip adapter — keeps the existing tests compiling while // gradually migrating to the Skip-with-reason pattern. Returns true when the // prerequisite check has no Fail entries. return (await RepositorySkipReasonAsync()) is null; } [Fact] public async Task TestConnection_returns_true_against_live_ZB() { if (!await ZbReachableAsync()) return; var repo = new GalaxyRepository(DevZbOptions()); (await repo.TestConnectionAsync()).ShouldBeTrue(); } [Fact] public async Task GetHierarchy_returns_at_least_one_deployed_gobject() { if (!await ZbReachableAsync()) return; var repo = new GalaxyRepository(DevZbOptions()); var rows = await repo.GetHierarchyAsync(); rows.Count.ShouldBeGreaterThan(0, "the dev Galaxy has at least the WinPlatform + AppEngine deployed"); rows.ShouldAllBe(r => !string.IsNullOrEmpty(r.TagName)); } [Fact] public async Task GetAttributes_returns_attributes_for_deployed_objects() { if (!await ZbReachableAsync()) return; var repo = new GalaxyRepository(DevZbOptions()); var attrs = await repo.GetAttributesAsync(); attrs.Count.ShouldBeGreaterThan(0); attrs.ShouldAllBe(a => !string.IsNullOrEmpty(a.FullTagReference) && a.FullTagReference.Contains(".")); } [Fact] public async Task GetLastDeployTime_returns_a_value() { if (!await ZbReachableAsync()) return; var repo = new GalaxyRepository(DevZbOptions()); var ts = await repo.GetLastDeployTimeAsync(); ts.ShouldNotBeNull(); } [Fact] public async Task DbBackedBackend_DiscoverAsync_returns_objects_with_attributes_and_categories() { if (!await ZbReachableAsync()) return; var backend = new DbBackedGalaxyBackend(new GalaxyRepository(DevZbOptions())); var resp = await backend.DiscoverAsync(new DiscoverHierarchyRequest { SessionId = 1 }, CancellationToken.None); resp.Success.ShouldBeTrue(resp.Error); resp.Objects.Length.ShouldBeGreaterThan(0); var firstWithAttrs = System.Linq.Enumerable.FirstOrDefault(resp.Objects, o => o.Attributes.Length > 0); firstWithAttrs.ShouldNotBeNull("at least one gobject in the dev Galaxy carries dynamic attributes"); firstWithAttrs!.TemplateCategory.ShouldNotBeNullOrEmpty(); } } }