feat(artifacts): additive Secrets list + SecretsApplied on the artifact deploy contract (proto field 9)
This commit is contained in:
+85
@@ -0,0 +1,85 @@
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Messages.Artifacts;
|
||||
using ZB.MOM.WW.ScadaBridge.Communication.Grpc;
|
||||
|
||||
namespace ZB.MOM.WW.ScadaBridge.Communication.Tests.Grpc;
|
||||
|
||||
/// <summary>
|
||||
/// Round-trip tests for the additive <c>Secrets</c> / <c>SecretsApplied</c> wire contract
|
||||
/// on <see cref="DeployArtifactsCommand"/> / <see cref="ArtifactDeploymentResponse"/>
|
||||
/// (proto field 9 / field 6). Site secrets design §3.3, task-2 of
|
||||
/// docs/plans/2026-09-28-site-secrets-artifact-deploy-design.md.
|
||||
/// </summary>
|
||||
public class SiteCommandDtoMapperSecretsTests
|
||||
{
|
||||
private static SecretArtifact Sample(string name) => new(name, "text/plain",
|
||||
[1, 2, 3], [4], [5], [6, 7], [8], [9], "kek-A", 7,
|
||||
DateTimeOffset.Parse("2026-09-01T00:00:00Z"), DateTimeOffset.Parse("2026-09-02T00:00:00Z"), "d");
|
||||
|
||||
[Fact]
|
||||
public void DeployArtifacts_Secrets_RoundTrip_Verbatim()
|
||||
{
|
||||
var cmd = new DeployArtifactsCommand("dep", null, null, null, null, null, null,
|
||||
DateTimeOffset.UtcNow, [Sample("A"), Sample("B")]);
|
||||
var back = (DeployArtifactsCommand)SiteCommandDtoMapper.FromProto(SiteCommandDtoMapper.ToProto(cmd));
|
||||
Assert.Equal(2, back.Secrets!.Count);
|
||||
Assert.Equal(cmd.Secrets![0].Ciphertext, back.Secrets[0].Ciphertext);
|
||||
Assert.Equal(cmd.Secrets[0].Nonce, back.Secrets[0].Nonce);
|
||||
Assert.Equal(cmd.Secrets[0].Tag, back.Secrets[0].Tag);
|
||||
Assert.Equal(cmd.Secrets[0].WrappedDek, back.Secrets[0].WrappedDek);
|
||||
Assert.Equal(cmd.Secrets[0].WrapNonce, back.Secrets[0].WrapNonce);
|
||||
Assert.Equal(cmd.Secrets[0].WrapTag, back.Secrets[0].WrapTag);
|
||||
Assert.Equal(cmd.Secrets[0].ContentType, back.Secrets[0].ContentType);
|
||||
Assert.Equal(cmd.Secrets[0].CreatedUtc, back.Secrets[0].CreatedUtc);
|
||||
Assert.Equal(cmd.Secrets[0].UpdatedUtc, back.Secrets[0].UpdatedUtc);
|
||||
Assert.Equal(cmd.Secrets[0].Description, back.Secrets[0].Description);
|
||||
Assert.Equal("kek-A", back.Secrets[1].KekId);
|
||||
Assert.Equal(7, back.Secrets[1].Revision);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DeployArtifacts_NullSecrets_StaysNull()
|
||||
{
|
||||
var cmd = new DeployArtifactsCommand("dep", null, null, null, null, null, null, DateTimeOffset.UtcNow);
|
||||
var dto = SiteCommandDtoMapper.ToProto(cmd);
|
||||
Assert.Null(dto.Secrets);
|
||||
Assert.Null(((DeployArtifactsCommand)SiteCommandDtoMapper.FromProto(dto)).Secrets);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DeployArtifacts_EmptySecrets_StaysEmpty_NotNull()
|
||||
{
|
||||
var cmd = new DeployArtifactsCommand("dep", null, null, null, null, null, null,
|
||||
DateTimeOffset.UtcNow, []);
|
||||
var back = (DeployArtifactsCommand)SiteCommandDtoMapper.FromProto(SiteCommandDtoMapper.ToProto(cmd));
|
||||
Assert.NotNull(back.Secrets);
|
||||
Assert.Empty(back.Secrets!);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DeployArtifacts_CommandDtoWithoutField9_MapsToNullSecrets()
|
||||
{
|
||||
// Simulates an OLD CENTRAL that never set field 9 — the DTO here carries no
|
||||
// Secrets wrapper at all, exactly as a pre-upgrade sender's wire bytes would
|
||||
// decode. FromProto must treat that absence as null, not an empty list.
|
||||
var dto = new DeployArtifactsCommandDto
|
||||
{
|
||||
DeploymentId = "dep",
|
||||
Timestamp = Timestamp.FromDateTimeOffset(DateTimeOffset.UtcNow)
|
||||
};
|
||||
|
||||
Assert.Null(dto.Secrets);
|
||||
|
||||
var back = SiteCommandDtoMapper.FromProto(dto);
|
||||
Assert.Null(back.Secrets);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(null)]
|
||||
[InlineData(3)]
|
||||
public void ArtifactResponse_SecretsApplied_RoundTrips(int? applied)
|
||||
{
|
||||
var r = new ArtifactDeploymentResponse("dep", "site-a", true, null, DateTimeOffset.UtcNow, applied);
|
||||
Assert.Equal(applied, SiteCommandDtoMapper.FromProto(SiteCommandDtoMapper.ToProto(r)).SecretsApplied);
|
||||
}
|
||||
}
|
||||
@@ -90,6 +90,8 @@ internal static class SiteCommandSamples
|
||||
Add(new NotificationListArtifact("ops", ["a@x.com"]), new NotificationListArtifact("empty", []));
|
||||
Add(DataConnection(), new DataConnectionArtifact("bare", "OpcUa", null, null, 0));
|
||||
Add(Smtp(), new SmtpConfigurationArtifact("bare", "smtp", 25, "None", "f@x.com", null, null, null));
|
||||
Add(Secret(), new SecretArtifact(
|
||||
"bare", "text/plain", [], [], [], [], [], [], "kek-bare", 0, T2, T2));
|
||||
|
||||
// ── Lifecycle replies ──
|
||||
Add(new DeploymentStatusResponse("dep-1", "Site1.Pump1", DeploymentStatus.Success, null, T1),
|
||||
@@ -233,6 +235,11 @@ internal static class SiteCommandSamples
|
||||
private static SmtpConfigurationArtifact Smtp() =>
|
||||
new("Default", "smtp.host", 587, "Basic", "from@x.com", "user", "secret", "{\"tenant\":\"t\"}");
|
||||
|
||||
private static SecretArtifact Secret() =>
|
||||
new("EsgApiKey", "application/octet-stream",
|
||||
[1, 2, 3, 4], [5, 6], [7, 8, 9], [10, 11], [12], [13, 14, 15],
|
||||
"kek-2026-09", 4, T1, T2, "MES API key");
|
||||
|
||||
private static BrowseNode Node() => new("ns=2;s=Pump1.Speed", "Speed", BrowseNodeClass.Variable, false, "Double", -1, true);
|
||||
|
||||
private static BrowseNode NodeBare() => new("ns=2;s=Root", "Root", BrowseNodeClass.Object, true);
|
||||
|
||||
Reference in New Issue
Block a user