- Admin-010: vendor Bootstrap 5.3.3 (CSS + JS bundle + maps + provenance README) under wwwroot/lib/bootstrap and reference local paths from App.razor — Admin no longer pulls Bootstrap from jsDelivr. - Admin-011: swap FleetStatusPoller's three plain dictionaries for ConcurrentDictionary so ResetCache can't race a poll tick. - Admin-012: drop the EquipmentId column from EquipmentCsvImporter (per admin-ui.md — equipment id is system-derived from EquipmentUuid); EquipmentImportBatchService and the textarea placeholder updated to match. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
65 lines
3.1 KiB
C#
65 lines
3.1 KiB
C#
using System.IO;
|
|
using System.Reflection;
|
|
using Shouldly;
|
|
using Xunit;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Admin.Tests;
|
|
|
|
/// <summary>
|
|
/// Regression for Admin-010 — admin-ui.md "Tech Stack" requires Bootstrap 5
|
|
/// "vendored under wwwroot/lib/bootstrap/" so the Admin app has no third-party
|
|
/// runtime dependency and works in air-gapped fleet deployments. These tests
|
|
/// guard against a future re-introduction of the cdn.jsdelivr.net references
|
|
/// in App.razor.
|
|
/// </summary>
|
|
[Trait("Category", "Unit")]
|
|
public sealed class BootstrapVendoringTests
|
|
{
|
|
[Fact]
|
|
public void AppRazor_does_not_reference_a_remote_CDN_for_bootstrap()
|
|
{
|
|
var appRazor = File.ReadAllText(ResolveAdminPath("Components/App.razor"));
|
|
|
|
appRazor.ShouldNotContain("cdn.jsdelivr.net",
|
|
customMessage: "Admin-010: Bootstrap must be served from the vendored copy under wwwroot/lib/bootstrap/, not jsDelivr — air-gapped deployments cannot reach the public CDN.");
|
|
appRazor.ShouldNotContain("cdnjs.cloudflare.com",
|
|
customMessage: "Admin-010: third-party CDN references regress the vendoring requirement.");
|
|
appRazor.ShouldNotContain("unpkg.com",
|
|
customMessage: "Admin-010: third-party CDN references regress the vendoring requirement.");
|
|
}
|
|
|
|
[Fact]
|
|
public void AppRazor_references_vendored_bootstrap_assets()
|
|
{
|
|
var appRazor = File.ReadAllText(ResolveAdminPath("Components/App.razor"));
|
|
|
|
appRazor.ShouldContain("lib/bootstrap/css/bootstrap.min.css",
|
|
customMessage: "App.razor must load the vendored Bootstrap stylesheet.");
|
|
appRazor.ShouldContain("lib/bootstrap/js/bootstrap.bundle.min.js",
|
|
customMessage: "App.razor must load the vendored Bootstrap JS bundle.");
|
|
}
|
|
|
|
[Fact]
|
|
public void Vendored_bootstrap_assets_exist_under_wwwroot_lib_bootstrap()
|
|
{
|
|
var root = ResolveAdminPath("wwwroot/lib/bootstrap");
|
|
|
|
Directory.Exists(root).ShouldBeTrue($"expected vendored bootstrap directory at '{root}'");
|
|
File.Exists(Path.Combine(root, "css", "bootstrap.min.css")).ShouldBeTrue("bootstrap.min.css missing");
|
|
File.Exists(Path.Combine(root, "js", "bootstrap.bundle.min.js")).ShouldBeTrue("bootstrap.bundle.min.js missing");
|
|
|
|
// Sanity-check non-empty (a zero-byte placeholder would still pass File.Exists).
|
|
new FileInfo(Path.Combine(root, "css", "bootstrap.min.css")).Length.ShouldBeGreaterThan(100_000);
|
|
new FileInfo(Path.Combine(root, "js", "bootstrap.bundle.min.js")).Length.ShouldBeGreaterThan(50_000);
|
|
}
|
|
|
|
/// <summary>Resolve a path under the Admin source project from the test runner's bin folder.</summary>
|
|
private static string ResolveAdminPath(string relative)
|
|
{
|
|
var asmDir = Path.GetDirectoryName(typeof(BootstrapVendoringTests).Assembly.Location)!;
|
|
// tests/Server/ZB.MOM.WW.OtOpcUa.Admin.Tests/bin/Debug/net10.0 -> ../../../../../src/Server/...
|
|
var repoRoot = Path.GetFullPath(Path.Combine(asmDir, "..", "..", "..", "..", "..", ".."));
|
|
return Path.Combine(repoRoot, "src", "Server", "ZB.MOM.WW.OtOpcUa.Admin", relative.Replace('/', Path.DirectorySeparatorChar));
|
|
}
|
|
}
|