513fa1aa16
Comment/XML-doc changes only: - add summaries to ~10k test methods, fixtures, fakes and helpers (+ <param> on theories, <returns> on async Task methods, <inheritdoc/> on interface implementations the checker can resolve) - strip ~2.6k project-tracking references (WP*, P*-*, row-NN, Task N, #NN, Component-0NN) from comments, keeping the explanatory text; reword false positives in comments only - real docs on fakes implementing BCL/library interfaces (IDisposable, ILogger, IOptionsMonitor, IAsyncLifetime) - reword the last 5 src/ false positives (CWE-1236, failures-1, year-0001, M365) Only non-comment edits: four one-line nested classes split across lines so members can carry docs. Build 0 errors / 0 warnings.
262 lines
10 KiB
C#
262 lines
10 KiB
C#
using Bunit;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using NSubstitute;
|
|
using ZB.MOM.WW.ScadaBridge.CentralUI.Services;
|
|
using ZB.MOM.WW.ScadaBridge.Commons.Types.DataConnections;
|
|
using ZB.MOM.WW.ScadaBridge.Commons.Types.Flattening;
|
|
using ZB.MOM.WW.ScadaBridge.Commons.Validators;
|
|
using OpcUaEndpointEditor = ZB.MOM.WW.ScadaBridge.CentralUI.Components.Forms.OpcUaEndpointEditor;
|
|
|
|
namespace ZB.MOM.WW.ScadaBridge.CentralUI.Tests.Forms;
|
|
|
|
public class OpcUaEndpointEditorTests : BunitContext
|
|
{
|
|
/// <summary>Initializes the bUnit context with no-op endpoint verification and certificate management services.</summary>
|
|
public OpcUaEndpointEditorTests()
|
|
{
|
|
// OpcUaEndpointEditor injects IEndpointVerificationService for its Verify-endpoint
|
|
// button (B8 / T17) and ICertManagementService for the Trust-certificate button
|
|
// (B10 / T17). These tests only exercise the form bindings, so no-op substitutes
|
|
// satisfy the injections without driving verification or trust.
|
|
Services.AddSingleton<IEndpointVerificationService>(Substitute.For<IEndpointVerificationService>());
|
|
Services.AddSingleton<ICertManagementService>(Substitute.For<ICertManagementService>());
|
|
}
|
|
|
|
/// <summary>Verifies that the editor renders all four section labels.</summary>
|
|
[Fact]
|
|
public void Renders_All_Four_Section_Labels()
|
|
{
|
|
var config = new OpcUaEndpointConfig();
|
|
var cut = Render<OpcUaEndpointEditor>(p => p
|
|
.Add(c => c.Config, config)
|
|
.Add(c => c.Title, "Primary Endpoint"));
|
|
|
|
Assert.Contains("Primary Endpoint", cut.Markup);
|
|
Assert.Contains("Timing", cut.Markup);
|
|
Assert.Contains("Subscription", cut.Markup);
|
|
Assert.Contains("Heartbeat", cut.Markup);
|
|
}
|
|
|
|
/// <summary>Verifies that editing a field mutates the configuration instance passed to the editor.</summary>
|
|
[Fact]
|
|
public void Binding_MutatesPassedConfigInstance()
|
|
{
|
|
var config = new OpcUaEndpointConfig();
|
|
var cut = Render<OpcUaEndpointEditor>(p => p.Add(c => c.Config, config));
|
|
|
|
cut.Find("input[type='text']").Change("opc.tcp://new-host:4840");
|
|
|
|
Assert.Equal("opc.tcp://new-host:4840", config.EndpointUrl);
|
|
}
|
|
|
|
/// <summary>Verifies that enabling the heartbeat creates the heartbeat sub-object.</summary>
|
|
[Fact]
|
|
public void EnableHeartbeat_CreatesSubObject()
|
|
{
|
|
var config = new OpcUaEndpointConfig();
|
|
var cut = Render<OpcUaEndpointEditor>(p => p.Add(c => c.Config, config));
|
|
|
|
Assert.Null(config.Heartbeat);
|
|
cut.FindAll("button").First(b => b.TextContent.Contains("Enable Heartbeat")).Click();
|
|
|
|
Assert.NotNull(config.Heartbeat);
|
|
}
|
|
|
|
/// <summary>Verifies that removing the heartbeat sets the heartbeat sub-object to null.</summary>
|
|
[Fact]
|
|
public void RemoveHeartbeat_NullsSubObject()
|
|
{
|
|
var config = new OpcUaEndpointConfig
|
|
{
|
|
Heartbeat = new OpcUaHeartbeatConfig { TagPath = "Hb", MaxSilenceSeconds = 30 }
|
|
};
|
|
var cut = Render<OpcUaEndpointEditor>(p => p.Add(c => c.Config, config));
|
|
|
|
cut.FindAll("button").First(b => b.TextContent.Contains("Remove Heartbeat")).Click();
|
|
|
|
Assert.Null(config.Heartbeat);
|
|
}
|
|
|
|
/// <summary>Verifies that the errors parameter renders red error text beside the affected fields.</summary>
|
|
[Fact]
|
|
public void Errors_Parameter_RendersPerFieldRedText()
|
|
{
|
|
var config = new OpcUaEndpointConfig { EndpointUrl = "" };
|
|
var errors = OpcUaEndpointConfigValidator.Validate(config, "Primary.");
|
|
var cut = Render<OpcUaEndpointEditor>(p => p
|
|
.Add(c => c.Config, config)
|
|
.Add(c => c.Errors, errors));
|
|
|
|
Assert.Contains("Endpoint URL is required.", cut.Markup);
|
|
Assert.Contains("text-danger", cut.Markup);
|
|
}
|
|
|
|
/// <summary>Verifies that a legacy configuration renders the warning banner.</summary>
|
|
[Fact]
|
|
public void IsLegacy_True_RendersWarningBanner()
|
|
{
|
|
var cut = Render<OpcUaEndpointEditor>(p => p
|
|
.Add(c => c.Config, new OpcUaEndpointConfig())
|
|
.Add(c => c.IsLegacy, true));
|
|
|
|
Assert.Contains("alert-warning", cut.Markup);
|
|
Assert.Contains("migrated from a legacy format", cut.Markup);
|
|
}
|
|
|
|
// ── Layer E: new editor sections ──
|
|
|
|
/// <summary>Verifies that the editor renders the authentication section label.</summary>
|
|
[Fact]
|
|
public void Renders_Authentication_Section_Label()
|
|
{
|
|
var cut = Render<OpcUaEndpointEditor>(p => p.Add(c => c.Config, new OpcUaEndpointConfig()));
|
|
Assert.Contains("Authentication", cut.Markup);
|
|
}
|
|
|
|
/// <summary>Verifies that enabling authentication creates the user identity sub-object.</summary>
|
|
[Fact]
|
|
public void EnableAuthentication_CreatesUserIdentitySubObject()
|
|
{
|
|
var config = new OpcUaEndpointConfig();
|
|
var cut = Render<OpcUaEndpointEditor>(p => p.Add(c => c.Config, config));
|
|
|
|
Assert.Null(config.UserIdentity);
|
|
cut.FindAll("button").First(b => b.TextContent.Contains("Enable Authentication")).Click();
|
|
|
|
Assert.NotNull(config.UserIdentity);
|
|
Assert.Equal(OpcUaUserTokenType.Anonymous, config.UserIdentity!.TokenType);
|
|
}
|
|
|
|
/// <summary>Verifies that removing authentication sets the user identity to null.</summary>
|
|
[Fact]
|
|
public void RemoveAuthentication_NullsUserIdentity()
|
|
{
|
|
var config = new OpcUaEndpointConfig
|
|
{
|
|
UserIdentity = new OpcUaUserIdentityConfig
|
|
{
|
|
TokenType = OpcUaUserTokenType.UsernamePassword,
|
|
Username = "alice",
|
|
Password = "secret"
|
|
}
|
|
};
|
|
var cut = Render<OpcUaEndpointEditor>(p => p.Add(c => c.Config, config));
|
|
|
|
cut.FindAll("button").First(b => b.TextContent.Contains("Remove Authentication")).Click();
|
|
|
|
Assert.Null(config.UserIdentity);
|
|
}
|
|
|
|
/// <summary>Verifies that the username/password token type renders username and password inputs.</summary>
|
|
[Fact]
|
|
public void UsernamePassword_RendersUsernameAndPasswordInputs()
|
|
{
|
|
var config = new OpcUaEndpointConfig
|
|
{
|
|
UserIdentity = new OpcUaUserIdentityConfig
|
|
{
|
|
TokenType = OpcUaUserTokenType.UsernamePassword
|
|
}
|
|
};
|
|
var cut = Render<OpcUaEndpointEditor>(p => p.Add(c => c.Config, config));
|
|
|
|
// <label>Username</label> only renders for the UsernamePassword branch
|
|
Assert.Contains(">Username<", cut.Markup);
|
|
Assert.Contains(">Password<", cut.Markup);
|
|
Assert.DoesNotContain(">Certificate path<", cut.Markup);
|
|
}
|
|
|
|
/// <summary>Verifies that the X.509 certificate token type renders the certificate fields.</summary>
|
|
[Fact]
|
|
public void X509Certificate_RendersCertificateFields()
|
|
{
|
|
var config = new OpcUaEndpointConfig
|
|
{
|
|
UserIdentity = new OpcUaUserIdentityConfig
|
|
{
|
|
TokenType = OpcUaUserTokenType.X509Certificate
|
|
}
|
|
};
|
|
var cut = Render<OpcUaEndpointEditor>(p => p.Add(c => c.Config, config));
|
|
|
|
Assert.Contains(">Certificate path<", cut.Markup);
|
|
Assert.Contains(">Certificate password<", cut.Markup);
|
|
Assert.DoesNotContain(">Username<", cut.Markup);
|
|
}
|
|
|
|
/// <summary>Verifies that the anonymous token type shows no extra credential fields.</summary>
|
|
[Fact]
|
|
public void AnonymousTokenType_ShowsNoExtraFields()
|
|
{
|
|
var config = new OpcUaEndpointConfig
|
|
{
|
|
UserIdentity = new OpcUaUserIdentityConfig { TokenType = OpcUaUserTokenType.Anonymous }
|
|
};
|
|
var cut = Render<OpcUaEndpointEditor>(p => p.Add(c => c.Config, config));
|
|
|
|
Assert.DoesNotContain(">Username<", cut.Markup);
|
|
Assert.DoesNotContain(">Certificate path<", cut.Markup);
|
|
}
|
|
|
|
/// <summary>Verifies that enabling the deadband creates the deadband sub-object.</summary>
|
|
[Fact]
|
|
public void EnableDeadband_CreatesDeadbandSubObject()
|
|
{
|
|
var config = new OpcUaEndpointConfig();
|
|
var cut = Render<OpcUaEndpointEditor>(p => p.Add(c => c.Config, config));
|
|
|
|
Assert.Null(config.Deadband);
|
|
cut.FindAll("button").First(b => b.TextContent.Contains("Enable Deadband")).Click();
|
|
|
|
Assert.NotNull(config.Deadband);
|
|
}
|
|
|
|
/// <summary>Verifies that removing the deadband sets the deadband sub-object to null.</summary>
|
|
[Fact]
|
|
public void RemoveDeadband_NullsDeadband()
|
|
{
|
|
var config = new OpcUaEndpointConfig
|
|
{
|
|
Deadband = new OpcUaDeadbandConfig { Type = OpcUaDeadbandType.Percent, Value = 1.5 }
|
|
};
|
|
var cut = Render<OpcUaEndpointEditor>(p => p.Add(c => c.Config, config));
|
|
|
|
cut.FindAll("button").First(b => b.TextContent.Contains("Remove Deadband")).Click();
|
|
|
|
Assert.Null(config.Deadband);
|
|
}
|
|
|
|
/// <summary>Verifies that the advanced subscription section renders.</summary>
|
|
[Fact]
|
|
public void AdvancedSubscription_Section_Renders()
|
|
{
|
|
var config = new OpcUaEndpointConfig();
|
|
var cut = Render<OpcUaEndpointEditor>(p => p.Add(c => c.Config, config));
|
|
|
|
Assert.Contains("Discard oldest", cut.Markup);
|
|
Assert.Contains("Subscription display name", cut.Markup);
|
|
Assert.Contains("Subscription priority", cut.Markup);
|
|
Assert.Contains("Timestamps to return", cut.Markup);
|
|
}
|
|
|
|
/// <summary>Verifies that a user identity error renders under the username field.</summary>
|
|
[Fact]
|
|
public void UserIdentityError_RendersPerFieldUnderUsername()
|
|
{
|
|
var config = new OpcUaEndpointConfig
|
|
{
|
|
UserIdentity = new OpcUaUserIdentityConfig
|
|
{
|
|
TokenType = OpcUaUserTokenType.UsernamePassword,
|
|
Username = ""
|
|
}
|
|
};
|
|
var errors = OpcUaEndpointConfigValidator.Validate(config, "Primary.");
|
|
var cut = Render<OpcUaEndpointEditor>(p => p
|
|
.Add(c => c.Config, config)
|
|
.Add(c => c.Errors, errors));
|
|
|
|
Assert.Contains("Username is required", cut.Markup);
|
|
}
|
|
}
|