From e618137ce7e3b2cad25393561b48d10c2ecbf577 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Sat, 6 Jun 2026 11:41:52 -0400 Subject: [PATCH] test(e2e): add InstanceConfigureFixture (template+attr+connection+area+instance on site-a) Also extends AddAttributeAsync with an optional dataSourceReference parameter so the fixture attribute appears in both _bindingDataSourceAttrs (bindings UI) and _overrideAttrs (overrides UI) on the InstanceConfigure page. --- .../Cluster/CliRunner.Helpers.cs | 28 +++++- .../Deployment/InstanceConfigureFixture.cs | 91 +++++++++++++++++++ 2 files changed, 115 insertions(+), 4 deletions(-) create mode 100644 tests/ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests/Deployment/InstanceConfigureFixture.cs diff --git a/tests/ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests/Cluster/CliRunner.Helpers.cs b/tests/ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests/Cluster/CliRunner.Helpers.cs index d0195447..6ca4ffee 100644 --- a/tests/ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests/Cluster/CliRunner.Helpers.cs +++ b/tests/ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests/Cluster/CliRunner.Helpers.cs @@ -70,14 +70,34 @@ public static partial class CliRunner /// (Boolean, Int32, Double, String). /// Defaults to Double. /// + /// + /// Optional data source reference (tag path). When provided, maps to + /// --data-source on the CLI and sets + /// TemplateAttribute.DataSourceReference. The InstanceConfigure page + /// populates _bindingDataSourceAttrs by filtering attributes to those + /// where DataSourceReference is non-empty, so an attribute that needs + /// to appear in the Connection Bindings panel MUST be created with this set. + /// /// The CLI failed. - public static async Task AddAttributeAsync(int templateId, string name, string dataType = "Double") + public static async Task AddAttributeAsync( + int templateId, string name, string dataType = "Double", + string? dataSourceReference = null) { - await RunAsync( + var inv = System.Globalization.CultureInfo.InvariantCulture; + var args = new List + { "template", "attribute", "add", - "--template-id", templateId.ToString(System.Globalization.CultureInfo.InvariantCulture), + "--template-id", templateId.ToString(inv), "--name", name, - "--data-type", dataType); + "--data-type", dataType, + }; + if (!string.IsNullOrEmpty(dataSourceReference)) + { + args.Add("--data-source"); + args.Add(dataSourceReference); + } + + await RunAsync([.. args]); } /// diff --git a/tests/ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests/Deployment/InstanceConfigureFixture.cs b/tests/ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests/Deployment/InstanceConfigureFixture.cs new file mode 100644 index 00000000..760ec95c --- /dev/null +++ b/tests/ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests/Deployment/InstanceConfigureFixture.cs @@ -0,0 +1,91 @@ +using System.Text.Json; +using ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests.Cluster; + +namespace ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests.Deployment; + +/// +/// fixture for the InstanceConfigure E2E tests. Provisions, on the real +/// running site-a: a zztest template with a single bindable Double attribute, a zztest +/// data-connection (so the bindings UI has a connection to assign), a zztest area (for the +/// area-reassignment test), and one instance created with no area. The instance is NOT deployed — +/// bindings/overrides/area assignment are pre-deploy configuration operations. +/// +/// +/// Why the attribute is created with --data-source "Value": +/// InstanceConfigure.razor.cs populates _bindingDataSourceAttrs by filtering +/// GetAttributesByTemplateIdAsync to attributes where +/// !string.IsNullOrEmpty(a.DataSourceReference) (line 581 of InstanceConfigure.razor). +/// A plain Double attribute created without --data-source has +/// DataSourceReference = null, so it would not appear in the Connection Bindings panel +/// and the bindings-UI tests would silently see "No data-sourced attributes". The attribute +/// DOES appear unconditionally in _overrideAttrs (filtered only on !IsLocked, +/// line 592), so overrides work with or without a tag path. The fixture therefore sets +/// dataSourceReference: "Value" when adding the attribute so both panels are exercised. +/// +/// +public sealed class InstanceConfigureFixture : IAsyncLifetime +{ + private const string SiteAIdentifier = "site-a"; + + public int SiteAId { get; private set; } + public int TemplateId { get; private set; } + public int ConnectionId { get; private set; } + public int AreaId { get; private set; } + public int InstanceId { get; private set; } + + /// The single bindable/overridable attribute name on the fixture template. + public string AttributeName => "Value"; + + /// The fixture data-connection name (for locating it in the bindings UI dropdown). + public string ConnectionName { get; private set; } = string.Empty; + + public bool Available { get; private set; } + + public async Task InitializeAsync() + { + Available = await ClusterAvailability.IsAvailableAsync(); + if (!Available) + { + return; + } + + try + { + SiteAId = await CliRunner.ResolveSiteIdAsync(SiteAIdentifier); + TemplateId = await CliRunner.CreateTemplateAsync(CliRunner.UniqueName("cfgtmpl")); + // The attribute must have DataSourceReference set (via --data-source) so it appears + // in _bindingDataSourceAttrs on the InstanceConfigure page. Without it the Connection + // Bindings panel shows "No data-sourced attributes" and binding tests cannot run. + // See the class-level XML doc for the full analysis. + await CliRunner.AddAttributeAsync(TemplateId, AttributeName, "Double", dataSourceReference: AttributeName); + ConnectionName = CliRunner.UniqueName("conn"); + ConnectionId = await CliRunner.CreateDataConnectionAsync(SiteAId, ConnectionName); + AreaId = await CliRunner.CreateAreaAsync(SiteAId, CliRunner.UniqueName("cfgarea")); + InstanceId = await CliRunner.CreateInstanceAsync(CliRunner.UniqueName("cfginst"), TemplateId, SiteAId); + } + catch + { + await SafeCleanupAsync(); + Available = false; + throw; + } + } + + public async Task DisposeAsync() + { + if (!Available) + { + return; + } + + await SafeCleanupAsync(); + } + + private async Task SafeCleanupAsync() + { + await CliRunner.DeleteInstanceAsync(InstanceId); + await CliRunner.DeleteDataConnectionAsync(ConnectionId); + await CliRunner.DeleteAreaAsync(AreaId); + await CliRunner.DeleteTemplateAsync(TemplateId); + } +}