using System.Runtime.CompilerServices;
using Google.Protobuf;
using Shouldly;
using Xunit;
namespace ZB.MOM.WW.OtOpcUa.Driver.Mqtt.Tests;
///
/// Guards the committed Golden/*.bin Sparkplug-B wire vectors: they must still be exactly
/// what produces, and they must actually reach the test
/// binary's output directory.
///
///
///
/// Without the drift guard the vectors would be write-once decoration — a re-vendored proto,
/// a protoc upgrade, or an edited builder could change the encoding while every decode test
/// kept passing against stale bytes. With it, the committed files are a genuine pin on the
/// wire format the driver is expected to read.
///
///
/// To regenerate (only after a deliberate, reviewed change to the vectors):
/// OTOPCUA_SPARKPLUG_GOLDEN_REGEN=1 dotnet test tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Mqtt.Tests
/// --filter "FullyQualifiedName~SparkplugGoldenVectorTests", then review and commit the
/// resulting .bin diff.
///
///
public sealed class SparkplugGoldenVectorTests
{
private const string RegenEnvVar = "OTOPCUA_SPARKPLUG_GOLDEN_REGEN";
/// The committed NBIRTH vector is byte-identical to its builder's output.
[Fact]
public void Nbirth_CommittedBytes_MatchTheBuilder() =>
AssertMatchesBuilder(SparkplugGoldenPayloads.NbirthFile, SparkplugGoldenPayloads.BuildNbirth().ToByteArray());
/// The committed NDATA vector is byte-identical to its builder's output.
[Fact]
public void Ndata_CommittedBytes_MatchTheBuilder() =>
AssertMatchesBuilder(SparkplugGoldenPayloads.NdataFile, SparkplugGoldenPayloads.BuildNdata().ToByteArray());
///
/// Both vectors are present in the test binary's output directory — i.e. the csproj really does
/// copy Golden/**. A missing copy item is invisible in source and turns every decode test
/// into a at runtime.
///
[Fact]
public void GoldenVectors_AreCopiedToTheOutputDirectory()
{
File.Exists(SparkplugGoldenPayloads.PathTo(SparkplugGoldenPayloads.NbirthFile)).ShouldBeTrue();
File.Exists(SparkplugGoldenPayloads.PathTo(SparkplugGoldenPayloads.NdataFile)).ShouldBeTrue();
}
/// Compares a committed vector to freshly built bytes, regenerating first when asked to.
/// The vector's file name.
/// The bytes the builder produces now.
private static void AssertMatchesBuilder(string fileName, byte[] built)
{
if (Environment.GetEnvironmentVariable(RegenEnvVar) == "1")
{
// Write to BOTH the source tree (so the change can be committed) and the output directory
// (so the assertion below reads what was just written rather than a stale copied file).
var source = Path.Combine(SourceDirectory(), SparkplugGoldenPayloads.Directory, fileName);
System.IO.Directory.CreateDirectory(Path.GetDirectoryName(source)!);
File.WriteAllBytes(source, built);
var output = SparkplugGoldenPayloads.PathTo(fileName);
System.IO.Directory.CreateDirectory(Path.GetDirectoryName(output)!);
File.WriteAllBytes(output, built);
}
var path = SparkplugGoldenPayloads.PathTo(fileName);
File.Exists(path).ShouldBeTrue(
$"Golden vector '{fileName}' is missing from the output directory. If the csproj copy item is "
+ $"intact, regenerate with {RegenEnvVar}=1.");
File.ReadAllBytes(path).ShouldBe(
built,
$"Golden vector '{fileName}' no longer matches SparkplugGoldenPayloads. Either the builder or "
+ $"the vendored proto changed. If the change is intended, regenerate with {RegenEnvVar}=1 and "
+ "commit the .bin diff.");
}
/// This file's own source directory, resolved at compile time.
/// Supplied by the compiler; never pass it.
/// The directory holding the test sources.
private static string SourceDirectory([CallerFilePath] string path = "") => Path.GetDirectoryName(path)!;
}