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 single non-locked HiLo alarm on the fixture template (for the merge override test). public string AlarmName => "HiHi"; /// /// A second, non-HiLo (ValueMatch) alarm on the fixture template. HiLo overrides MERGE into /// inherited setpoints, but ValueMatch / RangeViolation / RateOfChange overrides WHOLE-REPLACE /// the inherited trigger config — so this alarm exercises the whole-replace path that the HiLo /// alarm cannot. Bound to the same (Value) so the /// AlarmTriggerEditor's attribute picker already has it selected. /// public string NonHiLoAlarmName => "Match"; /// Trigger type of . public string NonHiLoAlarmTriggerType => "ValueMatch"; /// The inherited (template) ValueMatch value baked into . public string NonHiLoInheritedMatchValue => "100"; /// 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); await CliRunner.AddAlarmAsync(TemplateId, AlarmName, "HiLo", priority: 500, attribute: AttributeName, hi: 80, hiHi: 95); // Second, non-HiLo alarm so the whole-replace override path is covered (HiLo merges, // ValueMatch/RangeViolation/RateOfChange whole-replace). Bound to the same attribute. await CliRunner.AddAlarmAsync(TemplateId, NonHiLoAlarmName, NonHiLoAlarmTriggerType, priority: 400, attribute: AttributeName, matchValue: NonHiLoInheritedMatchValue); 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); } }