3f6bfebd6d
Three additions to the script editor commands. Each one closes a real gap surfaced by the round-trip-test against \$DelmiaReceiver.ProcessRecipe. 1. `object scripts set --field <name>` — explicit text-field selection. Previously `scripts set` always wrote to <Name>.ExecuteText (via ScriptAttributeName's default). To rewrite DeclarationsText / StartupText / ShutdownText / OnScanText / OffScanText / Expression, callers had to pass the full attribute name as `--script Foo.StartupText`, which is brittle. The new `--field` flag accepts any of the seven canonical ScriptTextSuffixes and composes <script>.<field> directly. Validates against the suffix list so an unrecognised --field surfaces a friendly error rather than a downstream FindAttributeForMutation failure. Default behavior (no --field) is unchanged: ExecuteText. 2. `object scripts settings set --lock-trigger-type` — parallel to the existing --lock-trigger-period. After writing TriggerType the new flag calls SetLocked(MxLockedInMe), matching the lock pattern on the period field. Without it, --trigger-type writes the value but leaves the attribute unlocked. 3. `object scripts delete` — script-named alias for the existing extension-delete subcommand. Wraps obj.DeleteExtensionPrimitive( "ScriptExtension", scriptName) inside AtomicObjectEdit (checkout / save / checkin). Removes the burden of remembering the generic `--extension-type ScriptExtension --primitive <Name>` form. Test count delta: 61 -> 63 (+2 command-shape assertions for the new ObjectScriptsSetCommand and ObjectScriptsDeleteCommand). Live round-trip-test against \$DelmiaReceiver.ProcessRecipe: - `--field DeclarationsText` write composed `ProcessRecipe.DeclarationsText`, CheckOut/Save/CheckIn all returned OK. - `--field ExecuteText` round-trip same. - A subsequent re-read shows the original body, suggesting that IAttribute.SetValue silently no-ops for ScriptExtension text fields on this GRAccess version (or the package-export reader pulls from a different snapshot than the just-saved revision). This is upstream of the editor surface — the new flags route correctly to the same SetValue path that scripts set already used. Diagnosing the SetValue ineffectiveness for script-text fields is a separate followup that should look at IScriptExtension-specific COM interfaces (per docs/script-parsing.md:8 "Object-level scripts are less direct"). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
189 lines
7.8 KiB
C#
189 lines
7.8 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using CliFx.Attributes;
|
|
using Shouldly;
|
|
using Xunit;
|
|
using ZB.MOM.WW.GRAccess.Cli.Commands;
|
|
using ZB.MOM.WW.GRAccess.Cli.Protocol;
|
|
|
|
namespace ZB.MOM.WW.GRAccess.Cli.Tests.Commands
|
|
{
|
|
public class GRAccessSurfaceCommandTests
|
|
{
|
|
[Theory]
|
|
[InlineData(typeof(GalaxyInfoCommand), "galaxy info")]
|
|
[InlineData(typeof(GalaxyImportScriptLibraryCommand), "galaxy import-script-library")]
|
|
[InlineData(typeof(ObjectGetCommand), "object get")]
|
|
[InlineData(typeof(ObjectQueryNameCommand), "object query-name")]
|
|
[InlineData(typeof(ObjectScriptsCreateCommand), "object scripts create")]
|
|
[InlineData(typeof(ObjectScriptsSettingsSetCommand), "object scripts settings set")]
|
|
[InlineData(typeof(ObjectScriptsSetCommand), "object scripts set")]
|
|
[InlineData(typeof(ObjectScriptsDeleteCommand), "object scripts delete")]
|
|
[InlineData(typeof(TemplateDeriveCommand), "template derive")]
|
|
[InlineData(typeof(InstanceDeployCommand), "instance deploy")]
|
|
[InlineData(typeof(ObjectsExportCommand), "objects export")]
|
|
[InlineData(typeof(ToolsetListCommand), "toolset list")]
|
|
[InlineData(typeof(ScriptLibraryExportCommand), "script-library export")]
|
|
[InlineData(typeof(SecurityRolesCommand), "security roles")]
|
|
[InlineData(typeof(SettingsLocaleGetCommand), "settings locale get")]
|
|
public void Command_IsRegistered(Type commandType, string expectedName)
|
|
{
|
|
var attr = (CommandAttribute)Attribute.GetCustomAttribute(
|
|
commandType,
|
|
typeof(CommandAttribute));
|
|
|
|
attr.ShouldNotBeNull();
|
|
attr.Name.ShouldBe(expectedName);
|
|
}
|
|
|
|
[Fact]
|
|
public void PipeRequest_StoresStructuredArgs()
|
|
{
|
|
var request = PipeRequest.Execute(
|
|
"objects",
|
|
"checkout",
|
|
new Dictionary<string, object>
|
|
{
|
|
["name"] = new[] { "A", "B" },
|
|
["confirm"] = true
|
|
});
|
|
|
|
request.Args["name"].ShouldBeAssignableTo<string[]>();
|
|
((string[])request.Args["name"]).ShouldBe(new[] { "A", "B" });
|
|
request.Args["confirm"].ShouldBe(true);
|
|
}
|
|
|
|
[Fact]
|
|
public void ConfirmedCommand_IncludesConfirmationArgs()
|
|
{
|
|
var command = new ObjectCheckoutCommand
|
|
{
|
|
GalaxyName = "ZB",
|
|
ObjectName = "DEV",
|
|
Confirm = true,
|
|
ConfirmTarget = "DEV"
|
|
};
|
|
|
|
var args = command.Args();
|
|
|
|
args["confirm"].ShouldBe(true);
|
|
args["confirm-target"].ShouldBe("DEV");
|
|
args["name"].ShouldBe("DEV");
|
|
}
|
|
|
|
[Fact]
|
|
public void BulkCommand_PreservesMultipleNames()
|
|
{
|
|
var command = new ObjectsCheckoutCommand
|
|
{
|
|
GalaxyName = "ZB",
|
|
Names = new[] { "A", "B" },
|
|
Confirm = true,
|
|
ConfirmTarget = "A,B"
|
|
};
|
|
|
|
var names = ((IReadOnlyList<string>)command.Args()["name"]).ToArray();
|
|
|
|
names.ShouldBe(new[] { "A", "B" });
|
|
}
|
|
|
|
[Fact]
|
|
public void UdaCommands_DefaultToValidWritableCategory()
|
|
{
|
|
new ObjectUdaAddCommand().Args()["category"].ShouldBe("MxCategoryWriteable_USC");
|
|
new ObjectUdaUpdateCommand().Args()["category"].ShouldBe("MxCategoryWriteable_USC");
|
|
}
|
|
|
|
[Fact]
|
|
public void TemplateDelete_DefaultsToNonCascadingForceOption()
|
|
{
|
|
new TemplateDeleteCommand().Args()["force-option"].ShouldBe("dontForceTemplateDelete");
|
|
}
|
|
|
|
[Fact]
|
|
public void InstanceDelete_DefaultsToDeleteCleanupForceOption()
|
|
{
|
|
new InstanceDeleteCommand().Args()["force-option"].ShouldBe("undeployIfDeployed");
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("EForceDeleteTemplateOption", "dontForceTemplateDelete", "dontForceTemplateDelete")]
|
|
[InlineData("EForceDeleteTemplateOption", "galaxy_dontForceTemplateDelete", "dontForceTemplateDelete")]
|
|
[InlineData("MxAttributeCategory", "mxcategorywriteable_usc", "MxCategoryWriteable_USC")]
|
|
public void DispatcherEnumParser_AcceptsCaseInsensitiveNamesAndGalaxyPrefixAliases(
|
|
string enumType,
|
|
string value,
|
|
string expected)
|
|
{
|
|
var dispatcher = typeof(GalaxyInfoCommand).Assembly.GetType("ZB.MOM.WW.GRAccess.Cli.GRAccess.GRAccessCommandDispatcher");
|
|
var enumValue = dispatcher.GetMethod("EnumValue", BindingFlags.Static | BindingFlags.NonPublic, null, new[] { typeof(string), typeof(string) }, null)
|
|
.Invoke(null, new object[] { enumType, value });
|
|
|
|
enumValue.ToString().ShouldBe(expected);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("AnalogLimitAlarm", "AnalogExtension")]
|
|
[InlineData("analoglimitalarm", "AnalogExtension")]
|
|
[InlineData("HistoryExtension", "HistoryExtension")]
|
|
public void DispatcherExtensionType_NormalizesLegacyAliases(string value, string expected)
|
|
{
|
|
var dispatcher = typeof(GalaxyInfoCommand).Assembly.GetType("ZB.MOM.WW.GRAccess.Cli.GRAccess.GRAccessCommandDispatcher");
|
|
var extensionType = dispatcher.GetMethod("ExtensionType", BindingFlags.Static | BindingFlags.NonPublic)
|
|
.Invoke(null, new object[] { value });
|
|
|
|
extensionType.ShouldBe(expected);
|
|
}
|
|
|
|
[Fact]
|
|
public void DispatcherAtomicObjectEdit_UsesExpectedLifecycle()
|
|
{
|
|
var source = DispatcherSource();
|
|
var helper = source.Substring(source.IndexOf("private static string AtomicObjectEdit", StringComparison.Ordinal));
|
|
|
|
helper.IndexOf("obj.CheckOut();", StringComparison.Ordinal).ShouldBeLessThan(
|
|
helper.IndexOf("var summary = mutation(obj);", StringComparison.Ordinal));
|
|
helper.IndexOf("var summary = mutation(obj);", StringComparison.Ordinal).ShouldBeLessThan(
|
|
helper.IndexOf("obj.Save();", StringComparison.Ordinal));
|
|
helper.IndexOf("obj.Save();", StringComparison.Ordinal).ShouldBeLessThan(
|
|
helper.IndexOf("obj.CheckIn(string.Empty);", StringComparison.Ordinal));
|
|
helper.ShouldContain("obj.UndoCheckOut();");
|
|
}
|
|
|
|
[Fact]
|
|
public void DispatcherSingleObjectMutations_UseAtomicEditHelper()
|
|
{
|
|
var source = DispatcherSource();
|
|
|
|
source.ShouldContain("return AtomicObjectEdit(obj, editObj =>");
|
|
source.ShouldContain("return AtomicObjectEdit(FindSingleObject(galaxy, Kind(args), Arg(args, \"name\")), obj => ObjectScriptCreate");
|
|
source.ShouldContain("return AtomicObjectEdit(FindSingleObject(galaxy, Kind(args), Arg(args, \"name\")), obj => ObjectScriptSet");
|
|
source.ShouldContain("return AtomicObjectEdit(FindSingleObject(galaxy, Kind(args), Arg(args, \"name\")), obj => ObjectScriptSettingsSet");
|
|
}
|
|
|
|
private static string DispatcherSource()
|
|
{
|
|
var directory = new DirectoryInfo(Directory.GetCurrentDirectory());
|
|
while (directory != null)
|
|
{
|
|
var sourcePath = Path.Combine(
|
|
directory.FullName,
|
|
"src",
|
|
"ZB.MOM.WW.GRAccess.Cli",
|
|
"GRAccess",
|
|
"GRAccessCommandDispatcher.cs");
|
|
|
|
if (File.Exists(sourcePath))
|
|
return File.ReadAllText(sourcePath);
|
|
|
|
directory = directory.Parent;
|
|
}
|
|
|
|
throw new FileNotFoundException("Could not locate GRAccessCommandDispatcher.cs from the test working directory.");
|
|
}
|
|
}
|
|
}
|