using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.AdminUI.Uns.TagEditors;
using ZB.MOM.WW.OtOpcUa.Commons.Types;
using ZB.MOM.WW.OtOpcUa.ControlPlane.AdminOperations;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
using ZB.MOM.WW.OtOpcUa.Driver.Mqtt;
namespace ZB.MOM.WW.OtOpcUa.AdminUI.Tests.Uns;
///
/// lmxopcua#754: a Sparkplug tag's array shape is read by THREE parties — the address-space composer
/// (, which decides ValueRank/ArrayDimensions), the driver
/// (, which decides whether an array metric feeds
/// the tag) and the authoring gate ( / ,
/// which consult the driver's own parse). The driver's copy of the read lives in a Contracts assembly that cannot
/// reference Commons, so this pins it against the composer's over a corpus of edge shapes, and pins that
/// the array keys never change what authoring accepts.
///
[Trait("Category", "Unit")]
public sealed class MqttArrayShapeParityTests
{
private const string Tuple = """ "groupId":"Plant1","edgeNodeId":"EdgeA","deviceId":"Filler1","metricName":"Profile" """;
private static string B(string extraKeys) => "{" + Tuple + extraKeys + "}";
public static TheoryData Corpus() => new()
{
B(""),
B(""","isArray":true"""),
B(""","isArray":false"""),
B(""""
,"isArray":"true"
""""), // a string is NOT an array to the composer
B(""","isArray":1"""),
B(""","isArray":true,"arrayLength":8"""),
B(""","isArray":true,"arrayLength":0"""),
B(""","isArray":true,"arrayLength":-1"""),
B(""","isArray":true,"arrayLength":4294967296"""),
B(""","isArray":true,"arrayLength":2.5"""),
B(""""
,"isArray":true,"arrayLength":"8"
""""),
B(""","arrayLength":8"""), // a length without the bit is ignored
B(""","isArray":true,"dataType":"Float64","arrayLength":3"""),
};
/// The driver reads the shape exactly as the composer materialises the node.
[Theory]
[MemberData(nameof(Corpus))]
public void The_driver_reads_the_array_shape_exactly_as_the_composer_does(string blob)
{
var intent = TagConfigIntent.Parse(blob);
MqttTagDefinitionFactory.FromSparkplugTagConfig(blob, "Plant/Mqtt/Profile", out var def).ShouldBeTrue(blob);
def.IsArray.ShouldBe(intent.IsArray);
def.ArrayLength.ShouldBe(intent.ArrayLength);
}
///
/// The array keys are never a rejection cause: the node exists whatever they say, so a driver that
/// refused the tag would serve BadNodeIdUnknown on a live node. Authoring therefore accepts every one
/// of these, because the driver does — the model and the deploy-time inspector both consult its parse.
///
[Theory]
[MemberData(nameof(Corpus))]
public void Authoring_accepts_exactly_what_the_driver_serves(string blob)
{
MqttTagDefinitionFactory.FromSparkplugTagConfig(blob, "Plant/Mqtt/Profile", out _).ShouldBeTrue();
MqttTagConfigModel.FromJson(blob).Validate().ShouldBeNull();
EquipmentTagConfigInspector.Inspect(DriverTypeNames.Mqtt, blob).ShouldBeEmpty();
}
}