using Google.Protobuf; using Org.Eclipse.Tahu.Protobuf; using Shouldly; using Xunit; namespace ZB.MOM.WW.OtOpcUa.Driver.Mqtt.Tests; /// /// Pins the vendored Eclipse Tahu sparkplug_b protobuf codegen: the generated types exist, /// land in the namespace the rest of P2 is written against, and carry the Sparkplug-B field numbers /// on the wire. Nothing here decodes a real payload — that is Task 16 (SparkplugCodec + /// golden vectors); this suite exists so a broken/renamed/mangled vendored proto fails at the /// codegen seam rather than as a mysterious decode failure three tasks later. /// public sealed class SparkplugProtoCodegenTests { [Fact] public void GeneratedPayload_RoundTrips() { var p = new Payload { Seq = 3 }; p.Metrics.Add(new Payload.Types.Metric { Name = "Temperature", Alias = 5 }); var back = Payload.Parser.ParseFrom(p.ToByteArray()); back.Seq.ShouldBe(3ul); back.Metrics[0].Name.ShouldBe("Temperature"); back.Metrics[0].Alias.ShouldBe(5ul); } /// /// Tasks 16 / 20 / 25 name Org.Eclipse.Tahu.Protobuf explicitly, and the namespace is /// derived (not declared) — protoc PascalCases the proto package because the vendored file /// carries no option csharp_namespace. Editing the package line to "tidy" it would silently /// rehome every generated type. /// [Fact] public void GeneratedTypes_LiveInTheTahuPackageNamespace() { typeof(Payload).Namespace.ShouldBe("Org.Eclipse.Tahu.Protobuf"); Payload.Descriptor.File.Package.ShouldBe("org.eclipse.tahu.protobuf"); } /// /// Field numbers are the wire contract with every Sparkplug edge node in the plant; a vendored /// proto that was hand-edited or reconstructed rather than copied from upstream would most likely /// drift here first. /// [Fact] public void PayloadAndMetric_CarrySparkplugBFieldNumbers() { Payload.TimestampFieldNumber.ShouldBe(1); Payload.MetricsFieldNumber.ShouldBe(2); Payload.SeqFieldNumber.ShouldBe(3); Payload.UuidFieldNumber.ShouldBe(4); Payload.BodyFieldNumber.ShouldBe(5); Payload.Types.Metric.NameFieldNumber.ShouldBe(1); Payload.Types.Metric.AliasFieldNumber.ShouldBe(2); Payload.Types.Metric.TimestampFieldNumber.ShouldBe(3); Payload.Types.Metric.DatatypeFieldNumber.ShouldBe(4); Payload.Types.Metric.IsNullFieldNumber.ShouldBe(7); } /// /// Task 17's datatype map keys off these indices. They are Sparkplug-spec constants, not protobuf /// tags, so they are worth pinning independently of the field numbers above. /// [Fact] public void DataTypeEnum_MatchesSparkplugSpecIndices() { ((int)DataType.Int8).ShouldBe(1); ((int)DataType.Int32).ShouldBe(3); ((int)DataType.Uint64).ShouldBe(8); ((int)DataType.Float).ShouldBe(9); ((int)DataType.Double).ShouldBe(10); ((int)DataType.Boolean).ShouldBe(11); ((int)DataType.String).ShouldBe(12); ((int)DataType.DateTime).ShouldBe(13); ((int)DataType.DataSet).ShouldBe(16); ((int)DataType.Template).ShouldBe(19); ((int)DataType.Int32Array).ShouldBe(24); } /// /// protoc's C# generator RE-CASES enum member names: the proto's UInt64 becomes /// Uint64 and UUID becomes Uuid (only the leading character of each /// underscore-separated word survives as a capital). Task 17's datatype map has to spell them /// the mangled way, so pin the mangling rather than letting it be rediscovered as a build error. /// The wire-facing spelling is preserved on OriginalName, which is what /// DataType.Parser-style JSON round-trips use. /// [Fact] public void DataTypeEnum_CSharpNamesAreProtocMangled_NotTheProtoSpelling() { Enum.GetNames().ShouldContain("Uint64"); Enum.GetNames().ShouldNotContain("UInt64"); Enum.GetNames().ShouldContain("Uuid"); Enum.GetNames().ShouldNotContain("UUID"); } }