mirror of
https://github.com/GoodOlClint/PSProxmoxVE.git
synced 2026-07-26 07:58:14 +00:00
56dcf22cea
- VmService.ExecuteGuestCommand: guard against null elements in -Args. The old JSON-serialization tolerated nulls (as "null"); the repeated-key path would NRE in EncodeFormValue. Throw a clear ArgumentException instead. - findings.json: refresh the stale counters block (untouched since F085) to the actual ledger state — next_id 92, resolved 83 — and bump last_updated to 2026-05-22. last_scan_date stays 2026-03-26 (F086–F091 came from issue triage, not a formal review scan). - VmServiceTests: add empty-array (single command entry) and null-element (throws) cases. Note: F091 is the correct next ID — F086–F090 already exist from prior merged PRs (#60/#61/#66/#67); only the counters were lagging. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
114 lines
4.8 KiB
C#
114 lines
4.8 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using Moq;
|
|
using PSProxmoxVE.Core.Authentication;
|
|
using PSProxmoxVE.Core.Client;
|
|
using PSProxmoxVE.Core.Services;
|
|
using Xunit;
|
|
|
|
namespace PSProxmoxVE.Core.Tests.Services
|
|
{
|
|
public class VmServiceTests
|
|
{
|
|
private const string TestNode = "pve1";
|
|
private const int TestVmId = 100;
|
|
|
|
private static PveSession CreateSession() =>
|
|
new PveSession("pve1.example.com", 8006, true, "PVE:root@pam:TEST_TOKEN");
|
|
|
|
[Fact]
|
|
public void ExecuteGuestCommand_SendsCommandAndArgsAsRepeatedCommandArray()
|
|
{
|
|
List<KeyValuePair<string, string>>? captured = null;
|
|
var mockClient = new Mock<IPveHttpClient>();
|
|
mockClient
|
|
.Setup(c => c.PostAsync(It.IsAny<string>(), It.IsAny<IEnumerable<KeyValuePair<string, string>>>()))
|
|
.Callback<string, IEnumerable<KeyValuePair<string, string>>>((_, data) => captured = data.ToList())
|
|
.ReturnsAsync("{\"data\":{\"pid\":4242}}");
|
|
|
|
var service = new VmService(mockClient.Object);
|
|
var pid = service.ExecuteGuestCommand(CreateSession(), TestNode, TestVmId,
|
|
"cmd.exe", new[] { "/c", "echo", "WLMARK42" });
|
|
|
|
Assert.Equal(4242, pid);
|
|
Assert.NotNull(captured);
|
|
|
|
// Every element (exe + each arg) is its own "command" entry, in order.
|
|
Assert.All(captured!, kvp => Assert.Equal("command", kvp.Key));
|
|
Assert.Equal(
|
|
new[] { "cmd.exe", "/c", "echo", "WLMARK42" },
|
|
captured!.Select(kvp => kvp.Value).ToArray());
|
|
}
|
|
|
|
[Fact]
|
|
public void ExecuteGuestCommand_DoesNotUseInputDataForArgs()
|
|
{
|
|
List<KeyValuePair<string, string>>? captured = null;
|
|
var mockClient = new Mock<IPveHttpClient>();
|
|
mockClient
|
|
.Setup(c => c.PostAsync(It.IsAny<string>(), It.IsAny<IEnumerable<KeyValuePair<string, string>>>()))
|
|
.Callback<string, IEnumerable<KeyValuePair<string, string>>>((_, data) => captured = data.ToList())
|
|
.ReturnsAsync("{\"data\":{\"pid\":1}}");
|
|
|
|
var service = new VmService(mockClient.Object);
|
|
service.ExecuteGuestCommand(CreateSession(), TestNode, TestVmId,
|
|
"powershell.exe", new[] { "-NoProfile", "-Command", "echo hi" });
|
|
|
|
Assert.NotNull(captured);
|
|
// Args are argv, not STDIN — "input-data" must never be emitted.
|
|
Assert.DoesNotContain(captured!, kvp => kvp.Key == "input-data");
|
|
}
|
|
|
|
[Fact]
|
|
public void ExecuteGuestCommand_NoArgs_SendsSingleCommandEntry()
|
|
{
|
|
List<KeyValuePair<string, string>>? captured = null;
|
|
var mockClient = new Mock<IPveHttpClient>();
|
|
mockClient
|
|
.Setup(c => c.PostAsync(It.IsAny<string>(), It.IsAny<IEnumerable<KeyValuePair<string, string>>>()))
|
|
.Callback<string, IEnumerable<KeyValuePair<string, string>>>((_, data) => captured = data.ToList())
|
|
.ReturnsAsync("{\"data\":{\"pid\":7}}");
|
|
|
|
var service = new VmService(mockClient.Object);
|
|
service.ExecuteGuestCommand(CreateSession(), TestNode, TestVmId, "whoami", null);
|
|
|
|
Assert.NotNull(captured);
|
|
var only = Assert.Single(captured!);
|
|
Assert.Equal("command", only.Key);
|
|
Assert.Equal("whoami", only.Value);
|
|
}
|
|
|
|
[Fact]
|
|
public void ExecuteGuestCommand_EmptyArgs_SendsSingleCommandEntry()
|
|
{
|
|
List<KeyValuePair<string, string>>? captured = null;
|
|
var mockClient = new Mock<IPveHttpClient>();
|
|
mockClient
|
|
.Setup(c => c.PostAsync(It.IsAny<string>(), It.IsAny<IEnumerable<KeyValuePair<string, string>>>()))
|
|
.Callback<string, IEnumerable<KeyValuePair<string, string>>>((_, data) => captured = data.ToList())
|
|
.ReturnsAsync("{\"data\":{\"pid\":9}}");
|
|
|
|
var service = new VmService(mockClient.Object);
|
|
service.ExecuteGuestCommand(CreateSession(), TestNode, TestVmId, "whoami", new string[0]);
|
|
|
|
Assert.NotNull(captured);
|
|
var only = Assert.Single(captured!);
|
|
Assert.Equal("command", only.Key);
|
|
Assert.Equal("whoami", only.Value);
|
|
}
|
|
|
|
[Fact]
|
|
public void ExecuteGuestCommand_NullArgElement_ThrowsArgumentException()
|
|
{
|
|
var mockClient = new Mock<IPveHttpClient>();
|
|
var service = new VmService(mockClient.Object);
|
|
|
|
var ex = Assert.Throws<ArgumentException>(() =>
|
|
service.ExecuteGuestCommand(CreateSession(), TestNode, TestVmId,
|
|
"cmd.exe", new[] { "/c", null!, "echo" }));
|
|
Assert.Equal("args", ex.ParamName);
|
|
}
|
|
}
|
|
}
|