using System.Collections.Generic; using System.IO; using System.IO.Compression; using System.Linq; using System.Text; using Newtonsoft.Json.Linq; using Shouldly; using Xunit; using ZB.MOM.WW.GRAccess.Cli.GRAccess; using ZB.MOM.WW.GRAccess.Cli.Infrastructure; namespace ZB.MOM.WW.GRAccess.Cli.Tests.Commands { public class LlmIntegrationCommandTests { [Theory] [InlineData("capabilities", false, "")] [InlineData("object snapshot", false, "")] [InlineData("object lineage", false, "")] [InlineData("object children", false, "")] [InlineData("object attribute value get", false, "")] [InlineData("object attribute value set", true, "name")] [InlineData("object scripts list", false, "")] [InlineData("object scripts set", true, "name")] [InlineData("object scripts create", true, "name")] [InlineData("object scripts settings set", true, "name")] [InlineData("area create", true, "template")] [InlineData("engine create", true, "template")] [InlineData("instance assign-area", true, "name")] [InlineData("io assign", true, "name")] [InlineData("batch", false, "")] [InlineData("validate", false, "")] public void CapabilityRegistry_IncludesLlmCommands(string commandName, bool mutates, string confirmTarget) { var command = CommandCapabilityRegistry.Find(commandName); command.ShouldNotBeNull(); command.Mutates.ShouldBe(mutates); command.ConfirmTarget.ShouldBe(confirmTarget); command.SupportsLlmJson.ShouldBeTrue(); } [Fact] public void CapabilityValidation_RejectsMissingMutationConfirmation() { var result = CommandCapabilityRegistry.Validate( "object attribute value set", new Dictionary { ["galaxy"] = "ZB", ["name"] = "TestMachine", ["attribute"] = "Description", ["value"] = "Updated" }, requireMutationConfirmation: true); result.Valid.ShouldBeFalse(); result.Errors.ShouldContain("Missing required confirm=true."); result.Errors.ShouldContain("confirm-target must be 'TestMachine'."); } [Fact] public void CapabilityValidation_AcceptsConfirmedMutation() { var result = CommandCapabilityRegistry.Validate( "object attribute value set", new Dictionary { ["galaxy"] = "ZB", ["name"] = "TestMachine", ["attribute"] = "Description", ["value"] = "Updated", ["confirm"] = true, ["confirm-target"] = "TestMachine" }, requireMutationConfirmation: true); result.Valid.ShouldBeTrue(); } [Fact] public void LlmResponse_EnvelopeSerializesSuccessAndUnavailable() { var json = LlmResponse.Ok( "object snapshot", "ZB", "TestMachine", new { name = "TestMachine" }, unavailable: new[] { new LlmUnavailableField { Field = "scripts", Reason = "not exposed" } }); var token = JObject.Parse(json); token["success"]?.Value().ShouldBeTrue(); token["command"]?.Value().ShouldBe("object snapshot"); token["data"]?["name"]?.Value().ShouldBe("TestMachine"); token["unavailable"]?.Count().ShouldBe(1); } [Fact] public void SplitCommandName_UsesRegistrySubcommand() { var split = CommandCapabilityRegistry.SplitCommandName("object attribute value get"); split.Command.ShouldBe("object"); split.Subcommand.ShouldBe("attribute-value-get"); } [Fact] public void PackageSnapshotParser_ExtractsLineageAttributesAndScripts() { var path = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName() + ".aaPKG"); try { File.WriteAllText(path, @" Lineage: $gMachine -> $TestMachine AttributeValue Name=""Description"" DataType=""MxString"" Value=""Test machine template"" ScriptBody Name=""UpdateTestChangingInt.ExecuteText""> me.TestChangingInt = me.TestChangingInt + 1; "); var snapshot = PackageSnapshotParser.Parse(path); snapshot.PackageFallbackUsed.ShouldBeTrue(); snapshot.Lineage.ShouldBe(new[] { "$gMachine", "$TestMachine" }); snapshot.AttributeValues.Single().Name.ShouldBe("Description"); snapshot.AttributeValues.Single().Value.ShouldBe("Test machine template"); snapshot.ScriptBodies.Single().Name.ShouldBe("UpdateTestChangingInt.ExecuteText"); snapshot.ScriptBodies.Single().Body.ShouldContain("TestChangingInt"); } finally { if (File.Exists(path)) File.Delete(path); } } [Fact] public void PackageSnapshotParser_ExtractsNestedBinaryScriptExtensionBody() { var path = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName() + ".aaPKG"); try { var innerBytes = Encoding.Unicode.GetBytes(string.Join("\n", new[] { "UpdateTestChangingInt", "UpdateTestChangingInt_ScriptExtension", "Me.TestChangingInt = System.Random().Next(1,1000);", "Periodic" })); using (var outer = ZipFile.Open(path, ZipArchiveMode.Create)) { var entry = outer.CreateEntry("File1.cab"); using (var entryStream = entry.Open()) using (var inner = new ZipArchive(entryStream, ZipArchiveMode.Create)) { var txt = inner.CreateEntry("1055.txt"); using (var txtStream = txt.Open()) txtStream.Write(innerBytes, 0, innerBytes.Length); } } var snapshot = PackageSnapshotParser.Parse(path); var script = snapshot.ScriptBodies.Single(s => s.Name == "UpdateTestChangingInt.ExecuteText"); script.Body.ShouldBe("Me.TestChangingInt = System.Random().Next(1,1000);"); script.Source.ShouldBe("export-package:binary"); snapshot.AttributeValues.Single(a => a.Name == "UpdateTestChangingInt.ExecuteText").Value.ShouldBe(script.Body); } finally { if (File.Exists(path)) File.Delete(path); } } } }