Skip to content

Commit 46bcdc8

Browse files
committed
test(xml-tests): widen IConfiguration.VendorExtensions coverage on interface + mixed + namespace paths
Add four new assertions to the vendor-extension round-trip pinning surface: - Interface contract — IConfiguration.VendorExtensions getter reflects the value set through the concrete Configuration.VendorExtensions setter, pinning the polymorphic projection. - Mixed content — a Configuration carrying a standard Motion child AND a vendor extension round-trips through write + read with both slots populated. Exercises the branch of XmlConfiguration.ToConfiguration where strongly-typed children and vendor-namespaced children coexist. - Nested attribute preservation — extensions with attributes on both the root element AND nested descendants round-trip verbatim through the WriteRaw / XElement.Parse(LoadOptions.PreserveWhitespace) pipeline. - Distinct vendor namespaces — two extensions from different vendor namespaces keep their bindings and are distinguished by fully-qualified XName, not local name alone. Claude-Session: https://claude.ai/code/session_0162RfaA55VT8NX6QfU7RUVo
1 parent 2892a57 commit 46bcdc8

1 file changed

Lines changed: 145 additions & 0 deletions

File tree

tests/MTConnect.NET-XML-Tests/Devices/Configurations/ConfigurationVendorExtensionsRoundTripTests.cs

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,5 +313,150 @@ public void Write_emits_no_extension_when_VendorExtensions_is_all_nulls()
313313
xml,
314314
Is.EqualTo("<Configuration />").Or.EqualTo("<Configuration></Configuration>"));
315315
}
316+
317+
// ---------------- interface contract ----------------
318+
319+
/// <summary>The <see cref="IConfiguration.VendorExtensions"/> interface
320+
/// getter reflects the value set through the concrete
321+
/// <see cref="Configuration.VendorExtensions"/> setter — the
322+
/// polymorphic surface projects the concrete slot without a
323+
/// separate backing field.</summary>
324+
[Test]
325+
public void IConfiguration_VendorExtensions_getter_reflects_concrete_setter()
326+
{
327+
var payload = new[]
328+
{
329+
XElement.Parse("<v:V xmlns:v=\"urn:v\">payload</v:V>")
330+
};
331+
IConfiguration configuration = new Configuration
332+
{
333+
VendorExtensions = payload
334+
};
335+
336+
Assert.That(configuration.VendorExtensions, Is.SameAs(payload));
337+
}
338+
339+
// ---------------- mixed content ----------------
340+
341+
/// <summary>A <c>Configuration</c> that carries a standard
342+
/// <c>Motion</c> child AND a vendor extension round-trips through
343+
/// write + read with both slots populated. Pins the branch of
344+
/// <see cref="XmlConfiguration.ToConfiguration"/> where standard
345+
/// children AND vendor-namespaced children coexist inside the
346+
/// same envelope.</summary>
347+
[Test]
348+
public void Round_trip_preserves_standard_child_and_vendor_extension_together()
349+
{
350+
var original = new Configuration
351+
{
352+
Motion = new Motion
353+
{
354+
Id = "m1",
355+
Type = MotionType.PRISMATIC,
356+
Actuation = MotionActuationType.DIRECT,
357+
Axis = new Axis { Value = "1 2 3" }
358+
},
359+
VendorExtensions = new[]
360+
{
361+
XElement.Parse(
362+
"<vendor:Custom xmlns:vendor=\"urn:vendor:mtconnect\" "
363+
+ "id=\"custom-1\">"
364+
+ "<Payload>ping</Payload>"
365+
+ "</vendor:Custom>")
366+
}
367+
};
368+
369+
var xml = XmlRoundTripHelper.Write(w =>
370+
XmlConfiguration.WriteXml(w, original, outputComments: false));
371+
var wire = XmlRoundTripHelper.Read<XmlConfiguration>(xml);
372+
var round = wire.ToConfiguration();
373+
374+
Assert.That(round.Motion, Is.Not.Null);
375+
Assert.That(round.Motion!.Id, Is.EqualTo("m1"));
376+
377+
Assert.That(round.VendorExtensions, Is.Not.Null);
378+
var extensions = round.VendorExtensions.ToList();
379+
Assert.That(extensions, Has.Count.EqualTo(1));
380+
Assert.That(extensions[0].Name.LocalName, Is.EqualTo("Custom"));
381+
Assert.That(extensions[0].Attribute("id")?.Value, Is.EqualTo("custom-1"));
382+
Assert.That(extensions[0].Element("Payload")?.Value, Is.EqualTo("ping"));
383+
}
384+
385+
// ---------------- attribute + text preservation ----------------
386+
387+
/// <summary>Vendor extensions with attributes on both the root element
388+
/// AND nested descendants round-trip verbatim — the <c>WriteRaw</c>
389+
/// path on the write side and <c>XElement.Parse</c> with
390+
/// <see cref="LoadOptions.PreserveWhitespace"/> on the read side
391+
/// preserve the authored structure.</summary>
392+
[Test]
393+
public void Round_trip_preserves_nested_attributes_verbatim()
394+
{
395+
var original = new Configuration
396+
{
397+
VendorExtensions = new[]
398+
{
399+
XElement.Parse(
400+
"<vendor:Ext xmlns:vendor=\"urn:vendor\" rootAttr=\"1\">"
401+
+ "<Child childAttr=\"A\" order=\"first\">alpha</Child>"
402+
+ "<Child childAttr=\"B\" order=\"second\">beta</Child>"
403+
+ "</vendor:Ext>")
404+
}
405+
};
406+
407+
var xml = XmlRoundTripHelper.Write(w =>
408+
XmlConfiguration.WriteXml(w, original, outputComments: false));
409+
var wire = XmlRoundTripHelper.Read<XmlConfiguration>(xml);
410+
var round = wire.ToConfiguration();
411+
412+
var extensions = round.VendorExtensions.ToList();
413+
Assert.That(extensions, Has.Count.EqualTo(1));
414+
var ext = extensions[0];
415+
416+
Assert.That(ext.Attribute("rootAttr")?.Value, Is.EqualTo("1"));
417+
var children = ext.Elements("Child").ToList();
418+
Assert.That(children, Has.Count.EqualTo(2));
419+
Assert.That(children[0].Attribute("childAttr")?.Value, Is.EqualTo("A"));
420+
Assert.That(children[0].Attribute("order")?.Value, Is.EqualTo("first"));
421+
Assert.That(children[0].Value, Is.EqualTo("alpha"));
422+
Assert.That(children[1].Attribute("childAttr")?.Value, Is.EqualTo("B"));
423+
Assert.That(children[1].Attribute("order")?.Value, Is.EqualTo("second"));
424+
Assert.That(children[1].Value, Is.EqualTo("beta"));
425+
}
426+
427+
// ---------------- multiple vendors, distinct namespaces ----------------
428+
429+
/// <summary>Two vendor extensions from distinct vendor namespaces
430+
/// round-trip independently — each keeps its own namespace binding,
431+
/// and the deserialiser distinguishes them by fully-qualified
432+
/// <see cref="XName"/> rather than by local name alone.</summary>
433+
[Test]
434+
public void Round_trip_preserves_distinct_vendor_namespaces()
435+
{
436+
var original = new Configuration
437+
{
438+
VendorExtensions = new[]
439+
{
440+
XElement.Parse(
441+
"<v1:Ext xmlns:v1=\"urn:vendor-one\">one</v1:Ext>"),
442+
XElement.Parse(
443+
"<v2:Ext xmlns:v2=\"urn:vendor-two\">two</v2:Ext>")
444+
}
445+
};
446+
447+
var xml = XmlRoundTripHelper.Write(w =>
448+
XmlConfiguration.WriteXml(w, original, outputComments: false));
449+
var wire = XmlRoundTripHelper.Read<XmlConfiguration>(xml);
450+
var round = wire.ToConfiguration();
451+
452+
var extensions = round.VendorExtensions.ToList();
453+
Assert.That(extensions, Has.Count.EqualTo(2));
454+
Assert.That(extensions[0].Name.NamespaceName, Is.EqualTo("urn:vendor-one"));
455+
Assert.That(extensions[0].Name.LocalName, Is.EqualTo("Ext"));
456+
Assert.That(extensions[0].Value, Is.EqualTo("one"));
457+
Assert.That(extensions[1].Name.NamespaceName, Is.EqualTo("urn:vendor-two"));
458+
Assert.That(extensions[1].Name.LocalName, Is.EqualTo("Ext"));
459+
Assert.That(extensions[1].Value, Is.EqualTo("two"));
460+
}
316461
}
317462
}

0 commit comments

Comments
 (0)