using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Admin.Services;
namespace ZB.MOM.WW.OtOpcUa.Admin.Tests;
///
/// Regression for Admin-012 — admin-ui.md ("Equipment CSV import", revised after
/// adversarial review finding #4) requires no EquipmentId column: it is
/// system-derived ('EQ-' + first 12 hex chars of EquipmentUuid) and "never
/// accepted from CSV imports". Operator-supplied EquipmentId would mint duplicate
/// equipment identity on typos.
///
[Trait("Category", "Unit")]
public sealed class EquipmentCsvNoEquipmentIdColumnTests
{
[Fact]
public void RequiredColumns_does_not_include_EquipmentId()
{
EquipmentCsvImporter.RequiredColumns
.ShouldNotContain("EquipmentId",
customMessage: "Admin-012: admin-ui.md forbids an EquipmentId column on the CSV import — it is system-derived from EquipmentUuid.");
}
[Fact]
public void OptionalColumns_does_not_include_EquipmentId()
{
EquipmentCsvImporter.OptionalColumns
.ShouldNotContain("EquipmentId",
customMessage: "Admin-012: EquipmentId must not be an optional column either — it is never accepted from the CSV.");
}
[Fact]
public void EquipmentCsvRow_has_no_EquipmentId_property()
{
// The CSV row shape mirrors the accepted columns. Keeping EquipmentId on the
// row would invite the same misuse — drop it so the type system prevents
// accidental population from a future column.
var prop = typeof(EquipmentCsvRow).GetProperty("EquipmentId");
prop.ShouldBeNull("Admin-012: EquipmentCsvRow must not expose an EquipmentId — the value is derived at finalise time.");
}
[Fact]
public void Header_with_EquipmentId_column_is_rejected_as_unknown()
{
// After the fix, EquipmentId is an unknown column — the header validator must
// refuse it like any other unrecognised column so operators get an explicit
// error rather than silently importing the value.
const string csv =
"# OtOpcUaCsv v1\n" +
"ZTag,MachineCode,SAPID,EquipmentId,EquipmentUuid,Name,UnsAreaName,UnsLineName\n" +
"z-1,MC,SAP,eq,uuid,Oven,Warsaw,Line1";
var ex = Should.Throw(() => EquipmentCsvImporter.Parse(csv));
ex.Message.ShouldContain("EquipmentId",
customMessage: "Importer must reject CSVs that still carry the (now disallowed) EquipmentId column.");
}
[Fact]
public void Valid_csv_without_EquipmentId_is_accepted()
{
// The canonical header should now omit EquipmentId.
const string csv =
"# OtOpcUaCsv v1\n" +
"ZTag,MachineCode,SAPID,EquipmentUuid,Name,UnsAreaName,UnsLineName\n" +
"z-1,MC,SAP,11111111-2222-3333-4444-555555555555,Oven,Warsaw,Line1";
var result = EquipmentCsvImporter.Parse(csv);
result.AcceptedRows.Count.ShouldBe(1);
result.RejectedRows.ShouldBeEmpty();
result.AcceptedRows[0].ZTag.ShouldBe("z-1");
result.AcceptedRows[0].EquipmentUuid.ShouldBe("11111111-2222-3333-4444-555555555555");
}
}