From 8255e506a61b5b88dc2774676babdf598fda0f6b Mon Sep 17 00:00:00 2001 From: "goodolclint-claude[bot]" <323206664+goodolclint-claude[bot]@users.noreply.github.com> Date: Wed, 2 Sep 2026 17:32:09 +0000 Subject: [PATCH] fix: recognize boolean and varied integer formats for guest exec exited status (#160) * fix: recognize boolean and varied integer formats for guest exec exited status The guest-agent schema declares 'exited' as a boolean, but the PVE API may return it in different formats: as a JSON boolean true, or as integers 1/0, or string '1'/'0'. The cmdlet was only checking for long 1L, causing it to timeout on any PVE build that passed a boolean true unchanged. Add ApiValueHelper.IsExited() to normalize these values and recognize true, 1L, 1, and '1' as exited. Update the polling loop to use it. * test: add integration tests for ApiValueHelper with real JSON payloads Add tests that feed JSON data through the actual JsonHelper.ToNative parsing pipeline to verify ApiValueHelper.IsExited correctly recognizes boolean true and numeric 1 values as they arrive from the PVE API. These tests pin the contract between the JSON parsing layer and the value-recognition logic, ensuring the fix for issue #141 works end-to-end with real API response shapes. * fix: restore correct IsExited implementation with type checks The helper must handle boolean true, long/int 1, and string "1" as the issue specifies. This restores the correct multi-type check that was inadvertently reverted. * fix: correct boolean value handling in IsExited --------- Co-authored-by: goodolclint-claude[bot] <323206664+goodolclint-claude[bot]@users.noreply.github.com> --- .../Utilities/ApiValueHelper.cs | 38 ++++++++++++++ .../Cmdlets/Vms/InvokePveVmGuestExecCmdlet.cs | 3 +- .../Utilities/ApiValueHelperTests.cs | 52 +++++++++++++++++++ 3 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 src/PSProxmoxVE.Core/Utilities/ApiValueHelper.cs create mode 100644 tests/PSProxmoxVE.Core.Tests/Utilities/ApiValueHelperTests.cs 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); + } + } +}