feat: add JoeAppEngine OPC UA nodes, fix DCL auto-reconnect and quality push

- Add JoeAppEngine folder to OPC UA nodes.json (BTCS, AlarmCntsBySeverity, Scheduler/ScanTime)
- Fix DataConnectionActor: capture Self in PreStart for use from non-actor threads,
  preventing Self.Tell failure in Disconnected event handler
- Implement InstanceActor.HandleConnectionQualityChanged to mark attributes Bad on disconnect
- Fix LmxFakeProxy TagMapper to serialize arrays as JSON instead of "System.Int32[]"
- Allow DataType and DataSourceReference updates in TemplateService.UpdateAttributeAsync
- Update test_infra_opcua.md with JoeAppEngine documentation
This commit is contained in:
Joseph Doherty
2026-03-19 13:27:54 -04:00
parent ffdda51990
commit 7740a3bcf9
70 changed files with 2684 additions and 541 deletions

View File

@@ -0,0 +1,83 @@
using System.Security.Claims;
using Bunit;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.Extensions.DependencyInjection;
using ScadaLink.CentralUI.Components.Pages;
namespace ScadaLink.CentralUI.Tests;
/// <summary>
/// bUnit rendering tests for CentralUI Blazor components.
/// Verifies that pages render their expected markup structure.
/// </summary>
public class ComponentRenderingTests : BunitContext
{
[Fact]
public void LoginPage_RendersForm_WithUsernameAndPasswordFields()
{
var cut = Render<Login>();
// Verify the form action
var form = cut.Find("form");
Assert.Equal("/auth/login", form.GetAttribute("action"));
// Verify username field
var usernameInput = cut.Find("input#username");
Assert.Equal("text", usernameInput.GetAttribute("type"));
Assert.Equal("username", usernameInput.GetAttribute("name"));
// Verify password field
var passwordInput = cut.Find("input#password");
Assert.Equal("password", passwordInput.GetAttribute("type"));
Assert.Equal("password", passwordInput.GetAttribute("name"));
// Verify submit button
var submitButton = cut.Find("button[type='submit']");
Assert.Contains("Sign In", submitButton.TextContent);
}
[Fact]
public void LoginPage_WithoutError_DoesNotRenderAlert()
{
var cut = Render<Login>();
Assert.Throws<Bunit.ElementNotFoundException>(() => cut.Find("div.alert.alert-danger"));
}
[Fact]
public void Dashboard_RequiresAuthorizeAttribute()
{
var authorizeAttrs = typeof(Dashboard)
.GetCustomAttributes(typeof(AuthorizeAttribute), true);
Assert.NotEmpty(authorizeAttrs);
}
[Fact]
public void TemplateEditor_RequiresDesignPolicy()
{
var authorizeAttrs = typeof(ScadaLink.CentralUI.Components.Pages.Design.Templates)
.GetCustomAttributes(typeof(AuthorizeAttribute), true);
Assert.NotEmpty(authorizeAttrs);
var attr = (AuthorizeAttribute)authorizeAttrs[0];
Assert.Equal("RequireDesign", attr.Policy);
}
[Fact]
public void LoginPage_RendersLdapCredentialHint()
{
var cut = Render<Login>();
Assert.Contains("LDAP credentials", cut.Markup);
}
[Fact]
public void LoginPage_RendersScadaLinkTitle()
{
var cut = Render<Login>();
var title = cut.Find("h4.card-title");
Assert.Equal("ScadaLink", title.TextContent);
}
}

View File

@@ -9,8 +9,10 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="bunit" Version="2.0.33-preview" />
<PackageReference Include="coverlet.collector" Version="6.0.4" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<PackageReference Include="NSubstitute" Version="5.3.0" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.4" />
</ItemGroup>
@@ -19,6 +21,10 @@
<Using Include="Xunit" />
</ItemGroup>
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../../src/ScadaLink.CentralUI/ScadaLink.CentralUI.csproj" />
</ItemGroup>