mirror of
https://github.com/GoodOlClint/PSProxmoxVE.git
synced 2026-07-26 07:58:14 +00:00
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:
+47
-1
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user