v3 B1-WP6: migrate Core + Server test projects to greenfield schema
Fix TESTS only (production already green) so the four in-scope test projects compile + pass under the v3 dark address space: - Core.Abstractions.Tests: drop AllowedNamespaceKinds/NamespaceKindCompatibility from DriverTypeMetadata; rewrite EquipmentTagRefResolver tests to the single-func RawPath-lookup contract (no parseRef/transient cache). - Core.Tests: rewrite EquipmentNodeWalkerTests to EquipmentNamespaceContent(Areas, Lines,Equipment,VirtualTags?,ScriptedAlarms?) — folders + VirtualTags + ScriptedAlarms only; raw-tag variables dark (skipped Batch-4 placeholder). Drop retired Equipment.DriverInstanceId. - Runtime.Tests: rewrite golden corpus + TagConfigCorpusParityTests to the v3 RawPath/RawTagEntry round-trip (byte + TagConfigIntent parity; EquipmentTags dark). Salvage VirtualTag + ScriptedAlarm artifact parity to the new Compose signature. Retire the equipment-tag-materialization parity files (Array/Historize/Alias) and the equipment-device-binding DeviceHost parity to documented skipped placeholders. Skip 34 dark equipment-tag routing/value/alarm/write/discovery actor tests with a shared DarkAddressSpaceReasons constant. Re-key cluster-scoped tests to v3 UNS line->area attribution. - ControlPlane.Tests: ConfigComposer round-trips re-keyed to RawFolder/Device (no Namespaces); tag-config gate resolves driver via Device; deploy-gate collision test re-keyed to UnsEffectiveNameCollision; drop retired BadCrossClusterNamespaceBinding case.
This commit is contained in:
@@ -8,9 +8,8 @@ public sealed class DriverTypeRegistryTests
|
||||
{
|
||||
private static DriverTypeMetadata SampleMetadata(
|
||||
string typeName = "Modbus",
|
||||
NamespaceKindCompatibility allowed = NamespaceKindCompatibility.Equipment,
|
||||
DriverTier tier = DriverTier.B) =>
|
||||
new(typeName, allowed,
|
||||
new(typeName,
|
||||
DriverConfigJsonSchema: "{\"type\": \"object\"}",
|
||||
DeviceConfigJsonSchema: "{\"type\": \"object\"}",
|
||||
TagConfigJsonSchema: "{\"type\": \"object\"}",
|
||||
@@ -118,7 +117,7 @@ public sealed class DriverTypeRegistryTests
|
||||
var registry = new DriverTypeRegistry();
|
||||
registry.Register(SampleMetadata("Modbus"));
|
||||
registry.Register(SampleMetadata("S7"));
|
||||
registry.Register(SampleMetadata("Galaxy", NamespaceKindCompatibility.Equipment));
|
||||
registry.Register(SampleMetadata("Galaxy"));
|
||||
|
||||
var all = registry.All();
|
||||
|
||||
@@ -126,22 +125,6 @@ public sealed class DriverTypeRegistryTests
|
||||
all.Select(m => m.TypeName).ShouldBe(new[] { "Modbus", "S7", "Galaxy" }, ignoreOrder: true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that NamespaceKindCompatibility flags are implemented as a bitmask.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void NamespaceKindCompatibility_FlagsAreBitmask()
|
||||
{
|
||||
// Per decision #111 — driver types may be valid for multiple namespace kinds.
|
||||
var both = NamespaceKindCompatibility.Equipment | NamespaceKindCompatibility.Simulated;
|
||||
|
||||
both.HasFlag(NamespaceKindCompatibility.Equipment).ShouldBeTrue();
|
||||
both.HasFlag(NamespaceKindCompatibility.Simulated).ShouldBeTrue();
|
||||
|
||||
// A single-flag value does not carry the other flag — proves these are real bitmask bits.
|
||||
NamespaceKindCompatibility.Equipment.HasFlag(NamespaceKindCompatibility.Simulated).ShouldBeFalse();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that Get rejects empty or null type names.
|
||||
/// </summary>
|
||||
|
||||
+39
-28
@@ -4,54 +4,65 @@ using Xunit;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Core.Abstractions.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// v3: <see cref="EquipmentTagRefResolver{TDef}"/> is now a thin wrapper over the driver's
|
||||
/// authored <c>RawPath → TDef</c> lookup. The pre-v3 two-arg ctor (byName + a JSON-blob parse
|
||||
/// fallback with a transient cache) is retired — a reference is always a RawPath, and a miss is a
|
||||
/// miss (no parse attempt). These tests pin the single-func RawPath-lookup contract.
|
||||
/// </summary>
|
||||
public class EquipmentTagRefResolverTests
|
||||
{
|
||||
private sealed record Def(string Id);
|
||||
|
||||
private static EquipmentTagRefResolver<Def> Make(
|
||||
Dictionary<string, Def> byName, Func<string, Def?> parse)
|
||||
=> new(r => byName.TryGetValue(r, out var d) ? d : null, parse);
|
||||
private static EquipmentTagRefResolver<Def> Make(Dictionary<string, Def> byRawPath)
|
||||
=> new(r => byRawPath.TryGetValue(r, out var d) ? d : null);
|
||||
|
||||
[Fact]
|
||||
public void Legacy_name_resolves_via_byName()
|
||||
public void RawPath_resolves_via_lookup()
|
||||
{
|
||||
var r = Make(new() { ["Temp"] = new Def("Temp") }, _ => null);
|
||||
r.TryResolve("Temp", out var def).ShouldBeTrue();
|
||||
var r = Make(new() { ["Area/Modbus/Dev/Temp"] = new Def("Temp") });
|
||||
r.TryResolve("Area/Modbus/Dev/Temp", out var def).ShouldBeTrue();
|
||||
def!.Id.ShouldBe("Temp");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Equipment_ref_resolves_via_parse_and_is_cached()
|
||||
public void Unknown_rawPath_returns_false()
|
||||
{
|
||||
var calls = 0;
|
||||
var r = Make(new(), reference => { calls++; return reference.StartsWith("{") ? new Def(reference) : null; });
|
||||
r.TryResolve("{\"a\":1}", out var d1).ShouldBeTrue();
|
||||
r.TryResolve("{\"a\":1}", out var d2).ShouldBeTrue();
|
||||
d1!.Id.ShouldBe("{\"a\":1}");
|
||||
calls.ShouldBe(1);
|
||||
var r = Make(new() { ["Area/Modbus/Dev/Temp"] = new Def("Temp") });
|
||||
r.TryResolve("Area/Modbus/Dev/Missing", out _).ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Garbage_ref_returns_false_and_caches_negative()
|
||||
public void Lookup_is_consulted_each_call()
|
||||
{
|
||||
// v3 holds no transient parse cache — the resolver reads the driver's live table by closure,
|
||||
// so every TryResolve consults the underlying lookup (a table rebuild is immediately visible).
|
||||
var calls = 0;
|
||||
var r = Make(new(), _ => { calls++; return null; });
|
||||
r.TryResolve("nope", out _).ShouldBeFalse();
|
||||
r.TryResolve("nope", out _).ShouldBeFalse();
|
||||
calls.ShouldBe(1);
|
||||
}
|
||||
var table = new Dictionary<string, Def>();
|
||||
var r = new EquipmentTagRefResolver<Def>(reference =>
|
||||
{
|
||||
calls++;
|
||||
return table.TryGetValue(reference, out var d) ? d : null;
|
||||
});
|
||||
|
||||
[Fact]
|
||||
public void Clear_drops_transient_cache()
|
||||
{
|
||||
var calls = 0;
|
||||
var r = Make(new(), reference => { calls++; return new Def(reference); });
|
||||
r.TryResolve("{\"a\":1}", out _).ShouldBeTrue();
|
||||
r.Clear();
|
||||
r.TryResolve("{\"a\":1}", out _).ShouldBeTrue();
|
||||
r.TryResolve("Area/Dev/Temp", out _).ShouldBeFalse();
|
||||
table["Area/Dev/Temp"] = new Def("Temp");
|
||||
r.TryResolve("Area/Dev/Temp", out var def).ShouldBeTrue();
|
||||
def!.Id.ShouldBe("Temp");
|
||||
calls.ShouldBe(2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Clear_is_a_noop_and_lookup_still_works()
|
||||
{
|
||||
// Clear() is retained for call-site compatibility only — there is no transient cache to drop.
|
||||
var r = Make(new() { ["ref"] = new Def("ref") });
|
||||
r.TryResolve("ref", out _).ShouldBeTrue();
|
||||
r.Clear();
|
||||
r.TryResolve("ref", out var def).ShouldBeTrue();
|
||||
def!.Id.ShouldBe("ref");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Core.Abstractions-009: <see cref="EquipmentTagRefResolver{TDef}.TryResolve"/> carries
|
||||
/// <c>[MaybeNullWhen(false)]</c> on its <c>out</c> parameter so NRT-enabled callers do
|
||||
@@ -62,7 +73,7 @@ public class EquipmentTagRefResolverTests
|
||||
[Fact]
|
||||
public void TryResolve_false_path_sets_def_to_default()
|
||||
{
|
||||
var r = Make(new(), _ => null);
|
||||
var r = Make(new());
|
||||
var returned = r.TryResolve("no-such-ref", out var def);
|
||||
returned.ShouldBeFalse();
|
||||
// The [MaybeNullWhen(false)] attribute means callers must treat def as potentially null
|
||||
|
||||
Reference in New Issue
Block a user