using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Driver.AbCip.Import;
namespace ZB.MOM.WW.OtOpcUa.Driver.AbCip.Tests;
[Trait("Category", "Unit")]
public sealed class L5xParserTests
{
[Fact]
public void Controller_scope_Tag_elements_parse_with_metadata()
{
const string body = """
""";
var doc = L5xParser.Parse(new StringL5kSource(body));
doc.Tags.Count.ShouldBe(2);
doc.Tags[0].Name.ShouldBe("Motor1_Speed");
doc.Tags[0].DataType.ShouldBe("DINT");
doc.Tags[0].ExternalAccess.ShouldBe("Read/Write");
doc.Tags[0].Description.ShouldBe("Motor 1 set point");
doc.Tags[0].ProgramScope.ShouldBeNull();
doc.Tags[0].AliasFor.ShouldBeNull();
doc.Tags[1].Name.ShouldBe("Tank_Level");
doc.Tags[1].ExternalAccess.ShouldBe("Read Only");
}
[Fact]
public void Program_scope_Tag_elements_carry_program_name()
{
const string body = """
""";
var doc = L5xParser.Parse(new StringL5kSource(body));
doc.Tags.Count.ShouldBe(2);
doc.Tags.ShouldAllBe(t => t.ProgramScope == "MainProgram");
doc.Tags.Select(t => t.Name).ShouldBe(["StepIndex", "Running"]);
}
[Fact]
public void Alias_tag_carries_AliasFor_and_is_skipped_on_ingest()
{
const string body = """
""";
var doc = L5xParser.Parse(new StringL5kSource(body));
var alias = doc.Tags.Single(t => t.Name == "Aliased");
alias.AliasFor.ShouldBe("Real");
var ingestResult = new L5kIngest { DefaultDeviceHostAddress = "ab://10.0.0.1/0,1" }.Ingest(doc);
ingestResult.SkippedAliasCount.ShouldBe(1);
ingestResult.Tags.ShouldAllBe(t => t.Name != "Aliased");
}
[Fact]
public void DataType_block_collects_member_elements_and_skips_hidden_zzzz_host()
{
const string body = """
""";
var doc = L5xParser.Parse(new StringL5kSource(body));
doc.DataTypes.Count.ShouldBe(1);
var udt = doc.DataTypes[0];
udt.Name.ShouldBe("TankUDT");
udt.Members.Count.ShouldBe(2);
udt.Members.ShouldContain(m => m.Name == "Level" && m.DataType == "REAL");
udt.Members.ShouldContain(m => m.Name == "Active");
}
[Fact]
public void UDT_typed_tag_picks_up_member_layout_through_ingest()
{
const string body = """
""";
var doc = L5xParser.Parse(new StringL5kSource(body));
var result = new L5kIngest { DefaultDeviceHostAddress = "ab://10.0.0.1/0,1" }.Ingest(doc);
var tag = result.Tags.Single();
tag.Name.ShouldBe("Tank1");
tag.DataType.ShouldBe(AbCipDataType.Structure);
tag.Members.ShouldNotBeNull();
tag.Members!.Count.ShouldBe(2);
tag.Members.Select(m => m.Name).ShouldBe(["Level", "Pressure"]);
}
[Fact]
public void AOI_definition_surfaces_as_datatype_with_visible_parameters()
{
// EnableIn / EnableOut on real exports carry Hidden="true" — the parser must skip those
// so AOI-typed tags don't end up with phantom EnableIn/EnableOut members.
const string body = """
""";
var doc = L5xParser.Parse(new StringL5kSource(body));
// AOI definition should appear as a "DataType" entry alongside any UDTs.
var aoi = doc.DataTypes.Single(d => d.Name == "MyValveAoi");
aoi.Members.Count.ShouldBe(2);
aoi.Members.Select(m => m.Name).ShouldBe(["Cmd", "Status"]);
var result = new L5kIngest { DefaultDeviceHostAddress = "ab://10.0.0.1/0,1" }.Ingest(doc);
var tag = result.Tags.Single();
tag.Name.ShouldBe("Valve_001");
tag.DataType.ShouldBe(AbCipDataType.Structure);
tag.Members.ShouldNotBeNull();
tag.Members!.Select(m => m.Name).ShouldBe(["Cmd", "Status"]);
}
[Fact]
public void Empty_or_minimal_document_returns_empty_bundle_without_throwing()
{
const string body = """
""";
var doc = L5xParser.Parse(new StringL5kSource(body));
doc.Tags.Count.ShouldBe(0);
doc.DataTypes.Count.ShouldBe(0);
}
[Fact]
public void Missing_external_access_defaults_to_writable_through_ingest()
{
// L5X: ExternalAccess attribute absent → ingest treats as default (writable, not skipped).
const string body = """
""";
var doc = L5xParser.Parse(new StringL5kSource(body));
var result = new L5kIngest { DefaultDeviceHostAddress = "ab://10.0.0.1/0,1" }.Ingest(doc);
result.Tags.Single().Writable.ShouldBeTrue();
result.SkippedNoAccessCount.ShouldBe(0);
}
}