feat(cli): --element-type and JSON --value for List attributes

This commit is contained in:
Joseph Doherty
2026-06-16 16:18:08 -04:00
parent 0164f8a0d6
commit 85db4571b2
3 changed files with 307 additions and 19 deletions
@@ -0,0 +1,172 @@
using System.CommandLine;
using ZB.MOM.WW.ScadaBridge.CLI.Commands;
using ZB.MOM.WW.ScadaBridge.Commons.Messages.Management;
namespace ZB.MOM.WW.ScadaBridge.CLI.Tests.Commands;
/// <summary>
/// MV-11: the <c>template attribute add</c> / <c>update</c> commands must support
/// structured multi-value (List) attributes — a new <c>--element-type</c> option,
/// a JSON-array <c>--value</c>, client-side element-type validation, and
/// <see cref="AddTemplateAttributeCommand.ElementDataType"/> /
/// <see cref="UpdateTemplateAttributeCommand.ElementDataType"/> wired into the
/// payload sent to the Management API.
/// </summary>
public class TemplateAttributeListTests
{
private static readonly Option<string> Url = new("--url") { Recursive = true };
private static readonly Option<string> Username = new("--username") { Recursive = true };
private static readonly Option<string> Password = new("--password") { Recursive = true };
private static readonly Option<string> Format = CliOptions.CreateFormatOption();
private static Command AttributeGroup()
=> TemplateCommands.Build(Url, Format, Username, Password)
.Subcommands.Single(c => c.Name == "attribute");
// ---- option surface ----
[Fact]
public void AttributeAdd_HasElementTypeOption()
{
var add = AttributeGroup().Subcommands.Single(c => c.Name == "add");
Assert.Contains("--element-type", add.Options.Select(o => o.Name));
}
[Fact]
public void AttributeUpdate_HasElementTypeOption()
{
var update = AttributeGroup().Subcommands.Single(c => c.Name == "update");
Assert.Contains("--element-type", update.Options.Select(o => o.Name));
}
// ---- client-side element-type validation (both directions) ----
[Theory]
[InlineData("String")]
[InlineData("Int32")]
[InlineData("Float")]
[InlineData("Double")]
[InlineData("Boolean")]
[InlineData("DateTime")]
public void ValidateElementType_ListWithValidScalar_Ok(string elementType)
{
var ok = TemplateCommands.TryValidateElementType("List", elementType, out var error);
Assert.True(ok);
Assert.Null(error);
}
[Fact]
public void ValidateElementType_ListWithValidScalar_CaseInsensitive()
{
var ok = TemplateCommands.TryValidateElementType("List", "string", out var error);
Assert.True(ok);
Assert.Null(error);
}
[Fact]
public void ValidateElementType_ListWithoutElementType_Error()
{
var ok = TemplateCommands.TryValidateElementType("List", null, out var error);
Assert.False(ok);
Assert.NotNull(error);
}
[Fact]
public void ValidateElementType_ListWithBlankElementType_Error()
{
var ok = TemplateCommands.TryValidateElementType("List", " ", out var error);
Assert.False(ok);
Assert.NotNull(error);
}
[Fact]
public void ValidateElementType_ListWithInvalidScalar_Error()
{
var ok = TemplateCommands.TryValidateElementType("List", "List", out var error);
Assert.False(ok);
Assert.NotNull(error);
}
[Fact]
public void ValidateElementType_ListWithBinaryScalar_Error()
{
// Binary is a DataType but not a permitted List element scalar.
var ok = TemplateCommands.TryValidateElementType("List", "Binary", out var error);
Assert.False(ok);
Assert.NotNull(error);
}
[Fact]
public void ValidateElementType_ScalarWithElementType_Error()
{
var ok = TemplateCommands.TryValidateElementType("String", "Int32", out var error);
Assert.False(ok);
Assert.NotNull(error);
}
[Fact]
public void ValidateElementType_ScalarWithoutElementType_Ok()
{
var ok = TemplateCommands.TryValidateElementType("Float", null, out var error);
Assert.True(ok);
Assert.Null(error);
}
// ---- payload wiring: the raw JSON value + ElementDataType flow into the command ----
[Fact]
public void BuildAddCommand_ListAttribute_CarriesElementTypeAndRawJsonValue()
{
var cmd = TemplateCommands.BuildAddAttributeCommand(
templateId: 7,
name: "WorkOrders",
dataType: "List",
value: """["WO-1","WO-2"]""",
description: null,
dataSource: null,
isLocked: false,
elementType: "String");
Assert.Equal(7, cmd.TemplateId);
Assert.Equal("List", cmd.DataType);
Assert.Equal("String", cmd.ElementDataType);
// The CLI forwards the raw JSON string unchanged — the API/codec parses it.
Assert.Equal("""["WO-1","WO-2"]""", cmd.Value);
}
[Fact]
public void BuildUpdateCommand_ListAttribute_CarriesElementTypeAndRawJsonValue()
{
var cmd = TemplateCommands.BuildUpdateAttributeCommand(
attributeId: 42,
name: "WorkOrders",
dataType: "List",
value: """["A","B"]""",
description: null,
dataSource: null,
isLocked: false,
elementType: "Int32");
Assert.Equal(42, cmd.AttributeId);
Assert.Equal("List", cmd.DataType);
Assert.Equal("Int32", cmd.ElementDataType);
Assert.Equal("""["A","B"]""", cmd.Value);
}
[Fact]
public void BuildAddCommand_ScalarAttribute_LeavesElementTypeNull()
{
var cmd = TemplateCommands.BuildAddAttributeCommand(
templateId: 1,
name: "Speed",
dataType: "Float",
value: "0",
description: null,
dataSource: null,
isLocked: false,
elementType: null);
Assert.Null(cmd.ElementDataType);
Assert.Equal("Float", cmd.DataType);
}
}