feat(test): add mock PVE API server for integration testing

ASP.NET Minimal API project that simulates the Proxmox VE API using
existing JSON fixtures. Supports ticket and API token auth, request
tracking for assertions, self-signed HTTPS, and all major API endpoints.
Designed for programmatic test startup via MockPveServer.Start().

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Clint Branham
2026-03-17 16:14:56 -05:00
parent 7ee4519cd7
commit 239aee630f
15 changed files with 644 additions and 1 deletions
+47 -1
View File
@@ -1,4 +1,4 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
@@ -13,28 +13,74 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PSProxmoxVE", "src\PSProxmo
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PSProxmoxVE.Core.Tests", "tests\PSProxmoxVE.Core.Tests\PSProxmoxVE.Core.Tests.csproj", "{E5F6A7B8-C9D0-1234-EF12-345678901234}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PSProxmoxVE.MockServer", "tests\PSProxmoxVE.MockServer\PSProxmoxVE.MockServer.csproj", "{B7490D48-1665-41F4-B248-70051EC2F061}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{C3D4E5F6-A7B8-9012-CDEF-123456789012}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C3D4E5F6-A7B8-9012-CDEF-123456789012}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C3D4E5F6-A7B8-9012-CDEF-123456789012}.Debug|x64.ActiveCfg = Debug|Any CPU
{C3D4E5F6-A7B8-9012-CDEF-123456789012}.Debug|x64.Build.0 = Debug|Any CPU
{C3D4E5F6-A7B8-9012-CDEF-123456789012}.Debug|x86.ActiveCfg = Debug|Any CPU
{C3D4E5F6-A7B8-9012-CDEF-123456789012}.Debug|x86.Build.0 = Debug|Any CPU
{C3D4E5F6-A7B8-9012-CDEF-123456789012}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C3D4E5F6-A7B8-9012-CDEF-123456789012}.Release|Any CPU.Build.0 = Release|Any CPU
{C3D4E5F6-A7B8-9012-CDEF-123456789012}.Release|x64.ActiveCfg = Release|Any CPU
{C3D4E5F6-A7B8-9012-CDEF-123456789012}.Release|x64.Build.0 = Release|Any CPU
{C3D4E5F6-A7B8-9012-CDEF-123456789012}.Release|x86.ActiveCfg = Release|Any CPU
{C3D4E5F6-A7B8-9012-CDEF-123456789012}.Release|x86.Build.0 = Release|Any CPU
{D4E5F6A7-B8C9-0123-DEF1-234567890123}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D4E5F6A7-B8C9-0123-DEF1-234567890123}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D4E5F6A7-B8C9-0123-DEF1-234567890123}.Debug|x64.ActiveCfg = Debug|Any CPU
{D4E5F6A7-B8C9-0123-DEF1-234567890123}.Debug|x64.Build.0 = Debug|Any CPU
{D4E5F6A7-B8C9-0123-DEF1-234567890123}.Debug|x86.ActiveCfg = Debug|Any CPU
{D4E5F6A7-B8C9-0123-DEF1-234567890123}.Debug|x86.Build.0 = Debug|Any CPU
{D4E5F6A7-B8C9-0123-DEF1-234567890123}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D4E5F6A7-B8C9-0123-DEF1-234567890123}.Release|Any CPU.Build.0 = Release|Any CPU
{D4E5F6A7-B8C9-0123-DEF1-234567890123}.Release|x64.ActiveCfg = Release|Any CPU
{D4E5F6A7-B8C9-0123-DEF1-234567890123}.Release|x64.Build.0 = Release|Any CPU
{D4E5F6A7-B8C9-0123-DEF1-234567890123}.Release|x86.ActiveCfg = Release|Any CPU
{D4E5F6A7-B8C9-0123-DEF1-234567890123}.Release|x86.Build.0 = Release|Any CPU
{E5F6A7B8-C9D0-1234-EF12-345678901234}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E5F6A7B8-C9D0-1234-EF12-345678901234}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E5F6A7B8-C9D0-1234-EF12-345678901234}.Debug|x64.ActiveCfg = Debug|Any CPU
{E5F6A7B8-C9D0-1234-EF12-345678901234}.Debug|x64.Build.0 = Debug|Any CPU
{E5F6A7B8-C9D0-1234-EF12-345678901234}.Debug|x86.ActiveCfg = Debug|Any CPU
{E5F6A7B8-C9D0-1234-EF12-345678901234}.Debug|x86.Build.0 = Debug|Any CPU
{E5F6A7B8-C9D0-1234-EF12-345678901234}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E5F6A7B8-C9D0-1234-EF12-345678901234}.Release|Any CPU.Build.0 = Release|Any CPU
{E5F6A7B8-C9D0-1234-EF12-345678901234}.Release|x64.ActiveCfg = Release|Any CPU
{E5F6A7B8-C9D0-1234-EF12-345678901234}.Release|x64.Build.0 = Release|Any CPU
{E5F6A7B8-C9D0-1234-EF12-345678901234}.Release|x86.ActiveCfg = Release|Any CPU
{E5F6A7B8-C9D0-1234-EF12-345678901234}.Release|x86.Build.0 = Release|Any CPU
{B7490D48-1665-41F4-B248-70051EC2F061}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B7490D48-1665-41F4-B248-70051EC2F061}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B7490D48-1665-41F4-B248-70051EC2F061}.Debug|x64.ActiveCfg = Debug|Any CPU
{B7490D48-1665-41F4-B248-70051EC2F061}.Debug|x64.Build.0 = Debug|Any CPU
{B7490D48-1665-41F4-B248-70051EC2F061}.Debug|x86.ActiveCfg = Debug|Any CPU
{B7490D48-1665-41F4-B248-70051EC2F061}.Debug|x86.Build.0 = Debug|Any CPU
{B7490D48-1665-41F4-B248-70051EC2F061}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B7490D48-1665-41F4-B248-70051EC2F061}.Release|Any CPU.Build.0 = Release|Any CPU
{B7490D48-1665-41F4-B248-70051EC2F061}.Release|x64.ActiveCfg = Release|Any CPU
{B7490D48-1665-41F4-B248-70051EC2F061}.Release|x64.Build.0 = Release|Any CPU
{B7490D48-1665-41F4-B248-70051EC2F061}.Release|x86.ActiveCfg = Release|Any CPU
{B7490D48-1665-41F4-B248-70051EC2F061}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{C3D4E5F6-A7B8-9012-CDEF-123456789012} = {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}
{D4E5F6A7-B8C9-0123-DEF1-234567890123} = {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}
{E5F6A7B8-C9D0-1234-EF12-345678901234} = {B2C3D4E5-F6A7-8901-BCDE-F12345678901}
{B7490D48-1665-41F4-B248-70051EC2F061} = {B2C3D4E5-F6A7-8901-BCDE-F12345678901}
EndGlobalSection
EndGlobal
@@ -0,0 +1,209 @@
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Hosting.Server.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.Extensions.DependencyInjection;
using PSProxmoxVE.MockServer.Routes;
namespace PSProxmoxVE.MockServer;
public record RequestRecord(string Method, string Path, string? Body, DateTime Timestamp);
public class MockPveServer : IDisposable
{
private readonly WebApplication _app;
private bool _disposed;
public string BaseUrl { get; }
public int Port { get; }
public List<RequestRecord> Requests { get; } = new();
private static readonly Dictionary<string, string> _fixtures = new();
private static readonly object _fixtureLock = new();
// Track task status calls per UPID for simulating running -> completed transitions
internal Dictionary<string, int> TaskStatusCallCounts { get; } = new();
private MockPveServer(WebApplication app, int port)
{
_app = app;
Port = port;
BaseUrl = $"https://localhost:{port}";
}
public static MockPveServer Start(int port = 0)
{
LoadFixtures();
var cert = GenerateSelfSignedCert();
var builder = WebApplication.CreateBuilder();
builder.WebHost.ConfigureKestrel(options =>
{
options.ListenLocalhost(port, listenOptions =>
{
listenOptions.Protocols = HttpProtocols.Http1AndHttp2;
listenOptions.UseHttps(cert);
});
});
// Suppress default logging noise in tests
builder.Logging.ClearProviders();
var app = builder.Build();
var server = new MockPveServer(app, port);
// Request tracking middleware
app.Use(async (context, next) =>
{
string? body = null;
if (context.Request.ContentLength > 0 || context.Request.Headers.ContainsKey("Transfer-Encoding"))
{
context.Request.EnableBuffering();
using var reader = new StreamReader(context.Request.Body, leaveOpen: true);
body = await reader.ReadToEndAsync();
context.Request.Body.Position = 0;
}
server.Requests.Add(new RequestRecord(
context.Request.Method,
context.Request.Path + context.Request.QueryString,
body,
DateTime.UtcNow));
await next();
});
// Auth middleware
app.Use(async (context, next) =>
{
var path = context.Request.Path.Value ?? "";
var method = context.Request.Method;
// Skip auth for ticket creation and version endpoints
bool skipAuth = (path == "/api2/json/access/ticket" && method == "POST")
|| (path == "/api2/json/access/ticket" && method == "DELETE")
|| (path == "/api2/json/version" && method == "GET");
if (!skipAuth)
{
var hasCookie = context.Request.Headers["Cookie"].ToString().Contains("PVEAuthCookie=");
var hasToken = context.Request.Headers["Authorization"].ToString().StartsWith("PVEAPIToken=");
if (!hasCookie && !hasToken)
{
context.Response.StatusCode = 401;
context.Response.ContentType = "application/json";
await context.Response.WriteAsync("{\"data\":null,\"errors\":{\"authentication\":\"invalid credentials\"}}");
return;
}
}
await next();
});
// Register all routes
app.MapAuthRoutes();
app.MapVersionRoutes();
app.MapNodeRoutes();
app.MapVmRoutes();
app.MapContainerRoutes();
app.MapStorageRoutes();
app.MapNetworkRoutes();
app.MapUserRoutes();
app.MapClusterRoutes();
app.MapTaskRoutes(server);
app.MapSnapshotRoutes();
app.StartAsync().GetAwaiter().GetResult();
// Resolve the actual port (important when port=0 for random assignment)
var addresses = app.Urls;
var actualPort = port;
var serverInstance = app.Services.GetRequiredService<IServer>();
if (serverInstance != null)
{
var addressFeature = serverInstance.Features.Get<IServerAddressesFeature>();
if (addressFeature != null)
{
var addr = addressFeature.Addresses.FirstOrDefault();
if (addr != null)
{
var uri = new Uri(addr);
actualPort = uri.Port;
}
}
}
return new MockPveServer(app, actualPort);
}
public void Reset()
{
Requests.Clear();
TaskStatusCallCounts.Clear();
}
public void Dispose()
{
if (!_disposed)
{
_disposed = true;
_app.StopAsync().GetAwaiter().GetResult();
_app.DisposeAsync().AsTask().GetAwaiter().GetResult();
}
}
public static string GetFixture(string name)
{
if (_fixtures.TryGetValue(name, out var content))
return content;
return "{\"data\":null}";
}
public static string GenerateUpid(string node, string type, int vmid = 0)
=> $"UPID:{node}:{Guid.NewGuid():N}:{DateTimeOffset.UtcNow.ToUnixTimeSeconds():X}:{type}:{vmid}:root@pam:";
private static X509Certificate2 GenerateSelfSignedCert()
{
var rsa = RSA.Create(2048);
var req = new CertificateRequest("CN=pve-mock", rsa, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
var cert = req.CreateSelfSigned(DateTimeOffset.Now, DateTimeOffset.Now.AddYears(1));
return cert;
}
private static void LoadFixtures()
{
lock (_fixtureLock)
{
if (_fixtures.Count > 0) return;
// Resolve fixtures path relative to the assembly location
var basePath = AppContext.BaseDirectory;
var fixturesPath = Path.GetFullPath(Path.Combine(basePath, "..", "..", "..", "..", "PSProxmoxVE.Core.Tests", "Fixtures"));
// Fallback: try relative to the project source directory
if (!Directory.Exists(fixturesPath))
{
fixturesPath = Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, "..", "..", "..", "..", "..", "PSProxmoxVE.Core.Tests", "Fixtures"));
}
if (!Directory.Exists(fixturesPath))
{
// Last resort: look from the working directory
fixturesPath = Path.GetFullPath(Path.Combine(Directory.GetCurrentDirectory(), "..", "PSProxmoxVE.Core.Tests", "Fixtures"));
}
if (Directory.Exists(fixturesPath))
{
foreach (var file in Directory.GetFiles(fixturesPath, "*.json"))
{
var name = Path.GetFileNameWithoutExtension(file);
_fixtures[name] = File.ReadAllText(file);
}
}
}
}
}
@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
</Project>
+22
View File
@@ -0,0 +1,22 @@
using PSProxmoxVE.MockServer;
// When run standalone, start on a fixed port
using var server = MockPveServer.Start(port: 8006);
Console.WriteLine($"Mock Proxmox VE API server running at {server.BaseUrl}");
Console.WriteLine("Press Ctrl+C to stop.");
var cts = new CancellationTokenSource();
Console.CancelKeyPress += (_, e) =>
{
e.Cancel = true;
cts.Cancel();
};
try
{
await Task.Delay(Timeout.Infinite, cts.Token);
}
catch (OperationCanceledException)
{
// Graceful shutdown
}
@@ -0,0 +1,18 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
namespace PSProxmoxVE.MockServer.Routes;
public static class AuthRoutes
{
public static WebApplication MapAuthRoutes(this WebApplication app)
{
app.MapPost("/api2/json/access/ticket", () =>
Results.Content(MockPveServer.GetFixture("pve9_ticket_response"), "application/json"));
app.MapDelete("/api2/json/access/ticket", () =>
Results.Content("{\"data\":null}", "application/json"));
return app;
}
}
@@ -0,0 +1,15 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
namespace PSProxmoxVE.MockServer.Routes;
public static class ClusterRoutes
{
public static WebApplication MapClusterRoutes(this WebApplication app)
{
app.MapGet("/api2/json/cluster/status", () =>
Results.Content(MockPveServer.GetFixture("pve9_cluster_status"), "application/json"));
return app;
}
}
@@ -0,0 +1,36 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
namespace PSProxmoxVE.MockServer.Routes;
public static class ContainerRoutes
{
public static WebApplication MapContainerRoutes(this WebApplication app)
{
app.MapGet("/api2/json/nodes/{node}/lxc", (string node) =>
Results.Content(MockPveServer.GetFixture("pve9_containers"), "application/json"));
app.MapGet("/api2/json/nodes/{node}/lxc/{vmid}/config", (string node, int vmid) =>
Results.Content("{\"data\":{\"hostname\":\"test\",\"memory\":512,\"cores\":1}}", "application/json"));
app.MapPost("/api2/json/nodes/{node}/lxc", (string node) =>
Results.Content($"{{\"data\":\"{MockPveServer.GenerateUpid(node, "vzcreate")}\"}}", "application/json"));
app.MapPost("/api2/json/nodes/{node}/lxc/{vmid}/status/start", (string node, int vmid) =>
Results.Content($"{{\"data\":\"{MockPveServer.GenerateUpid(node, "vzstart", vmid)}\"}}", "application/json"));
app.MapPost("/api2/json/nodes/{node}/lxc/{vmid}/status/stop", (string node, int vmid) =>
Results.Content($"{{\"data\":\"{MockPveServer.GenerateUpid(node, "vzstop", vmid)}\"}}", "application/json"));
app.MapPost("/api2/json/nodes/{node}/lxc/{vmid}/status/shutdown", (string node, int vmid) =>
Results.Content($"{{\"data\":\"{MockPveServer.GenerateUpid(node, "vzshutdown", vmid)}\"}}", "application/json"));
app.MapPut("/api2/json/nodes/{node}/lxc/{vmid}/config", (string node, int vmid) =>
Results.Content("{\"data\":null}", "application/json"));
app.MapDelete("/api2/json/nodes/{node}/lxc/{vmid}", (string node, int vmid) =>
Results.Content($"{{\"data\":\"{MockPveServer.GenerateUpid(node, "vzdestroy", vmid)}\"}}", "application/json"));
return app;
}
}
@@ -0,0 +1,35 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
namespace PSProxmoxVE.MockServer.Routes;
public static class NetworkRoutes
{
public static WebApplication MapNetworkRoutes(this WebApplication app)
{
app.MapGet("/api2/json/nodes/{node}/network", (string node) =>
Results.Content(MockPveServer.GetFixture("pve9_networks"), "application/json"));
app.MapPost("/api2/json/nodes/{node}/network", (string node) =>
Results.Content("{\"data\":null}", "application/json"));
app.MapPut("/api2/json/nodes/{node}/network/{iface}", (string node, string iface) =>
Results.Content("{\"data\":null}", "application/json"));
app.MapDelete("/api2/json/nodes/{node}/network/{iface}", (string node, string iface) =>
Results.Content("{\"data\":null}", "application/json"));
// Apply network config
app.MapPut("/api2/json/nodes/{node}/network", (string node) =>
Results.Content($"{{\"data\":\"{MockPveServer.GenerateUpid(node, "netapply")}\"}}", "application/json"));
// SDN routes
app.MapGet("/api2/json/cluster/sdn/zones", () =>
Results.Content(MockPveServer.GetFixture("pve9_sdn_zones"), "application/json"));
app.MapGet("/api2/json/cluster/sdn/vnets", () =>
Results.Content(MockPveServer.GetFixture("pve9_sdn_vnets"), "application/json"));
return app;
}
}
@@ -0,0 +1,18 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
namespace PSProxmoxVE.MockServer.Routes;
public static class NodeRoutes
{
public static WebApplication MapNodeRoutes(this WebApplication app)
{
app.MapGet("/api2/json/nodes", () =>
Results.Content(MockPveServer.GetFixture("pve9_nodes"), "application/json"));
app.MapGet("/api2/json/nodes/{node}/status", (string node) =>
Results.Content(MockPveServer.GetFixture("pve9_node_status"), "application/json"));
return app;
}
}
@@ -0,0 +1,24 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
namespace PSProxmoxVE.MockServer.Routes;
public static class SnapshotRoutes
{
public static WebApplication MapSnapshotRoutes(this WebApplication app)
{
app.MapGet("/api2/json/nodes/{node}/qemu/{vmid}/snapshot", (string node, int vmid) =>
Results.Content(MockPveServer.GetFixture("pve9_snapshots"), "application/json"));
app.MapPost("/api2/json/nodes/{node}/qemu/{vmid}/snapshot", (string node, int vmid) =>
Results.Content($"{{\"data\":\"{MockPveServer.GenerateUpid(node, "qmsnapshot", vmid)}\"}}", "application/json"));
app.MapDelete("/api2/json/nodes/{node}/qemu/{vmid}/snapshot/{snapname}", (string node, int vmid, string snapname) =>
Results.Content($"{{\"data\":\"{MockPveServer.GenerateUpid(node, "qmdelsnapshot", vmid)}\"}}", "application/json"));
app.MapPost("/api2/json/nodes/{node}/qemu/{vmid}/snapshot/{snapname}/rollback", (string node, int vmid, string snapname) =>
Results.Content($"{{\"data\":\"{MockPveServer.GenerateUpid(node, "qmrollback", vmid)}\"}}", "application/json"));
return app;
}
}
@@ -0,0 +1,28 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
namespace PSProxmoxVE.MockServer.Routes;
public static class StorageRoutes
{
public static WebApplication MapStorageRoutes(this WebApplication app)
{
app.MapGet("/api2/json/storage", () =>
Results.Content(MockPveServer.GetFixture("pve9_storage"), "application/json"));
app.MapGet("/api2/json/nodes/{node}/storage/{storage}/content", (string node, string storage) =>
Results.Content(MockPveServer.GetFixture("pve9_storage_content"), "application/json"));
app.MapPost("/api2/json/nodes/{node}/storage/{storage}/upload", (string node, string storage) =>
Results.Content($"{{\"data\":\"{MockPveServer.GenerateUpid(node, "upload")}\"}}", "application/json"))
.DisableAntiforgery();
app.MapPost("/api2/json/storage", () =>
Results.Content("{\"data\":null}", "application/json"));
app.MapDelete("/api2/json/storage/{storage}", (string storage) =>
Results.Content("{\"data\":null}", "application/json"));
return app;
}
}
@@ -0,0 +1,33 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
namespace PSProxmoxVE.MockServer.Routes;
public static class TaskRoutes
{
public static WebApplication MapTaskRoutes(this WebApplication app, MockPveServer server)
{
app.MapGet("/api2/json/nodes/{node}/tasks/{upid}/status", (string node, string upid) =>
{
// Track call count per UPID: first call returns running, subsequent return completed
int count;
lock (server.TaskStatusCallCounts)
{
if (!server.TaskStatusCallCounts.TryGetValue(upid, out count))
count = 0;
count++;
server.TaskStatusCallCounts[upid] = count;
}
if (count <= 1)
return Results.Content(MockPveServer.GetFixture("pve9_task_running"), "application/json");
else
return Results.Content(MockPveServer.GetFixture("pve9_tasks"), "application/json");
});
app.MapGet("/api2/json/nodes/{node}/tasks/{upid}/log", (string node, string upid) =>
Results.Content("{\"data\":[{\"n\":1,\"t\":\"starting task\"}]}", "application/json"));
return app;
}
}
@@ -0,0 +1,58 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
namespace PSProxmoxVE.MockServer.Routes;
public static class UserRoutes
{
public static WebApplication MapUserRoutes(this WebApplication app)
{
app.MapGet("/api2/json/access/users", () =>
Results.Content(MockPveServer.GetFixture("pve9_users"), "application/json"));
app.MapGet("/api2/json/access/users/{userid}", (string userid) =>
{
// Return first user from fixture
var fixture = MockPveServer.GetFixture("pve9_users");
try
{
var doc = System.Text.Json.JsonDocument.Parse(fixture);
var dataArray = doc.RootElement.GetProperty("data");
var first = dataArray[0];
return Results.Content($"{{\"data\":{first.GetRawText()}}}", "application/json");
}
catch
{
return Results.Content("{\"data\":null}", "application/json");
}
});
app.MapPost("/api2/json/access/users", () =>
Results.Content("{\"data\":null}", "application/json"));
app.MapPut("/api2/json/access/users/{userid}", (string userid) =>
Results.Content("{\"data\":null}", "application/json"));
app.MapDelete("/api2/json/access/users/{userid}", (string userid) =>
Results.Content("{\"data\":null}", "application/json"));
// Roles
app.MapGet("/api2/json/access/roles", () =>
Results.Content(MockPveServer.GetFixture("pve9_roles"), "application/json"));
app.MapPost("/api2/json/access/roles", () =>
Results.Content("{\"data\":null}", "application/json"));
app.MapDelete("/api2/json/access/roles/{roleid}", (string roleid) =>
Results.Content("{\"data\":null}", "application/json"));
// ACL
app.MapGet("/api2/json/access/acl", () =>
Results.Content("{\"data\":[]}", "application/json"));
app.MapPut("/api2/json/access/acl", () =>
Results.Content("{\"data\":null}", "application/json"));
return app;
}
}
@@ -0,0 +1,15 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
namespace PSProxmoxVE.MockServer.Routes;
public static class VersionRoutes
{
public static WebApplication MapVersionRoutes(this WebApplication app)
{
app.MapGet("/api2/json/version", () =>
Results.Content(MockPveServer.GetFixture("pve9_version"), "application/json"));
return app;
}
}
@@ -0,0 +1,77 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
namespace PSProxmoxVE.MockServer.Routes;
public static class VmRoutes
{
public static WebApplication MapVmRoutes(this WebApplication app)
{
app.MapGet("/api2/json/nodes/{node}/qemu", (string node) =>
Results.Content(MockPveServer.GetFixture("pve9_vms"), "application/json"));
app.MapGet("/api2/json/nodes/{node}/qemu/{vmid}/config", (string node, int vmid) =>
Results.Content(MockPveServer.GetFixture("pve9_vm_config"), "application/json"));
app.MapGet("/api2/json/nodes/{node}/qemu/{vmid}/status/current", (string node, int vmid) =>
{
// Return first VM from fixture as a single-item response
var fixture = MockPveServer.GetFixture("pve9_vms");
try
{
var doc = System.Text.Json.JsonDocument.Parse(fixture);
var dataArray = doc.RootElement.GetProperty("data");
foreach (var item in dataArray.EnumerateArray())
{
if (item.TryGetProperty("vmid", out var id) && id.GetInt32() == vmid)
return Results.Content($"{{\"data\":{item.GetRawText()}}}", "application/json");
}
// If no match, return first item
var first = dataArray[0];
return Results.Content($"{{\"data\":{first.GetRawText()}}}", "application/json");
}
catch
{
return Results.Content("{\"data\":null}", "application/json");
}
});
app.MapPost("/api2/json/nodes/{node}/qemu", (string node) =>
Results.Content($"{{\"data\":\"{MockPveServer.GenerateUpid(node, "qmcreate")}\"}}", "application/json"));
app.MapPost("/api2/json/nodes/{node}/qemu/{vmid}/status/start", (string node, int vmid) =>
Results.Content($"{{\"data\":\"{MockPveServer.GenerateUpid(node, "qmstart", vmid)}\"}}", "application/json"));
app.MapPost("/api2/json/nodes/{node}/qemu/{vmid}/status/stop", (string node, int vmid) =>
Results.Content($"{{\"data\":\"{MockPveServer.GenerateUpid(node, "qmstop", vmid)}\"}}", "application/json"));
app.MapPost("/api2/json/nodes/{node}/qemu/{vmid}/status/shutdown", (string node, int vmid) =>
Results.Content($"{{\"data\":\"{MockPveServer.GenerateUpid(node, "qmshutdown", vmid)}\"}}", "application/json"));
app.MapPost("/api2/json/nodes/{node}/qemu/{vmid}/status/reset", (string node, int vmid) =>
Results.Content($"{{\"data\":\"{MockPveServer.GenerateUpid(node, "qmreset", vmid)}\"}}", "application/json"));
app.MapPost("/api2/json/nodes/{node}/qemu/{vmid}/status/suspend", (string node, int vmid) =>
Results.Content($"{{\"data\":\"{MockPveServer.GenerateUpid(node, "qmsuspend", vmid)}\"}}", "application/json"));
app.MapPost("/api2/json/nodes/{node}/qemu/{vmid}/status/resume", (string node, int vmid) =>
Results.Content($"{{\"data\":\"{MockPveServer.GenerateUpid(node, "qmresume", vmid)}\"}}", "application/json"));
app.MapPost("/api2/json/nodes/{node}/qemu/{vmid}/clone", (string node, int vmid) =>
Results.Content($"{{\"data\":\"{MockPveServer.GenerateUpid(node, "qmclone", vmid)}\"}}", "application/json"));
app.MapPost("/api2/json/nodes/{node}/qemu/{vmid}/migrate", (string node, int vmid) =>
Results.Content($"{{\"data\":\"{MockPveServer.GenerateUpid(node, "qmigrate", vmid)}\"}}", "application/json"));
app.MapPut("/api2/json/nodes/{node}/qemu/{vmid}/config", (string node, int vmid) =>
Results.Content("{\"data\":null}", "application/json"));
app.MapPut("/api2/json/nodes/{node}/qemu/{vmid}/resize", (string node, int vmid) =>
Results.Content("{\"data\":null}", "application/json"));
app.MapDelete("/api2/json/nodes/{node}/qemu/{vmid}", (string node, int vmid) =>
Results.Content($"{{\"data\":\"{MockPveServer.GenerateUpid(node, "qmdestroy", vmid)}\"}}", "application/json"));
return app;
}
}