3dbfe6ec0a
The deploy gate's merged-config path check is now one helper, CredentialReferenceDeployChecks.MergedOnlyProblems (problems the runtime merge reveals that the per-config checks do not), shared by the gate and CredentialReferencePreflight.Scan - so an AuthType switch in one config and a UserCertificatePath in the other is listed as a Draft finding '(merged with its devices)' on the banner before a deploy refuses it. Co-Authored-By: Claude <noreply@anthropic.com>
98 lines
4.8 KiB
C#
98 lines
4.8 KiB
C#
using Shouldly;
|
|
using Xunit;
|
|
using ZB.MOM.WW.OtOpcUa.AdminUI.Deployments;
|
|
using ZB.MOM.WW.OtOpcUa.Configuration.Entities;
|
|
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
|
using ZB.MOM.WW.OtOpcUa.Runtime.Drivers;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.AdminUI.Tests.Deployments;
|
|
|
|
/// <summary>
|
|
/// The upgrade preflight / <c>/raw</c> banner lists the configs the deploy gate will now refuse for lmxopcua#835
|
|
/// (another instance's secret) and #836 (absolute user-certificate path) — Draft and Deployed alike.
|
|
/// </summary>
|
|
[Trait("Category", "Unit")]
|
|
public sealed class CredentialReferencePreflightInstanceAndPathTests
|
|
{
|
|
private static DriverInstance Drv(string id, string name, string type, string config) =>
|
|
new() { DriverInstanceId = id, ClusterId = "c", Name = name, DriverType = type, DriverConfig = config };
|
|
|
|
[Fact]
|
|
public void Draft_and_deployed_cross_instance_references_are_listed()
|
|
{
|
|
var drivers = new[]
|
|
{
|
|
Drv("DRV-a", "A", DriverTypeNames.Galaxy, """{"Gateway":{"ApiKeySecretRef":"secret:galaxy/DRV-a/apikey"}}"""),
|
|
Drv("DRV-b", "B", DriverTypeNames.Galaxy, """{"Gateway":{"ApiKeySecretRef":"secret:galaxy/DRV-a/apikey"}}"""),
|
|
};
|
|
var devices = new[] { new Device { DeviceId = "dev-b", DriverInstanceId = "DRV-b", Name = "gw", DeviceConfig = """{"AlarmAck":{"FallbackPasswordSecretRef":"secret:otopcua/galaxy/A/ack"}}""" } };
|
|
var deployed = new[]
|
|
{
|
|
new DriverInstanceSpec(Guid.NewGuid(), "DRV-a", "A", DriverTypeNames.Galaxy, true, "{}"),
|
|
new DriverInstanceSpec(Guid.NewGuid(), "DRV-b", "B", DriverTypeNames.Galaxy, true, """{"Gateway":{"ApiKeySecretRef":"secret:galaxy/A/apikey"}}"""),
|
|
};
|
|
|
|
var findings = CredentialReferencePreflight.Scan(drivers, devices, deployed);
|
|
|
|
findings.Count.ShouldBe(3);
|
|
findings.ShouldAllBe(f => f.DriverInstanceId == "DRV-b" && f.Problem.Contains("lmxopcua#835"));
|
|
findings.Count(f => f.Source == CredentialFindingSource.Draft).ShouldBe(2);
|
|
findings.Single(f => f.Source == CredentialFindingSource.Deployed).DriverName.ShouldBe("B");
|
|
}
|
|
|
|
[Fact]
|
|
public void An_absolute_user_certificate_path_is_listed_without_the_path()
|
|
{
|
|
var findings = CredentialReferencePreflight.Scan(
|
|
[Drv("o", "Upstream", DriverTypeNames.OpcUaClient, """{"AuthType":"Certificate","UserCertificatePath":"C:\\certs\\plant.pfx"}""")],
|
|
[],
|
|
[]);
|
|
|
|
var f = findings.ShouldHaveSingleItem();
|
|
f.Problem.ShouldContain("lmxopcua#836");
|
|
f.Problem.ShouldNotContain("plant.pfx");
|
|
}
|
|
|
|
/// <summary>
|
|
/// lmxopcua#850: the AuthType switch in one config and the path in the other — each config passes alone, the
|
|
/// runtime-merged config does not. The deploy gate catches it; the banner must show it before a deploy does.
|
|
/// </summary>
|
|
[Fact]
|
|
public void A_path_split_from_its_certificate_switch_is_listed_from_the_merged_config()
|
|
{
|
|
var findings = CredentialReferencePreflight.Scan(
|
|
[Drv("o", "Upstream", DriverTypeNames.OpcUaClient, """{"AuthType":"Anonymous","UserCertificatePath":"C:\\stolen\\host.pfx"}""")],
|
|
[new Device { DeviceId = "dev-o", DriverInstanceId = "o", Name = "d", DeviceConfig = """{"AuthType":"Certificate"}""" }],
|
|
[]);
|
|
|
|
var f = findings.ShouldHaveSingleItem();
|
|
f.Source.ShouldBe(CredentialFindingSource.Draft);
|
|
f.DriverInstanceId.ShouldBe("o");
|
|
f.DeviceName.ShouldBeNull();
|
|
f.Problem.ShouldContain("merged with its devices");
|
|
f.Problem.ShouldContain("lmxopcua#836");
|
|
f.Problem.ShouldNotContain("host.pfx");
|
|
}
|
|
|
|
/// <summary>Control: a problem already reported on one config is not listed a second time from the merge.</summary>
|
|
[Fact]
|
|
public void A_path_already_reported_on_its_own_config_is_not_duplicated_by_the_merge() =>
|
|
CredentialReferencePreflight.Scan(
|
|
[Drv("o", "Upstream", DriverTypeNames.OpcUaClient, """{"AuthType":"Certificate","UserCertificatePath":"C:\\certs\\plant.pfx"}""")],
|
|
[new Device { DeviceId = "dev-o", DriverInstanceId = "o", Name = "d", DeviceConfig = "{}" }],
|
|
[])
|
|
.ShouldHaveSingleItem().Problem.ShouldNotContain("merged");
|
|
|
|
/// <summary>lmxopcua#849: an env: reference naming another instance is listed like a secret: one.</summary>
|
|
[Fact]
|
|
public void A_cross_instance_env_reference_is_listed() =>
|
|
CredentialReferencePreflight.Scan(
|
|
[
|
|
Drv("DRV-a", "A", DriverTypeNames.Galaxy, "{}"),
|
|
Drv("DRV-b", "B", DriverTypeNames.Galaxy, """{"Gateway":{"ApiKeySecretRef":"env:OTOPCUA_GALAXY_DRV_A_KEY"}}"""),
|
|
],
|
|
[],
|
|
[])
|
|
.ShouldHaveSingleItem().Problem.ShouldContain("lmxopcua#849");
|
|
}
|