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; /// /// Round-trip tests for the additive Secrets / SecretsApplied wire contract /// on / /// (proto field 9 / field 6). Site secrets design §3.3, task-2 of /// docs/plans/2026-09-28-site-secrets-artifact-deploy-design.md. /// 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); } }