using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.AdminUI.Uns;
using ZB.MOM.WW.OtOpcUa.AdminUI.Uns.TagEditors;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
namespace ZB.MOM.WW.OtOpcUa.AdminUI.Tests.Uns;
///
/// Parity guards for the two remaining per-driver dispatch maps (deferment.md G-5, G-6).
/// Both were already testable structures — unlike the two Razor @switch blocks that needed
/// extracting first — so nothing but the tests was missing.
///
[Trait("Category", "Unit")]
public sealed class DriverDispatchMapParityTests
{
///
/// Driver types with a typed tag editor but no CSV map, so import/export silently degrades them to
/// the raw TagConfigJson fallback. Empty — every editor-backed driver now has typed columns.
/// Deliberately expressed as "has a typed editor ⇒ has typed CSV columns" rather than
/// "every driver type", because a driver with no typed editor has nothing to project into columns.
/// Tying the two maps together is what makes this catch the next omission.
///
[Fact]
public void Every_driver_with_a_typed_tag_editor_has_typed_csv_columns()
{
var missing = DriverTypeNames.All
.Where(t => TagConfigEditorMap.Resolve(t) is not null && CsvColumnMap.Resolve(t) is null)
.OrderBy(t => t, StringComparer.Ordinal)
.ToList();
missing.ShouldBeEmpty(
$"these driver types have a typed tag editor but no CsvColumnMap entry, so CSV import/export "
+ $"drops them to the raw TagConfigJson fallback: {string.Join(", ", missing)}");
}
/// A CSV map for a driver type that no longer exists is a stale entry.
[Fact]
public void Every_csv_map_targets_a_declared_driver_type()
{
var declared = DriverTypeNames.All.ToHashSet(StringComparer.OrdinalIgnoreCase);
var stale = CsvColumnMap.All
.Select(m => m.DriverType)
.Where(t => !declared.Contains(t))
.OrderBy(t => t, StringComparer.Ordinal)
.ToList();
stale.ShouldBeEmpty($"CsvColumnMap has entries for undeclared driver types: {string.Join(", ", stale)}");
}
///
/// Every driver type either produces a TYPED blob from browse-commit, or is on the documented
/// non-browsable list. The generic {"address": …} fallback opens EMPTY in a typed editor and
/// blanks the field on save — the MTConnect branch was added for exactly that, and Sql had the same
/// bug unnoticed (deferment.md G-6) because the fallback's comment claimed "browsable drivers
/// are all handled above", which stopped being true the moment SqlDriverBrowser registered.
/// Asserted in both directions on purpose. A one-way check would let a newly-browsable
/// driver be quietly added to the exclusion list instead of getting a branch. Requiring the two sets
/// to match exactly means a driver gaining or losing a browser forces this list to be revisited.
///
[Fact]
public void Browse_commit_falls_back_to_the_generic_address_key_for_exactly_the_non_browsable_drivers()
{
// Address fields as a browser supplies them; a driver whose browser states none must still produce
// something its typed editor can read from the leaf name alone.
var addressFields = new Dictionary(StringComparer.Ordinal)
{
["schema"] = "dbo",
["table"] = "Readings",
["columnName"] = "Speed",
};
var fellBack = DriverTypeNames.All
.Where(t => RawBrowseCommitMapper.BuildTagConfig(t, "Speed", addressFields) == """{"address":"Speed"}""")
.OrderBy(t => t, StringComparer.Ordinal)
.ToList();
fellBack.ShouldBe(
NonBrowsableDriverTypes.OrderBy(t => t, StringComparer.Ordinal).ToList(),
$"the set of driver types committing to the generic \"address\" key drifted. Extra entries are "
+ $"browsable drivers whose typed editor will open EMPTY and blank the field on save; missing "
+ $"entries gained a typed branch and should leave this list. Got: {string.Join(", ", fellBack)}");
}
///
/// Driver types with no browser, for which the generic address key is the correct commit.
///
/// - Modbus — register addresses are numeric coordinates, not a browsable namespace.
/// - Calculation — a pseudo-driver over other tags' RawPaths; there is no device.
///
/// Adding a type here is a claim that it cannot be browsed. If it can, give it a branch instead.
///
private static readonly string[] NonBrowsableDriverTypes =
[
DriverTypeNames.Modbus,
DriverTypeNames.Calculation,
];
}