diff --git a/libraries/MTConnect.NET-JSON-cppagent/Devices/JsonDevicesHeader.cs b/libraries/MTConnect.NET-JSON-cppagent/Devices/JsonDevicesHeader.cs index 3d2a679fd..5e770d32b 100644 --- a/libraries/MTConnect.NET-JSON-cppagent/Devices/JsonDevicesHeader.cs +++ b/libraries/MTConnect.NET-JSON-cppagent/Devices/JsonDevicesHeader.cs @@ -15,6 +15,14 @@ public class JsonDevicesHeader [JsonPropertyName("version")] public string Version { get; set; } + /// + /// Header-nested schemaVersion identifies the AGENT's + /// configured MTConnect Standard release — what the data inside + /// the document refers to. It is distinct from the top-level + /// envelope schemaVersion, which identifies the document + /// schema the producer chose to emit. The two fields are + /// populated from independent sources and are not interchangeable. + /// [JsonPropertyName("schemaVersion")] public string SchemaVersion { get; set; } diff --git a/libraries/MTConnect.NET-JSON-cppagent/Devices/JsonMTConnectDevices.cs b/libraries/MTConnect.NET-JSON-cppagent/Devices/JsonMTConnectDevices.cs index daad1d0dd..8de189687 100644 --- a/libraries/MTConnect.NET-JSON-cppagent/Devices/JsonMTConnectDevices.cs +++ b/libraries/MTConnect.NET-JSON-cppagent/Devices/JsonMTConnectDevices.cs @@ -10,6 +10,14 @@ public class JsonMTConnectDevices [JsonPropertyName("jsonVersion")] public int JsonVersion { get; set; } + /// + /// Top-level schemaVersion identifies the envelope schema + /// this DOCUMENT conforms to — the wire format the producer chose + /// to emit. It is distinct from Header.schemaVersion, which + /// identifies the AGENT's configured MTConnect Standard release + /// (what the data inside refers to). The two fields are populated + /// from independent sources and are not interchangeable. + /// [JsonPropertyName("schemaVersion")] public string SchemaVersion { get; set; } @@ -26,16 +34,16 @@ public class JsonMTConnectDevices public JsonMTConnectDevices() { JsonVersion = 2; - SchemaVersion = "2.0"; } public JsonMTConnectDevices(IDevicesResponseDocument document) { JsonVersion = 2; - SchemaVersion = "2.0"; if (document != null) { + SchemaVersion = document.Version?.ToString(); + Header = new JsonDevicesHeader(document.Header); Devices = new JsonDevices(document); diff --git a/libraries/MTConnect.NET-JSON-cppagent/Streams/JsonMTConnectStreams.cs b/libraries/MTConnect.NET-JSON-cppagent/Streams/JsonMTConnectStreams.cs index e4359e847..b63047d30 100644 --- a/libraries/MTConnect.NET-JSON-cppagent/Streams/JsonMTConnectStreams.cs +++ b/libraries/MTConnect.NET-JSON-cppagent/Streams/JsonMTConnectStreams.cs @@ -11,6 +11,14 @@ public class JsonMTConnectStreams [JsonPropertyName("jsonVersion")] public int JsonVersion { get; set; } + /// + /// Top-level schemaVersion identifies the envelope schema + /// this DOCUMENT conforms to — the wire format the producer chose + /// to emit. It is distinct from Header.schemaVersion, which + /// identifies the AGENT's configured MTConnect Standard release + /// (what the data inside refers to). The two fields are populated + /// from independent sources and are not interchangeable. + /// [JsonPropertyName("schemaVersion")] public string SchemaVersion { get; set; } @@ -21,19 +29,18 @@ public class JsonMTConnectStreams public JsonStreams Streams { get; set; } - public JsonMTConnectStreams() + public JsonMTConnectStreams() { JsonVersion = 2; - SchemaVersion = "2.0"; } public JsonMTConnectStreams(IStreamsResponseOutputDocument streamsDocument) { JsonVersion = 2; - SchemaVersion = "2.0"; if (streamsDocument != null) { + SchemaVersion = streamsDocument.Version?.ToString(); Header = new JsonStreamsHeader(streamsDocument.Header); Streams = new JsonStreams(streamsDocument); } diff --git a/tests/MTConnect.NET-JSON-cppagent-Tests/Devices/JsonDataItemPropertyCasingTests.cs b/tests/MTConnect.NET-JSON-cppagent-Tests/Devices/JsonDataItemPropertyCasingTests.cs new file mode 100644 index 000000000..606b0cd4e --- /dev/null +++ b/tests/MTConnect.NET-JSON-cppagent-Tests/Devices/JsonDataItemPropertyCasingTests.cs @@ -0,0 +1,67 @@ +// Copyright (c) 2026 TrakHound Inc., All Rights Reserved. +// TrakHound Inc. licenses this file to you under the MIT license. + +using MTConnect.Devices.Json; +using NUnit.Framework; +using System.Linq; +using System.Reflection; +using System.Text.Json.Serialization; + +namespace MTConnect.NET_JSON_cppagent_Tests.Devices +{ + /// + /// Pins the cppagent JSON wire-shape casing convention for + /// : complex object members are PascalCase, + /// scalar attribute members are camelCase. The cppagent reference + /// implementation distinguishes the two so consumers can tell at a + /// glance whether a key carries a nested object or a scalar. + /// + [TestFixture] + [Category("WireShape")] + public class JsonDataItemPropertyCasingTests + { + // CLR property name -> expected JSON key. + private static readonly (string Clr, string Json)[] PascalCaseObjects = new[] + { + ("Source", "Source"), + ("Constraints", "Constraints"), + ("Filters", "Filters"), + ("Definition", "Definition"), + ("Relationships", "Relationships"), + }; + + private static readonly (string Clr, string Json)[] CamelCaseScalars = new[] + { + ("DataItemCategory", "category"), + ("Id", "id"), + ("Type", "type"), + }; + + private static string GetJsonName(string clrPropertyName) + { + var prop = typeof(JsonDataItem).GetProperty( + clrPropertyName, + BindingFlags.Public | BindingFlags.Instance); + Assert.That(prop, Is.Not.Null, + $"Property {clrPropertyName} must exist on JsonDataItem."); + var attribute = prop!.GetCustomAttribute(); + Assert.That(attribute, Is.Not.Null, + $"Property {clrPropertyName} must carry a [JsonPropertyName] attribute."); + return attribute!.Name; + } + + [TestCaseSource(nameof(PascalCaseObjects))] + public void Complex_object_property_uses_PascalCase_json_key((string Clr, string Json) entry) + { + Assert.That(GetJsonName(entry.Clr), Is.EqualTo(entry.Json), + "Complex object members on JsonDataItem must remain PascalCase to match the cppagent JSON wire shape."); + } + + [TestCaseSource(nameof(CamelCaseScalars))] + public void Scalar_attribute_property_uses_camelCase_json_key((string Clr, string Json) entry) + { + Assert.That(GetJsonName(entry.Clr), Is.EqualTo(entry.Json), + "Scalar attribute members on JsonDataItem must remain camelCase to match the cppagent JSON wire shape."); + } + } +} diff --git a/tests/MTConnect.NET-JSON-cppagent-Tests/Devices/JsonMTConnectDevicesSchemaVersionTests.cs b/tests/MTConnect.NET-JSON-cppagent-Tests/Devices/JsonMTConnectDevicesSchemaVersionTests.cs new file mode 100644 index 000000000..61eb0d636 --- /dev/null +++ b/tests/MTConnect.NET-JSON-cppagent-Tests/Devices/JsonMTConnectDevicesSchemaVersionTests.cs @@ -0,0 +1,34 @@ +// Copyright (c) 2026 TrakHound Inc., All Rights Reserved. +// TrakHound Inc. licenses this file to you under the MIT license. + +using MTConnect.NET_JSON_cppagent_Tests.TestHelpers; +using NUnit.Framework; +using System; + +namespace MTConnect.NET_JSON_cppagent_Tests.Devices +{ + /// + /// Asserts that the cppagent JSON-MQTT Devices envelope emits the + /// configured MTConnect release as schemaVersion instead of + /// the hardcoded "2.0" literal. + /// + /// Pre-fix: every case fails with Expected "<version>" / But was "2.0". + /// Post-fix: every case passes; the wire output matches cppagent's + /// two-segment format (e.g. "2.5" for v2.5). + /// + [TestFixture] + [Category("SchemaVersionFromConfiguration")] + public class JsonMTConnectDevicesSchemaVersionTests + { + [TestCaseSource(typeof(VersionMatrix), nameof(VersionMatrix.All))] + public void Devices_envelope_schemaVersion_equals_configured_release(Version configured) + { + var envelope = EnvelopeFixtures.BuildDevicesEnvelope(configured); + + Assert.That( + envelope.SchemaVersion, + Is.EqualTo(configured.ToString()), + "Devices.schemaVersion must mirror AgentConfiguration.DefaultVersion (issue #128)."); + } + } +} diff --git a/tests/MTConnect.NET-JSON-cppagent-Tests/Regressions/Issue128_HardcodedLiteralGuardTests.cs b/tests/MTConnect.NET-JSON-cppagent-Tests/Regressions/Issue128_HardcodedLiteralGuardTests.cs new file mode 100644 index 000000000..d8d461c96 --- /dev/null +++ b/tests/MTConnect.NET-JSON-cppagent-Tests/Regressions/Issue128_HardcodedLiteralGuardTests.cs @@ -0,0 +1,50 @@ +// Copyright (c) 2026 TrakHound Inc., All Rights Reserved. +// TrakHound Inc. licenses this file to you under the MIT license. + +using MTConnect.NET_JSON_cppagent_Tests.TestHelpers; +using NUnit.Framework; +using System.IO; +using System.Text.RegularExpressions; + +namespace MTConnect.NET_JSON_cppagent_Tests.Regressions +{ + /// + /// Guard test that walks the JSON-cppagent library source tree and + /// fails if either touched envelope file re-introduces a hardcoded + /// SchemaVersion = "<literal>" assignment. Catches + /// copy-paste regressions even when the parametric matrix would + /// stay green (e.g. someone hardcodes "2.5" and ships it). + /// + [TestFixture] + public class Issue128_HardcodedLiteralGuardTests + { + // SchemaVersion = ""; — string-literal assignment. + private static readonly Regex HardcodedSchemaVersion = + new(@"SchemaVersion\s*=\s*""[^""]+""\s*;", RegexOptions.Compiled); + + [TestCase("Streams/JsonMTConnectStreams.cs")] + [TestCase("Devices/JsonMTConnectDevices.cs")] + public void Source_file_must_not_hardcode_schemaVersion_literal(string relativePath) + { + var librarySourceDir = LocateLibrarySourceDir(); + var fullPath = Path.Combine(librarySourceDir, relativePath); + + Assert.That(File.Exists(fullPath), Is.True, + $"expected to find library source at {fullPath} (test must run from a checked-out repo)"); + + var source = File.ReadAllText(fullPath); + var match = HardcodedSchemaVersion.Match(source); + + Assert.That(match.Success, Is.False, + $"{relativePath} contains a hardcoded `SchemaVersion = \"...\"` literal: '{match.Value}'. " + + "Issue #128 forbids re-introducing the hardcode — derive the value from the response document."); + } + + private static string LocateLibrarySourceDir() + { + // Test binary lives at .../tests/MTConnect.NET-JSON-cppagent-Tests/bin/Debug/net8.0/. + // Find the repo root via the shared sentinel walk, then descend into the library. + return Path.Combine(RepoRootLocator.LocateRoot(), "libraries", "MTConnect.NET-JSON-cppagent"); + } + } +} diff --git a/tests/MTConnect.NET-JSON-cppagent-Tests/Regressions/Issue128_SchemaVersionConfiguredTests.cs b/tests/MTConnect.NET-JSON-cppagent-Tests/Regressions/Issue128_SchemaVersionConfiguredTests.cs new file mode 100644 index 000000000..365ff4e84 --- /dev/null +++ b/tests/MTConnect.NET-JSON-cppagent-Tests/Regressions/Issue128_SchemaVersionConfiguredTests.cs @@ -0,0 +1,36 @@ +// Copyright (c) 2026 TrakHound Inc., All Rights Reserved. +// TrakHound Inc. licenses this file to you under the MIT license. + +using MTConnect.NET_JSON_cppagent_Tests.TestHelpers; +using NUnit.Framework; +using System; + +namespace MTConnect.NET_JSON_cppagent_Tests.Regressions +{ + /// + /// Pinned regression for + /// issue #128. + /// + /// JSON-cppagent envelopes (MTConnectStreams + MTConnectDevices) + /// MUST emit the configured MTConnect release as schemaVersion; + /// the pre-fix code stamped a literal "2.0" regardless of + /// AgentConfiguration.DefaultVersion. + /// + [TestFixture] + public class Issue128_SchemaVersionConfiguredTests + { + [TestCaseSource(typeof(VersionMatrix), nameof(VersionMatrix.All))] + public void Streams_schemaVersion_equals_configured(Version configured) + { + var envelope = EnvelopeFixtures.BuildStreamsEnvelope(configured); + Assert.That(envelope.SchemaVersion, Is.EqualTo(configured.ToString())); + } + + [TestCaseSource(typeof(VersionMatrix), nameof(VersionMatrix.All))] + public void Devices_schemaVersion_equals_configured(Version configured) + { + var envelope = EnvelopeFixtures.BuildDevicesEnvelope(configured); + Assert.That(envelope.SchemaVersion, Is.EqualTo(configured.ToString())); + } + } +} diff --git a/tests/MTConnect.NET-JSON-cppagent-Tests/Regressions/SchemaVersionFieldsCoexistTests.cs b/tests/MTConnect.NET-JSON-cppagent-Tests/Regressions/SchemaVersionFieldsCoexistTests.cs new file mode 100644 index 000000000..fee1d490c --- /dev/null +++ b/tests/MTConnect.NET-JSON-cppagent-Tests/Regressions/SchemaVersionFieldsCoexistTests.cs @@ -0,0 +1,155 @@ +// Copyright (c) 2026 TrakHound Inc., All Rights Reserved. +// TrakHound Inc. licenses this file to you under the MIT license. + +using System.IO; +using System.Reflection; +using System.Text.Json.Serialization; +using MTConnect.Devices.Json; +using MTConnect.NET_JSON_cppagent_Tests.TestHelpers; +using MTConnect.Streams.Json; +using NUnit.Framework; + +namespace MTConnect.NET_JSON_cppagent_Tests.Regressions +{ + /// + /// Pins the envelope-vs-Header schemaVersion contract for the + /// cppagent JSON wire format. Both fields must exist independently + /// and continue to be wired through their own + /// : + /// + /// - JsonMTConnectStreams.SchemaVersion -> JSON key "schemaVersion" at envelope root + /// - JsonMTConnectDevices.SchemaVersion -> JSON key "schemaVersion" at envelope root + /// - JsonDevicesHeader.SchemaVersion -> JSON key "schemaVersion" nested inside Header + /// + /// The two SchemaVersion fields are populated from independent + /// sources and are NOT interchangeable — the envelope field + /// identifies the document schema the producer emitted while the + /// Header field identifies the agent's configured MTConnect + /// Standard release. Removing either field, or repointing one to + /// the other's source, would silently change the wire shape and + /// corrupt downstream consumers. + /// + /// The fixture also pins that each field carries an XML doc + /// comment that explains the envelope-vs-Header semantics so + /// future maintainers cannot accidentally collapse the two. + /// XML doc presence is enforced by reading the source file + /// (parsed XML doc files are not deployed alongside the assembly + /// in this repo). + /// + /// Sources: + /// - cppagent JSON envelope: https://github.com/mtconnect/cppagent + /// v2.7.0.7 reference printer emits both fields independently. + /// - Issue: https://github.com/TrakHound/MTConnect.NET/issues/128 + /// + [TestFixture] + [Category("WireShape")] + public class SchemaVersionFieldsCoexistTests + { + [TestCase(typeof(JsonMTConnectStreams), "envelope")] + [TestCase(typeof(JsonMTConnectDevices), "envelope")] + [TestCase(typeof(JsonDevicesHeader), "Header")] + public void SchemaVersion_property_exists_with_camelCase_json_key( + System.Type carrier, string surface) + { + var prop = carrier.GetProperty( + "SchemaVersion", BindingFlags.Public | BindingFlags.Instance); + + Assert.That(prop, Is.Not.Null, + $"`{carrier.Name}` ({surface}) must expose a `SchemaVersion` property; " + + "removing it silently regresses the cppagent JSON wire shape."); + + var attribute = prop!.GetCustomAttribute(); + Assert.That(attribute, Is.Not.Null, + $"`{carrier.Name}.SchemaVersion` must carry a [JsonPropertyName] attribute; " + + "the JSON key cannot be inferred from the property name alone."); + Assert.That(attribute!.Name, Is.EqualTo("schemaVersion"), + $"`{carrier.Name}.SchemaVersion` must serialize as the camelCase JSON key " + + $"`schemaVersion` (the cppagent wire-shape convention for scalar attributes)."); + } + + [Test] + public void Streams_envelope_and_devices_envelope_carry_independent_SchemaVersion_fields() + { + // Both envelopes have their own SchemaVersion. They are NOT + // shared via inheritance or composition, so a future refactor + // that consolidates them would also need to update this pin. + var streamsProp = typeof(JsonMTConnectStreams).GetProperty( + "SchemaVersion", BindingFlags.Public | BindingFlags.Instance); + var devicesProp = typeof(JsonMTConnectDevices).GetProperty( + "SchemaVersion", BindingFlags.Public | BindingFlags.Instance); + + Assert.That(streamsProp, Is.Not.Null); + Assert.That(devicesProp, Is.Not.Null); + Assert.That(streamsProp!.DeclaringType, Is.EqualTo(typeof(JsonMTConnectStreams)), + "JsonMTConnectStreams.SchemaVersion must be declared on the Streams envelope itself, " + + "not inherited from a shared base — the field is wired from streamsDocument.Version."); + Assert.That(devicesProp!.DeclaringType, Is.EqualTo(typeof(JsonMTConnectDevices)), + "JsonMTConnectDevices.SchemaVersion must be declared on the Devices envelope itself, " + + "not inherited from a shared base — the field is wired from document.Version."); + } + + [Test] + public void Devices_envelope_SchemaVersion_distinct_from_Header_SchemaVersion() + { + // The Devices envelope has its own SchemaVersion AND nests a + // Header which also has its own SchemaVersion. Both must + // coexist — collapsing them would conflate "what wire format + // did the producer emit" with "what Standard release does the + // agent run". + var envelopeProp = typeof(JsonMTConnectDevices).GetProperty( + "SchemaVersion", BindingFlags.Public | BindingFlags.Instance); + var headerProp = typeof(JsonDevicesHeader).GetProperty( + "SchemaVersion", BindingFlags.Public | BindingFlags.Instance); + + Assert.That(envelopeProp, Is.Not.Null); + Assert.That(headerProp, Is.Not.Null); + Assert.That(envelopeProp!.DeclaringType, Is.Not.EqualTo(headerProp!.DeclaringType), + "Envelope and Header SchemaVersion fields must live on distinct types so " + + "they can be populated from independent sources."); + } + + // The XML doc presence guard. Reads the committed source files + // (XML doc XML output is not deployed) so a future maintainer + // cannot delete the doc comments without tripping a guard. + [TestCase( + "libraries/MTConnect.NET-JSON-cppagent/Streams/JsonMTConnectStreams.cs", + "envelope")] + [TestCase( + "libraries/MTConnect.NET-JSON-cppagent/Devices/JsonMTConnectDevices.cs", + "envelope")] + [TestCase( + "libraries/MTConnect.NET-JSON-cppagent/Devices/JsonDevicesHeader.cs", + "Header")] + public void SchemaVersion_property_carries_envelope_vs_Header_xml_doc( + string relativeSourcePath, string surface) + { + var path = Path.Combine(RepoRootLocator.LocateRoot(), relativeSourcePath); + Assert.That(File.Exists(path), Is.True, $"Expected source file at '{path}'."); + + var text = File.ReadAllText(path); + + // Locate the SchemaVersion property and walk back to the doc + // comment that immediately precedes it. The doc must contain + // the words "envelope" AND "Header" so the contrast between + // the two surfaces stays explicit. + var anchor = text.IndexOf("public string SchemaVersion", System.StringComparison.Ordinal); + Assert.That(anchor, Is.GreaterThan(0), + $"`{relativeSourcePath}` must declare `public string SchemaVersion`."); + + // Look at the 600 chars preceding the property declaration — + // doc comments are bounded by `/// ` tags. + var windowStart = System.Math.Max(0, anchor - 800); + var window = text.Substring(windowStart, anchor - windowStart); + + Assert.That(window, Does.Contain("///"), + $"`{relativeSourcePath}` ({surface}): the `SchemaVersion` property must " + + "carry an XML doc comment explaining the envelope-vs-Header semantics."); + Assert.That(window.ToLowerInvariant(), Does.Contain("envelope"), + $"`{relativeSourcePath}` ({surface}): SchemaVersion XML doc must mention " + + "the word \"envelope\" so the contrast with the Header field is explicit."); + Assert.That(window, Does.Contain("Header"), + $"`{relativeSourcePath}` ({surface}): SchemaVersion XML doc must mention " + + "the word \"Header\" so the contrast with the envelope field is explicit."); + } + } +} diff --git a/tests/MTConnect.NET-JSON-cppagent-Tests/Streams/JsonMTConnectStreamsSchemaVersionTests.cs b/tests/MTConnect.NET-JSON-cppagent-Tests/Streams/JsonMTConnectStreamsSchemaVersionTests.cs new file mode 100644 index 000000000..87e2b4577 --- /dev/null +++ b/tests/MTConnect.NET-JSON-cppagent-Tests/Streams/JsonMTConnectStreamsSchemaVersionTests.cs @@ -0,0 +1,34 @@ +// Copyright (c) 2026 TrakHound Inc., All Rights Reserved. +// TrakHound Inc. licenses this file to you under the MIT license. + +using MTConnect.NET_JSON_cppagent_Tests.TestHelpers; +using NUnit.Framework; +using System; + +namespace MTConnect.NET_JSON_cppagent_Tests.Streams +{ + /// + /// Asserts that the cppagent JSON-MQTT Streams envelope emits the + /// configured MTConnect release as schemaVersion instead of + /// the hardcoded "2.0" literal. + /// + /// Pre-fix: every case fails with Expected "<version>" / But was "2.0". + /// Post-fix: every case passes; the wire output matches cppagent's + /// two-segment format (e.g. "2.5" for v2.5). + /// + [TestFixture] + [Category("SchemaVersionFromConfiguration")] + public class JsonMTConnectStreamsSchemaVersionTests + { + [TestCaseSource(typeof(VersionMatrix), nameof(VersionMatrix.All))] + public void Streams_envelope_schemaVersion_equals_configured_release(Version configured) + { + var envelope = EnvelopeFixtures.BuildStreamsEnvelope(configured); + + Assert.That( + envelope.SchemaVersion, + Is.EqualTo(configured.ToString()), + "Streams.schemaVersion must mirror AgentConfiguration.DefaultVersion (issue #128)."); + } + } +} diff --git a/tests/MTConnect.NET-JSON-cppagent-Tests/TestHelpers/EnvelopeFixtures.cs b/tests/MTConnect.NET-JSON-cppagent-Tests/TestHelpers/EnvelopeFixtures.cs new file mode 100644 index 000000000..9db8c56fa --- /dev/null +++ b/tests/MTConnect.NET-JSON-cppagent-Tests/TestHelpers/EnvelopeFixtures.cs @@ -0,0 +1,53 @@ +// Copyright (c) 2026 TrakHound Inc., All Rights Reserved. +// TrakHound Inc. licenses this file to you under the MIT license. + +using MTConnect.Devices; +using MTConnect.Devices.Json; +using MTConnect.Headers; +using MTConnect.Streams.Json; +using MTConnect.Streams.Output; +using System; + +namespace MTConnect.NET_JSON_cppagent_Tests.TestHelpers +{ + /// + /// Builds minimal Streams / Devices response documents tagged with + /// the supplied configured version, then runs them through the + /// JSON-cppagent envelope ctor under test. + /// + internal static class EnvelopeFixtures + { + public static JsonMTConnectStreams BuildStreamsEnvelope(Version configured) + { + var doc = new StreamsResponseOutputDocument + { + Header = new MTConnectStreamsHeader + { + InstanceId = 1, + Version = configured.ToString(), + Sender = "test", + }, + Streams = Array.Empty(), + Version = configured, + }; + + return new JsonMTConnectStreams(doc); + } + + public static JsonMTConnectDevices BuildDevicesEnvelope(Version configured) + { + var doc = new DevicesResponseDocument + { + Header = new MTConnectDevicesHeader + { + InstanceId = 1, + Version = configured.ToString(), + Sender = "test", + }, + Version = configured, + }; + + return new JsonMTConnectDevices(doc); + } + } +} diff --git a/tests/MTConnect.NET-JSON-cppagent-Tests/TestHelpers/VersionMatrix.cs b/tests/MTConnect.NET-JSON-cppagent-Tests/TestHelpers/VersionMatrix.cs new file mode 100644 index 000000000..588a5ba41 --- /dev/null +++ b/tests/MTConnect.NET-JSON-cppagent-Tests/TestHelpers/VersionMatrix.cs @@ -0,0 +1,49 @@ +// Copyright (c) 2026 TrakHound Inc., All Rights Reserved. +// TrakHound Inc. licenses this file to you under the MIT license. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace MTConnect.NET_JSON_cppagent_Tests.TestHelpers +{ + /// + /// Reflects every public static readonly Version field on + /// MTConnectVersions so the parametric test cases stay in + /// lock-step with the library's declared release matrix. + /// + internal static class VersionMatrix + { + public static IEnumerable All => Versions().Select(v => new TestCaseDataWrapper(v)); + + public static IEnumerable Versions() + { + var versionsType = typeof(MTConnectVersions); + var fields = versionsType.GetFields(BindingFlags.Public | BindingFlags.Static) + .Where(f => f.FieldType == typeof(Version)); + + foreach (var f in fields) + { + var value = (Version?)f.GetValue(null); + if (value != null) + { + yield return value; + } + } + } + } + + /// + /// Wraps a in NUnit's TestCaseData so the + /// failure / success message names the version. + /// + internal class TestCaseDataWrapper : NUnit.Framework.TestCaseData + { + public TestCaseDataWrapper(Version version) : base(version) + { + SetArgDisplayNames(version.ToString()); + } + } +}