refactor: delete ten zero-caller public service methods (#220) (#225)

Delete CloudInitService.GetCloudInitConfig, ClusterConfigService.GetTotem/
GetQdevice/GetApiVersion, ContainerService.GetContainer, HaService.GetManagerStatus,
NodeService.GetVersion, TaskService.GetTaskLog, UserService.GetUser, and
VmService.ShutdownVm. #207 found these had no caller outside tests/; re-confirmed by
grep against main at 3ea0e11 before this change. Also deletes the xUnit tests that
existed only to exercise each method, CloudInitService's now-unused CloudInitFields
array, and HaService's now-unused JsonHelper using directive.

Co-authored-by: goodolclint-claude[bot] <323206664+goodolclint-claude[bot]@users.noreply.github.com>
This commit is contained in:
goodolclint-claude[bot]
2026-09-03 18:04:43 +00:00
committed by GitHub
parent 3ea0e11988
commit 832aa6e4df
13 changed files with 0 additions and 355 deletions
@@ -14,14 +14,6 @@ namespace PSProxmoxVE.Core.Services
/// </summary>
public class CloudInitService : PveServiceBase
{
// Cloud-Init field names as used in the PVE API
private static readonly string[] CloudInitFields =
{
"ciuser", "cipassword", "sshkeys",
"ipconfig0", "ipconfig1", "ipconfig2", "ipconfig3",
"nameserver", "searchdomain", "cicustom"
};
private readonly VmService _vmService;
/// <summary>
@@ -57,34 +49,6 @@ namespace PSProxmoxVE.Core.Services
return _vmService.GetVmConfig(session, node, vmid);
}
/// <summary>
/// Retrieves the Cloud-Init specific configuration fields for a VM.
/// Internally fetches the full VM config and extracts the CI fields.
/// </summary>
public PveCloudInitConfig GetCloudInitConfig(PveSession session, string node, int vmid)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
return Invoke(session, client =>
{
var response = client.GetAsync($"nodes/{Uri.EscapeDataString(node)}/qemu/{vmid}/config")
.GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
if (data == null) return new PveCloudInitConfig();
// Extract only the Cloud-Init fields into a reduced JObject for deserialization
var ciObj = new JObject();
foreach (var field in CloudInitFields)
{
if (data[field] != null)
ciObj[field] = data[field];
}
return ciObj.ToObject<PveCloudInitConfig>() ?? new PveCloudInitConfig();
});
}
/// <summary>
/// Updates Cloud-Init configuration fields on a VM. Only fields present in
/// <paramref name="config"/> are changed; unspecified fields are left as-is.
@@ -229,51 +229,6 @@ namespace PSProxmoxVE.Core.Services
});
}
/// <summary>
/// Returns the Corosync totem configuration (GET /cluster/config/totem).
/// </summary>
public Dictionary<string, object?> GetTotem(PveSession session)
{
if (session == null) throw new ArgumentNullException(nameof(session));
return Invoke(session, client =>
{
var response = client.GetAsync("cluster/config/totem").GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return JsonHelper.ToDictionary(data as JObject);
});
}
/// <summary>
/// Returns the external quorum device (qdevice) status (GET /cluster/config/qdevice).
/// </summary>
public Dictionary<string, object?> GetQdevice(PveSession session)
{
if (session == null) throw new ArgumentNullException(nameof(session));
return Invoke(session, client =>
{
var response = client.GetAsync("cluster/config/qdevice").GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return JsonHelper.ToDictionary(data as JObject);
});
}
/// <summary>
/// Returns the cluster API version (GET /cluster/config/apiversion).
/// </summary>
public int GetApiVersion(PveSession session)
{
if (session == null) throw new ArgumentNullException(nameof(session));
return Invoke(session, client =>
{
var response = client.GetAsync("cluster/config/apiversion").GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject<int>() ?? 0;
});
}
/// <summary>
/// Returns the cluster-wide options (GET /cluster/options).
/// </summary>
@@ -96,22 +96,6 @@ namespace PSProxmoxVE.Core.Services
});
}
/// <summary>
/// Returns a single container by its ID on the specified node.
/// </summary>
public PveContainer GetContainer(PveSession session, string node, int vmid)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
var containers = GetContainersOnNode(session, node);
var ct = containers.FirstOrDefault(c => c.VmId == vmid);
if (ct == null)
throw new InvalidOperationException($"Container {vmid} not found on node '{node}'.");
ct.Node ??= node;
return ct;
}
/// <summary>
/// Returns the full configuration of a container.
/// </summary>
@@ -4,7 +4,6 @@ using Newtonsoft.Json.Linq;
using PSProxmoxVE.Core.Authentication;
using PSProxmoxVE.Core.Client;
using PSProxmoxVE.Core.Models.HA;
using PSProxmoxVE.Core.Utilities;
namespace PSProxmoxVE.Core.Services
{
@@ -285,21 +284,6 @@ namespace PSProxmoxVE.Core.Services
});
}
/// <summary>
/// Returns the full HA manager status as a raw JSON object.
/// </summary>
public Dictionary<string, object?> GetManagerStatus(PveSession session)
{
if (session == null) throw new ArgumentNullException(nameof(session));
return Invoke(session, client =>
{
var response = client.GetAsync("cluster/ha/status/manager_status").GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return JsonHelper.ToDictionary(data as JObject);
});
}
// -------------------------------------------------------------------------
// Rules (PVE 9.0+)
// -------------------------------------------------------------------------
@@ -175,23 +175,5 @@ namespace PSProxmoxVE.Core.Services
return PveTaskResponse.Parse(response, node);
});
}
/// <summary>
/// Returns the Proxmox VE version running on the server.
/// </summary>
public PveVersion GetVersion(PveSession session)
{
if (session == null) throw new ArgumentNullException(nameof(session));
return Invoke(session, client =>
{
var response = client.GetAsync("version").GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
var versionStr = data?["version"]?.ToString();
if (string.IsNullOrEmpty(versionStr))
throw new InvalidOperationException("Failed to retrieve PVE version from API response.");
return PveVersion.Parse(versionStr!);
});
}
}
}
@@ -83,25 +83,6 @@ namespace PSProxmoxVE.Core.Services
return task;
}
/// <summary>
/// Returns all log lines for a task identified by its UPID.
/// </summary>
public PveTaskLog[] GetTaskLog(PveSession session, string node, string upid)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
if (string.IsNullOrWhiteSpace(upid)) throw new ArgumentNullException(nameof(upid));
return Invoke(session, client =>
{
var encodedUpid = Uri.EscapeDataString(upid);
var response = client.GetAsync($"nodes/{Uri.EscapeDataString(node)}/tasks/{encodedUpid}/log")
.GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
return data?.ToObject<PveTaskLog[]>() ?? Array.Empty<PveTaskLog>();
});
}
/// <summary>
/// Polls the task status until it completes, throws on timeout or failure. One HTTP
/// client is held open for the whole wait.
@@ -44,27 +44,6 @@ namespace PSProxmoxVE.Core.Services
});
}
/// <summary>Returns a single user by their user ID (e.g. "admin@pam").</summary>
/// <param name="session">The authenticated PVE session.</param>
/// <param name="userId">The user ID in "username@realm" format.</param>
public PveUser GetUser(PveSession session, string userId)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(userId)) throw new ArgumentNullException(nameof(userId));
return Invoke(session, client =>
{
var encodedId = Uri.EscapeDataString(userId);
var response = client.GetAsync($"access/users/{encodedId}").GetAwaiter().GetResult();
var data = JObject.Parse(response)["data"];
var user = data?.ToObject<PveUser>() ?? new PveUser();
// The single-user endpoint may not echo back the userid
if (string.IsNullOrEmpty(user.UserId))
user.UserId = userId;
return user;
});
}
/// <summary>
/// Creates a new user account.
/// </summary>
@@ -309,28 +309,6 @@ namespace PSProxmoxVE.Core.Services
public PveTask StopVm(PveSession session, string node, int vmid)
=> PostStatus(session, node, vmid, "stop");
/// <summary>Gracefully shuts down a VM. Returns the task UPID.</summary>
/// <param name="session">The authenticated PVE session.</param>
/// <param name="node">The cluster node name.</param>
/// <param name="vmid">The VM ID.</param>
/// <param name="timeoutSeconds">Optional shutdown timeout in seconds.</param>
public PveTask ShutdownVm(PveSession session, string node, int vmid, int? timeoutSeconds = null)
{
if (session == null) throw new ArgumentNullException(nameof(session));
if (string.IsNullOrWhiteSpace(node)) throw new ArgumentNullException(nameof(node));
var formData = new Dictionary<string, string>();
if (timeoutSeconds.HasValue)
formData["timeout"] = timeoutSeconds.Value.ToString();
return Invoke(session, client =>
{
var response = client.PostAsync($"nodes/{Uri.EscapeDataString(node)}/qemu/{vmid}/status/shutdown", formData)
.GetAwaiter().GetResult();
return PveTaskResponse.Parse(response, node);
});
}
/// <summary>
/// Reboots a VM through PVE's native reboot endpoint. Returns the task UPID.
/// </summary>
@@ -17,61 +17,6 @@ namespace PSProxmoxVE.Core.Tests.Services
"root@pam!testtoken=aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee");
}
[Fact]
public void GetCloudInitConfig_ReturnsConfig()
{
// Arrange
var json = @"{""data"": {
""ciuser"": ""ubuntu"",
""ipconfig0"": ""ip=dhcp"",
""nameserver"": ""8.8.8.8"",
""searchdomain"": ""example.com"",
""sshkeys"": ""ssh-rsa%20AAAA...%20user%40host"",
""boot"": ""order=scsi0;net0"",
""cores"": 4,
""memory"": 8192
}}";
var mockClient = new Mock<IPveHttpClient>();
mockClient.Setup(c => c.GetAsync("nodes/pve1/qemu/100/config")).ReturnsAsync(json);
var service = new CloudInitService(mockClient.Object);
// Act
var config = service.GetCloudInitConfig(CreateSession(), "pve1", 100);
// Assert
Assert.NotNull(config);
Assert.Equal("ubuntu", config.CiUser);
Assert.Equal("ip=dhcp", config.IpConfig0);
Assert.Equal("8.8.8.8", config.Nameserver);
Assert.Equal("example.com", config.Searchdomain);
Assert.Equal("ssh-rsa%20AAAA...%20user%40host", config.SshKeys);
mockClient.Verify(c => c.GetAsync("nodes/pve1/qemu/100/config"), Times.Once);
}
[Fact]
public void GetCloudInitConfig_ExcludesNonCiFields()
{
// Arrange — response includes non-CI fields that should be ignored
var json = @"{""data"": {
""ciuser"": ""admin"",
""cores"": 4,
""memory"": 8192,
""boot"": ""order=scsi0""
}}";
var mockClient = new Mock<IPveHttpClient>();
mockClient.Setup(c => c.GetAsync("nodes/pve1/qemu/200/config")).ReturnsAsync(json);
var service = new CloudInitService(mockClient.Object);
// Act
var config = service.GetCloudInitConfig(CreateSession(), "pve1", 200);
// Assert
Assert.Equal("admin", config.CiUser);
// Non-CI fields should not appear in the result
Assert.Null(config.IpConfig0);
Assert.Null(config.Nameserver);
}
[Fact]
public void SetCloudInitConfig_CallsPutAsyncWithCorrectResource()
{
@@ -225,43 +225,6 @@ namespace PSProxmoxVE.Core.Tests.Services
Times.Once);
}
[Fact]
public void GetTotem_ReturnsJObject()
{
// Arrange
var json = @"{""data"": {""version"": ""2"", ""secauth"": ""on"", ""cluster_name"": ""pve-cluster""}}";
var mockClient = new Mock<IPveHttpClient>();
mockClient.Setup(c => c.GetAsync("cluster/config/totem")).ReturnsAsync(json);
var service = new ClusterConfigService(mockClient.Object);
// Act
var totem = service.GetTotem(CreateSession());
// Assert
Assert.NotNull(totem);
Assert.Equal("2", totem["version"]?.ToString());
Assert.Equal("on", totem["secauth"]?.ToString());
Assert.Equal("pve-cluster", totem["cluster_name"]?.ToString());
mockClient.Verify(c => c.GetAsync("cluster/config/totem"), Times.Once);
}
[Fact]
public void GetApiVersion_ReturnsInt()
{
// Arrange
var json = @"{""data"": 10}";
var mockClient = new Mock<IPveHttpClient>();
mockClient.Setup(c => c.GetAsync("cluster/config/apiversion")).ReturnsAsync(json);
var service = new ClusterConfigService(mockClient.Object);
// Act
var version = service.GetApiVersion(CreateSession());
// Assert
Assert.Equal(10, version);
mockClient.Verify(c => c.GetAsync("cluster/config/apiversion"), Times.Once);
}
[Fact]
public void GetClusterOptions_ReturnsPveClusterOptions()
{
@@ -337,25 +337,6 @@ namespace PSProxmoxVE.Core.Tests.Services
mockClient.Verify(c => c.GetAsync("cluster/ha/status/current"), Times.Once);
}
[Fact]
public void GetManagerStatus_ReturnsJObject()
{
// Arrange
var json = @"{""data"": {""manager_status"": {""master_node"": ""pve1""}, ""quorum"": {""node"": ""pve1""}}}";
var mockClient = new Mock<IPveHttpClient>();
mockClient.Setup(c => c.GetAsync("cluster/ha/status/manager_status")).ReturnsAsync(json);
var service = new HaService(mockClient.Object);
// Act
var status = service.GetManagerStatus(CreateSession());
// Assert
Assert.NotNull(status);
Assert.NotNull(status["manager_status"]);
Assert.NotNull(status["quorum"]);
mockClient.Verify(c => c.GetAsync("cluster/ha/status/manager_status"), Times.Once);
}
// -----------------------------------------------------------------
// Rules
// -----------------------------------------------------------------
@@ -58,35 +58,6 @@ namespace PSProxmoxVE.Core.Tests.Services
Assert.True(task.IsSuccessful);
}
[Fact]
public void GetTaskLog_HappyPath_ReturnsLogEntries()
{
// Arrange
var json = @"{
""data"": [
{ ""n"": 1, ""t"": ""starting task qmstart"" },
{ ""n"": 2, ""t"": ""VM 100 started"" },
{ ""n"": 3, ""t"": ""TASK OK"" }
]
}";
var mockClient = new Mock<IPveHttpClient>();
mockClient.Setup(c => c.GetAsync(It.IsAny<string>()))
.ReturnsAsync(json);
var service = new TaskService(mockClient.Object);
var session = CreateSession();
// Act
var logs = service.GetTaskLog(session, TestNode, TestUpid);
// Assert
Assert.Equal(3, logs.Length);
Assert.Equal(1, logs[0].LineNumber);
Assert.Equal("starting task qmstart", logs[0].Text);
Assert.Equal("TASK OK", logs[2].Text);
}
[Fact]
public void GetTasks_HappyPath_ReturnsCorrectCount()
{
@@ -53,28 +53,6 @@ namespace PSProxmoxVE.Core.Tests.Services
_mockClient.Verify(c => c.GetAsync("access/users"), Times.Once);
}
[Fact]
public void GetUser_ReturnsSingleUser()
{
// Arrange
_mockClient.Setup(c => c.GetAsync("access/users/root%40pam"))
.ReturnsAsync(@"{""data"":{""email"":""root@example.com"",""firstname"":""Root"",""lastname"":""Admin"",""enable"":1}}");
// Act
var result = _service.GetUser(_session, "root@pam");
// Assert
Assert.Equal("root@pam", result.UserId);
Assert.Equal("root@example.com", result.Email);
Assert.Equal(1, result.Enabled);
}
[Fact]
public void GetUser_NullUserId_ThrowsArgumentNullException()
{
Assert.Throws<ArgumentNullException>(() => _service.GetUser(_session, null!));
}
[Fact]
public void CreateUser_PostsFormData()
{