diff --git a/src/PSProxmoxVE.Core/Utilities/ApiValueHelper.cs b/src/PSProxmoxVE.Core/Utilities/ApiValueHelper.cs new file mode 100644 index 0000000..8296cae --- /dev/null +++ b/src/PSProxmoxVE.Core/Utilities/ApiValueHelper.cs @@ -0,0 +1,38 @@ +using System; + +namespace PSProxmoxVE.Core.Utilities +{ + /// + /// Helper methods for normalizing and checking values received from the Proxmox VE API. + /// The API may return the same logical value in different formats (e.g., boolean true, integer 1, or string "1"). + /// + public static class ApiValueHelper + { + /// + /// Determines if a value represents a true/exited state. + /// Accepts boolean true, integer 1 (as Int64 or Int32), and string "1" as true. + /// All other values (false, 0, "0", null, etc.) are false. + /// + /// The value to check, typically from API response data. + /// True if the value represents an exited/true state, false otherwise. + public static bool IsExited(object? value) + { + if (value == null) + return false; + + if (value is bool b) + return b; + + if (value is long l) + return l == 1L; + + if (value is int i) + return i == 1; + + if (value is string s) + return s == "1"; + + return false; + } + } +} diff --git a/src/PSProxmoxVE/Cmdlets/Vms/InvokePveVmGuestExecCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Vms/InvokePveVmGuestExecCmdlet.cs index b61ba1e..1c1d523 100644 --- a/src/PSProxmoxVE/Cmdlets/Vms/InvokePveVmGuestExecCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/Vms/InvokePveVmGuestExecCmdlet.cs @@ -2,6 +2,7 @@ using System; using System.Diagnostics; using System.Management.Automation; using PSProxmoxVE.Core.Services; +using PSProxmoxVE.Core.Utilities; namespace PSProxmoxVE.Cmdlets.Vms { @@ -60,7 +61,7 @@ namespace PSProxmoxVE.Cmdlets.Vms if (sw.Elapsed >= deadline) throw new TimeoutException($"Guest command did not complete within {Timeout} seconds."); result = service.GetGuestExecStatus(session, Node, VmId, pid); - } while (!result.TryGetValue("exited", out var exited) || !Equals(exited, 1L)); + } while (!result.TryGetValue("exited", out var exited) || !ApiValueHelper.IsExited(exited)); var output = new PSObject(); output.Properties.Add(new PSNoteProperty("ExitCode", result.TryGetValue("exitcode", out var ec) && ec is long ecl ? (int)ecl : -1)); diff --git a/tests/PSProxmoxVE.Core.Tests/Utilities/ApiValueHelperTests.cs b/tests/PSProxmoxVE.Core.Tests/Utilities/ApiValueHelperTests.cs new file mode 100644 index 0000000..1d69253 --- /dev/null +++ b/tests/PSProxmoxVE.Core.Tests/Utilities/ApiValueHelperTests.cs @@ -0,0 +1,52 @@ +using Newtonsoft.Json.Linq; +using PSProxmoxVE.Core.Utilities; +using Xunit; + +namespace PSProxmoxVE.Core.Tests.Utilities +{ + public class ApiValueHelperTests + { + [Theory] + [InlineData(true)] + [InlineData(1L)] + [InlineData(1)] + [InlineData("1")] + public void IsExited_TrueValues_ReturnsTrue(object value) + { + Assert.True(ApiValueHelper.IsExited(value)); + } + + [Theory] + [InlineData(false)] + [InlineData(0L)] + [InlineData(0)] + [InlineData("0")] + [InlineData(null)] + [InlineData("")] + [InlineData("true")] + [InlineData(2L)] + [InlineData(42)] + public void IsExited_FalseValues_ReturnsFalse(object? value) + { + Assert.False(ApiValueHelper.IsExited(value)); + } + + [Theory] + [InlineData("{\"data\":{\"exited\":true}}", true)] + [InlineData("{\"data\":{\"exited\":false}}", false)] + [InlineData("{\"data\":{\"exited\":1}}", true)] + [InlineData("{\"data\":{\"exited\":0}}", false)] + [InlineData("{\"data\":{\"exited\":\"1\"}}", true)] + [InlineData("{\"data\":{\"exited\":\"0\"}}", false)] + [InlineData("{\"data\":{\"exited\":null}}", false)] + [InlineData("{\"data\":{}}", false)] + public void IsExited_ApiJsonValues_ReturnExpectedCompletionState(string json, bool expected) + { + var data = (JObject)JObject.Parse(json)["data"]!; + var status = JsonHelper.ToDictionary(data); + var completed = status.TryGetValue("exited", out var exited) && ApiValueHelper.IsExited(exited); + + Assert.Equal(expected, completed); + } + } +}