diff --git a/tests/PSProxmoxVE.Core.Tests/Authentication/PveAuthenticatorTests.cs b/tests/PSProxmoxVE.Core.Tests/Authentication/PveAuthenticatorTests.cs new file mode 100644 index 0000000..654714d --- /dev/null +++ b/tests/PSProxmoxVE.Core.Tests/Authentication/PveAuthenticatorTests.cs @@ -0,0 +1,85 @@ +using System; +using Xunit; +using PSProxmoxVE.Core.Authentication; + +namespace PSProxmoxVE.Core.Tests.Authentication +{ + /// + /// Tests for PveAuthenticator input validation. These tests exercise the + /// argument-validation logic without making actual HTTP calls. + /// + public class PveAuthenticatorTests + { + private const string ValidHostname = "pve.example.com"; + private const int ValidPort = 8006; + + [Fact] + public void ValidateUsername_MissingAtSign_Throws() + { + var ex = Assert.Throws(() => + PveAuthenticator.AuthenticateWithCredentials(ValidHostname, ValidPort, false, "rootpam", "password")); + + Assert.Contains("realm", ex.Message, StringComparison.OrdinalIgnoreCase); + } + + [Fact] + public void ValidateApiToken_InvalidFormat_NoTokenId_Throws() + { + // Missing the !TOKENID part — no exclamation mark + var ex = Assert.Throws(() => + PveAuthenticator.AuthenticateWithApiToken(ValidHostname, ValidPort, false, "root@pam=someuuid")); + + Assert.Contains("token", ex.Message, StringComparison.OrdinalIgnoreCase); + } + + [Fact] + public void ValidateApiToken_InvalidFormat_NoEquals_Throws() + { + // Has ! but no = sign after TOKENID + var ex = Assert.Throws(() => + PveAuthenticator.AuthenticateWithApiToken(ValidHostname, ValidPort, false, "root@pam!mytoken")); + + Assert.Contains("token", ex.Message, StringComparison.OrdinalIgnoreCase); + } + + [Fact] + public void ValidateApiToken_InvalidFormat_NoRealm_Throws() + { + // Missing the @realm portion + var ex = Assert.Throws(() => + PveAuthenticator.AuthenticateWithApiToken(ValidHostname, ValidPort, false, "root!mytoken=someuuid")); + + Assert.Contains("token", ex.Message, StringComparison.OrdinalIgnoreCase); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData(" ")] + public void ValidateHostname_NullOrEmpty_Throws_ForCredentials(string hostname) + { + Assert.Throws(() => + PveAuthenticator.AuthenticateWithCredentials(hostname, ValidPort, false, "root@pam", "password")); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData(" ")] + public void ValidateHostname_NullOrEmpty_Throws_ForApiToken(string hostname) + { + Assert.Throws(() => + PveAuthenticator.AuthenticateWithApiToken(hostname, ValidPort, false, "root@pam!mytoken=uuid")); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData(" ")] + public void ValidateApiToken_NullOrEmpty_Throws(string token) + { + Assert.Throws(() => + PveAuthenticator.AuthenticateWithApiToken(ValidHostname, ValidPort, false, token)); + } + } +} diff --git a/tests/PSProxmoxVE.Core.Tests/Authentication/PveSessionTests.cs b/tests/PSProxmoxVE.Core.Tests/Authentication/PveSessionTests.cs new file mode 100644 index 0000000..af9d753 --- /dev/null +++ b/tests/PSProxmoxVE.Core.Tests/Authentication/PveSessionTests.cs @@ -0,0 +1,104 @@ +using System; +using Xunit; +using PSProxmoxVE.Core.Authentication; + +namespace PSProxmoxVE.Core.Tests.Authentication +{ + public class PveSessionTests + { + private const string TestHostname = "pve.example.com"; + private const int TestPort = 8006; + + [Fact] + public void TicketSession_NotExpired_OnCreation() + { + var expiry = DateTime.UtcNow.AddHours(2); + var session = new PveSession(TestHostname, TestPort, false, "PVE:root@pam:TICKET", "CSRFTOKEN", expiry); + + Assert.False(session.IsExpired); + } + + [Fact] + public void TicketSession_IsExpired_After2Hours() + { + // Simulate a ticket that already expired an hour ago + var expiry = DateTime.UtcNow.AddHours(-1); + var session = new PveSession(TestHostname, TestPort, false, "PVE:root@pam:OLDTICKET", "CSRFTOKEN", expiry); + + Assert.True(session.IsExpired); + } + + [Fact] + public void ApiTokenSession_NeverExpires() + { + var session = new PveSession(TestHostname, TestPort, false, "root@pam!mytoken=aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"); + + Assert.False(session.IsExpired); + Assert.Equal(PveAuthMode.ApiToken, session.AuthMode); + } + + [Fact] + public void BaseUrl_CorrectFormat() + { + var session = new PveSession(TestHostname, TestPort, false, "root@pam!mytoken=aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"); + + Assert.Equal($"https://{TestHostname}:{TestPort}/api2/json", session.BaseUrl); + } + + [Fact] + public void TicketSession_AuthMode_IsTicket() + { + var expiry = DateTime.UtcNow.AddHours(2); + var session = new PveSession(TestHostname, TestPort, false, "PVE:root@pam:TICKET", "CSRFTOKEN", expiry); + + Assert.Equal(PveAuthMode.Ticket, session.AuthMode); + } + + [Fact] + public void SeparateSessions_DoNotShareState() + { + var expiry1 = DateTime.UtcNow.AddHours(2); + var session1 = new PveSession("host1", 8006, false, "PVE:root@pam:TICKET1", "CSRF1", expiry1); + var session2 = new PveSession("host2", 8006, false, "root@pam!token=aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"); + + Assert.NotEqual(session1.Hostname, session2.Hostname); + Assert.Equal(PveAuthMode.Ticket, session1.AuthMode); + Assert.Equal(PveAuthMode.ApiToken, session2.AuthMode); + Assert.Equal("PVE:root@pam:TICKET1", session1.Ticket); + Assert.Null(session1.ApiToken); + Assert.Null(session2.Ticket); + Assert.NotNull(session2.ApiToken); + } + + [Fact] + public void TicketSession_StoresTicketAndCsrf() + { + var expiry = DateTime.UtcNow.AddHours(2); + const string ticket = "PVE:root@pam:ABCD1234"; + const string csrf = "CSRFPREVENTION"; + + var session = new PveSession(TestHostname, TestPort, false, ticket, csrf, expiry); + + Assert.Equal(ticket, session.Ticket); + Assert.Equal(csrf, session.CsrfToken); + } + + [Fact] + public void ApiTokenSession_StoresApiToken() + { + const string token = "root@pam!mytoken=aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"; + var session = new PveSession(TestHostname, TestPort, false, token); + + Assert.Equal(token, session.ApiToken); + } + + [Fact] + public void SkipCertificateCheck_IsStored() + { + var session = new PveSession(TestHostname, TestPort, skipCertificateCheck: true, + "root@pam!mytoken=aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"); + + Assert.True(session.SkipCertificateCheck); + } + } +} diff --git a/tests/PSProxmoxVE.Core.Tests/Authentication/PveVersionTests.cs b/tests/PSProxmoxVE.Core.Tests/Authentication/PveVersionTests.cs new file mode 100644 index 0000000..6e85816 --- /dev/null +++ b/tests/PSProxmoxVE.Core.Tests/Authentication/PveVersionTests.cs @@ -0,0 +1,91 @@ +using System; +using Xunit; +using PSProxmoxVE.Core.Authentication; + +namespace PSProxmoxVE.Core.Tests.Authentication +{ + public class PveVersionTests + { + [Fact] + public void Parse_ValidVersion_9_1() + { + var version = PveVersion.Parse("9.1-1"); + Assert.Equal(9, version.Major); + Assert.Equal(1, version.Minor); + } + + [Fact] + public void Parse_ValidVersion_8_3() + { + var version = PveVersion.Parse("8.3-2"); + Assert.Equal(8, version.Major); + Assert.Equal(3, version.Minor); + } + + [Fact] + public void Parse_ValidVersion_8_0() + { + var version = PveVersion.Parse("8.0-1"); + Assert.Equal(8, version.Major); + Assert.Equal(0, version.Minor); + } + + [Fact] + public void Parse_NoPatchLevel() + { + var version = PveVersion.Parse("9.1"); + Assert.Equal(9, version.Major); + Assert.Equal(1, version.Minor); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData(" ")] + public void Parse_NullOrEmpty_Throws(string input) + { + Assert.Throws(() => PveVersion.Parse(input)); + } + + [Fact] + public void Parse_InvalidFormat_Throws() + { + Assert.Throws(() => PveVersion.Parse("invalid")); + } + + [Fact] + public void IsAtLeast_True_SameMajorLowerMinor() + { + var version = PveVersion.Parse("9.1-1"); + Assert.True(version.IsAtLeast(9, 0)); + } + + [Fact] + public void IsAtLeast_True_LowerMajor() + { + var version = PveVersion.Parse("9.1-1"); + Assert.True(version.IsAtLeast(8, 0)); + } + + [Fact] + public void IsAtLeast_False() + { + var version = PveVersion.Parse("8.3-2"); + Assert.False(version.IsAtLeast(9, 0)); + } + + [Fact] + public void IsAtLeast_ExactMatch() + { + var version = PveVersion.Parse("8.0-1"); + Assert.True(version.IsAtLeast(8, 0)); + } + + [Fact] + public void ToString_Format() + { + var version = PveVersion.Parse("9.1-1"); + Assert.Equal("9.1", version.ToString()); + } + } +} diff --git a/tests/PSProxmoxVE.Core.Tests/Fixtures/pve8_nodes.json b/tests/PSProxmoxVE.Core.Tests/Fixtures/pve8_nodes.json new file mode 100644 index 0000000..405ce8e --- /dev/null +++ b/tests/PSProxmoxVE.Core.Tests/Fixtures/pve8_nodes.json @@ -0,0 +1 @@ +{"data":[{"node":"pve-old","status":"online","maxcpu":4,"maxmem":16106127360,"mem":4294967296,"uptime":172800,"pveversion":"pve-manager/8.3-2/12345678","kversion":"6.5.13-6-pve","loadavg":["0.10","0.15","0.12"]}]} diff --git a/tests/PSProxmoxVE.Core.Tests/Fixtures/pve8_version.json b/tests/PSProxmoxVE.Core.Tests/Fixtures/pve8_version.json new file mode 100644 index 0000000..3497b61 --- /dev/null +++ b/tests/PSProxmoxVE.Core.Tests/Fixtures/pve8_version.json @@ -0,0 +1 @@ +{"data":{"version":"8.3-2","release":"8.3","repoid":"12345678","console":"xtermjs","keyboard":"en-us"}} diff --git a/tests/PSProxmoxVE.Core.Tests/Fixtures/pve9_cluster_status.json b/tests/PSProxmoxVE.Core.Tests/Fixtures/pve9_cluster_status.json new file mode 100644 index 0000000..155ca2e --- /dev/null +++ b/tests/PSProxmoxVE.Core.Tests/Fixtures/pve9_cluster_status.json @@ -0,0 +1 @@ +{"data":[{"type":"cluster","name":"testcluster","nodes":3,"quorate":1,"version":3},{"type":"node","name":"pve1","online":1,"local":1,"nodeid":1,"ip":"192.168.1.10","level":""},{"type":"node","name":"pve2","online":1,"local":0,"nodeid":2,"ip":"192.168.1.11","level":""},{"type":"node","name":"pve3","online":0,"local":0,"nodeid":3,"ip":"192.168.1.12","level":""}]} diff --git a/tests/PSProxmoxVE.Core.Tests/Fixtures/pve9_containers.json b/tests/PSProxmoxVE.Core.Tests/Fixtures/pve9_containers.json new file mode 100644 index 0000000..1396463 --- /dev/null +++ b/tests/PSProxmoxVE.Core.Tests/Fixtures/pve9_containers.json @@ -0,0 +1 @@ +{"data":[{"vmid":200,"name":"ct-web","status":"running","node":"pve1","cpus":2,"maxmem":2147483648,"maxswap":536870912,"maxdisk":8589934592,"ostype":"ubuntu","unprivileged":1,"uptime":43200,"tags":"web"},{"vmid":201,"name":"ct-db","status":"stopped","node":"pve1","cpus":1,"maxmem":1073741824,"maxswap":268435456,"maxdisk":4294967296,"ostype":"debian","unprivileged":1,"uptime":0}]} diff --git a/tests/PSProxmoxVE.Core.Tests/Fixtures/pve9_networks.json b/tests/PSProxmoxVE.Core.Tests/Fixtures/pve9_networks.json new file mode 100644 index 0000000..a559447 --- /dev/null +++ b/tests/PSProxmoxVE.Core.Tests/Fixtures/pve9_networks.json @@ -0,0 +1 @@ +{"data":[{"iface":"vmbr0","type":"bridge","address":"192.168.1.10","netmask":"255.255.255.0","gateway":"192.168.1.1","bridge_ports":"enp0s31f6","autostart":1,"active":1,"cidr":"192.168.1.10/24"},{"iface":"vmbr1","type":"bridge","address":"10.0.0.1","netmask":"255.255.255.0","bridge_ports":"enp2s0","autostart":1,"active":1,"cidr":"10.0.0.1/24","bridge_vlan_aware":1}]} diff --git a/tests/PSProxmoxVE.Core.Tests/Fixtures/pve9_node_status.json b/tests/PSProxmoxVE.Core.Tests/Fixtures/pve9_node_status.json new file mode 100644 index 0000000..10b1f60 --- /dev/null +++ b/tests/PSProxmoxVE.Core.Tests/Fixtures/pve9_node_status.json @@ -0,0 +1 @@ +{"data":{"node":"pve1","status":"online","maxcpu":16,"maxmem":68719476736,"mem":17179869184,"uptime":864000,"cpu":0.125,"rootfs":{"total":107374182400,"used":21474836480,"avail":85899345920,"free":85899345920},"swap":{"total":8589934592,"used":1073741824,"free":7516192768},"netin":549755813888,"netout":274877906944,"loadavg":["0.45","0.32","0.28"],"pveversion":"pve-manager/9.1-1/abcdef12","kversion":"6.8.12-5-pve"}} diff --git a/tests/PSProxmoxVE.Core.Tests/Fixtures/pve9_nodes.json b/tests/PSProxmoxVE.Core.Tests/Fixtures/pve9_nodes.json new file mode 100644 index 0000000..69790e2 --- /dev/null +++ b/tests/PSProxmoxVE.Core.Tests/Fixtures/pve9_nodes.json @@ -0,0 +1 @@ +{"data":[{"node":"pve1","status":"online","maxcpu":16,"maxmem":68719476736,"mem":17179869184,"uptime":864000,"pveversion":"pve-manager/9.1-1/abcdef12","kversion":"6.8.12-5-pve","loadavg":["0.45","0.32","0.28"]},{"node":"pve2","status":"online","maxcpu":8,"maxmem":34359738368,"mem":8589934592,"uptime":432000,"pveversion":"pve-manager/9.1-1/abcdef12","kversion":"6.8.12-5-pve","loadavg":["0.12","0.08","0.06"]}]} diff --git a/tests/PSProxmoxVE.Core.Tests/Fixtures/pve9_roles.json b/tests/PSProxmoxVE.Core.Tests/Fixtures/pve9_roles.json new file mode 100644 index 0000000..01372bc --- /dev/null +++ b/tests/PSProxmoxVE.Core.Tests/Fixtures/pve9_roles.json @@ -0,0 +1 @@ +{"data":[{"roleid":"Administrator","privs":"Datastore.Allocate,Datastore.AllocateSpace,Datastore.AllocateTemplate,Datastore.Audit,Group.Allocate,Mapping.Audit,Mapping.Modify,Mapping.Use,Permissions.Modify,Pool.Allocate,Pool.Audit,Realm.Allocate,Realm.AllocateUser,SDN.Allocate,SDN.Audit,SDN.Use,Sys.Audit,Sys.Console,Sys.Incoming,Sys.Modify,Sys.PowerMgmt,Sys.Syslog,User.Modify,VM.Allocate,VM.Audit,VM.Backup,VM.Clone,VM.Config.CDROM,VM.Config.CPU,VM.Config.Cloudinit,VM.Config.Disk,VM.Config.HWType,VM.Config.Memory,VM.Config.Net,VM.Config.Options,VM.Console,VM.Migrate,VM.Monitor,VM.PowerMgmt,VM.Snapshot,VM.Snapshot.Rollback","special":1},{"roleid":"PVEAuditor","privs":"Datastore.Audit,Mapping.Audit,Pool.Audit,SDN.Audit,Sys.Audit,VM.Audit","special":1}]} diff --git a/tests/PSProxmoxVE.Core.Tests/Fixtures/pve9_sdn_vnets.json b/tests/PSProxmoxVE.Core.Tests/Fixtures/pve9_sdn_vnets.json new file mode 100644 index 0000000..4c75e91 --- /dev/null +++ b/tests/PSProxmoxVE.Core.Tests/Fixtures/pve9_sdn_vnets.json @@ -0,0 +1 @@ +{"data":[{"vnet":"myvnet","zone":"localzone","tag":100,"alias":"My VNET"},{"vnet":"prodnet","zone":"vxlanzone","tag":200,"alias":"Production Network","vlanaware":1}]} diff --git a/tests/PSProxmoxVE.Core.Tests/Fixtures/pve9_sdn_zones.json b/tests/PSProxmoxVE.Core.Tests/Fixtures/pve9_sdn_zones.json new file mode 100644 index 0000000..3010e84 --- /dev/null +++ b/tests/PSProxmoxVE.Core.Tests/Fixtures/pve9_sdn_zones.json @@ -0,0 +1 @@ +{"data":[{"zone":"localzone","type":"simple","dns":"","reversedns":"","dnszone":"","mtu":1500,"pending":0},{"zone":"vxlanzone","type":"vxlan","dns":"","reversedns":"","dnszone":"","mtu":1450,"nodes":"pve1,pve2","pending":0}]} diff --git a/tests/PSProxmoxVE.Core.Tests/Fixtures/pve9_snapshots.json b/tests/PSProxmoxVE.Core.Tests/Fixtures/pve9_snapshots.json new file mode 100644 index 0000000..360fec5 --- /dev/null +++ b/tests/PSProxmoxVE.Core.Tests/Fixtures/pve9_snapshots.json @@ -0,0 +1 @@ +{"data":[{"name":"current","description":"You are here!","snaptime":0,"parent":"snap1"},{"name":"snap1","description":"Before upgrade","snaptime":1710000000,"vmstate":1,"parent":"base"},{"name":"base","description":"Initial setup","snaptime":1709000000,"vmstate":0}]} diff --git a/tests/PSProxmoxVE.Core.Tests/Fixtures/pve9_storage.json b/tests/PSProxmoxVE.Core.Tests/Fixtures/pve9_storage.json new file mode 100644 index 0000000..a438908 --- /dev/null +++ b/tests/PSProxmoxVE.Core.Tests/Fixtures/pve9_storage.json @@ -0,0 +1 @@ +{"data":[{"storage":"local","type":"dir","content":"iso,vztmpl,backup","total":107374182400,"used":21474836480,"avail":85899345920,"enabled":1,"shared":0,"active":1},{"storage":"local-lvm","type":"lvmthin","content":"images,rootdir","total":536870912000,"used":107374182400,"avail":429496729600,"enabled":1,"shared":0,"active":1},{"storage":"ceph-pool","type":"rbd","content":"images","total":1099511627776,"used":214748364800,"avail":884763262976,"enabled":1,"shared":1,"active":1}]} diff --git a/tests/PSProxmoxVE.Core.Tests/Fixtures/pve9_storage_content.json b/tests/PSProxmoxVE.Core.Tests/Fixtures/pve9_storage_content.json new file mode 100644 index 0000000..21097a9 --- /dev/null +++ b/tests/PSProxmoxVE.Core.Tests/Fixtures/pve9_storage_content.json @@ -0,0 +1 @@ +{"data":[{"volid":"local:iso/ubuntu-24.04-live-server-amd64.iso","content":"iso","format":"iso","size":2774532096,"ctime":1710000000},{"volid":"local:iso/debian-12.5.0-amd64-netinst.iso","content":"iso","format":"iso","size":658505728,"ctime":1709000000}]} diff --git a/tests/PSProxmoxVE.Core.Tests/Fixtures/pve9_task_running.json b/tests/PSProxmoxVE.Core.Tests/Fixtures/pve9_task_running.json new file mode 100644 index 0000000..b15ab79 --- /dev/null +++ b/tests/PSProxmoxVE.Core.Tests/Fixtures/pve9_task_running.json @@ -0,0 +1 @@ +{"data":{"upid":"UPID:pve1:00001234:00ABCDEF:65F00000:qmstart:100:root@pam:","type":"qmstart","status":"running","node":"pve1","starttime":1710000000,"user":"root@pam","id":"100"}} diff --git a/tests/PSProxmoxVE.Core.Tests/Fixtures/pve9_tasks.json b/tests/PSProxmoxVE.Core.Tests/Fixtures/pve9_tasks.json new file mode 100644 index 0000000..0c21de8 --- /dev/null +++ b/tests/PSProxmoxVE.Core.Tests/Fixtures/pve9_tasks.json @@ -0,0 +1 @@ +{"data":{"upid":"UPID:pve1:00001234:00ABCDEF:65F00000:qmstart:100:root@pam:","type":"qmstart","status":"stopped","exitstatus":"OK","node":"pve1","starttime":1710000000,"endtime":1710000010,"user":"root@pam","id":"100"}} diff --git a/tests/PSProxmoxVE.Core.Tests/Fixtures/pve9_ticket_response.json b/tests/PSProxmoxVE.Core.Tests/Fixtures/pve9_ticket_response.json new file mode 100644 index 0000000..dafb08a --- /dev/null +++ b/tests/PSProxmoxVE.Core.Tests/Fixtures/pve9_ticket_response.json @@ -0,0 +1 @@ +{"data":{"username":"root@pam","ticket":"PVE:root@pam:65F00000::abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890","CSRFPreventionToken":"65F00000:abcdef1234567890abcdef12"}} diff --git a/tests/PSProxmoxVE.Core.Tests/Fixtures/pve9_users.json b/tests/PSProxmoxVE.Core.Tests/Fixtures/pve9_users.json new file mode 100644 index 0000000..8d6e46d --- /dev/null +++ b/tests/PSProxmoxVE.Core.Tests/Fixtures/pve9_users.json @@ -0,0 +1 @@ +{"data":[{"userid":"root@pam","firstname":"Root","lastname":"User","email":"root@example.com","enable":1,"realm":"pam","expire":0},{"userid":"admin@pve","firstname":"Admin","lastname":"User","email":"admin@example.com","enable":1,"realm":"pve","groups":"admins","expire":0},{"userid":"readonly@pve","firstname":"Read","lastname":"Only","email":"readonly@example.com","enable":1,"realm":"pve","groups":"audit","comment":"Read-only audit user","expire":0}]} diff --git a/tests/PSProxmoxVE.Core.Tests/Fixtures/pve9_version.json b/tests/PSProxmoxVE.Core.Tests/Fixtures/pve9_version.json new file mode 100644 index 0000000..2ccbe5a --- /dev/null +++ b/tests/PSProxmoxVE.Core.Tests/Fixtures/pve9_version.json @@ -0,0 +1 @@ +{"data":{"version":"9.1-1","release":"9.1","repoid":"abcdef12","console":"xtermjs","keyboard":"en-us"}} diff --git a/tests/PSProxmoxVE.Core.Tests/Fixtures/pve9_vm_config.json b/tests/PSProxmoxVE.Core.Tests/Fixtures/pve9_vm_config.json new file mode 100644 index 0000000..eb41209 --- /dev/null +++ b/tests/PSProxmoxVE.Core.Tests/Fixtures/pve9_vm_config.json @@ -0,0 +1 @@ +{"data":{"cores":4,"sockets":1,"memory":8192,"cpu":"host","bios":"ovmf","machine":"q35","boot":"order=scsi0;net0","description":"Test VM","tags":"prod;web","protection":0,"numa":0,"balloon":0,"ostype":"l26","virtio0":"local-lvm:vm-100-disk-0,size=100G","net0":"virtio=AA:BB:CC:DD:EE:FF,bridge=vmbr0,firewall=1","ide2":"local:iso/ubuntu-24.04-live-server-amd64.iso,media=cdrom","scsi0":"local-lvm:vm-100-disk-0,size=100G","scsihw":"virtio-scsi-single","ciuser":"admin","nameserver":"8.8.8.8","searchdomain":"example.com","ipconfig0":"ip=dhcp"}} diff --git a/tests/PSProxmoxVE.Core.Tests/Fixtures/pve9_vms.json b/tests/PSProxmoxVE.Core.Tests/Fixtures/pve9_vms.json new file mode 100644 index 0000000..300ebb7 --- /dev/null +++ b/tests/PSProxmoxVE.Core.Tests/Fixtures/pve9_vms.json @@ -0,0 +1 @@ +{"data":[{"vmid":100,"name":"test-vm-1","status":"running","node":"pve1","cpus":4,"maxmem":8589934592,"maxdisk":107374182400,"uptime":86400,"tags":"prod;web","template":0,"pid":12345,"qmpstatus":"running","agent":1},{"vmid":101,"name":"test-vm-2","status":"stopped","node":"pve1","cpus":2,"maxmem":4294967296,"maxdisk":53687091200,"uptime":0,"tags":"dev","template":0},{"vmid":9000,"name":"ubuntu-template","status":"stopped","node":"pve1","cpus":2,"maxmem":4294967296,"maxdisk":53687091200,"uptime":0,"template":1}]} diff --git a/tests/PSProxmoxVE.Core.Tests/Fixtures/pve_api_error.json b/tests/PSProxmoxVE.Core.Tests/Fixtures/pve_api_error.json new file mode 100644 index 0000000..dcb5492 --- /dev/null +++ b/tests/PSProxmoxVE.Core.Tests/Fixtures/pve_api_error.json @@ -0,0 +1 @@ +{"errors":{"vmid":"property is missing and it is not optional"},"data":null} diff --git a/tests/PSProxmoxVE.Core.Tests/Models/ClusterModelTests.cs b/tests/PSProxmoxVE.Core.Tests/Models/ClusterModelTests.cs new file mode 100644 index 0000000..d1a7ca3 --- /dev/null +++ b/tests/PSProxmoxVE.Core.Tests/Models/ClusterModelTests.cs @@ -0,0 +1,135 @@ +using Xunit; +using Newtonsoft.Json.Linq; +using PSProxmoxVE.Core.Models.Cluster; + +namespace PSProxmoxVE.Core.Tests.Models +{ + public class ClusterModelTests + { + [Fact] + public void PveClusterStatus_Deserialize_Pve9_ReturnsCorrectCount() + { + var json = TestHelper.LoadFixture("pve9_cluster_status.json"); + var data = JObject.Parse(json)["data"]; + var entries = data.ToObject(); + Assert.NotNull(entries); + Assert.Equal(4, entries.Length); + } + + [Fact] + public void PveClusterStatus_Deserialize_Pve9_FirstEntry_IsClusterType() + { + var json = TestHelper.LoadFixture("pve9_cluster_status.json"); + var data = JObject.Parse(json)["data"]; + var entries = data.ToObject(); + Assert.NotNull(entries); + Assert.Equal("cluster", entries[0].Type); + } + + [Fact] + public void PveClusterStatus_Deserialize_Pve9_ClusterEntry_HasCorrectName() + { + var json = TestHelper.LoadFixture("pve9_cluster_status.json"); + var data = JObject.Parse(json)["data"]; + var entries = data.ToObject(); + Assert.NotNull(entries); + Assert.Equal("testcluster", entries[0].Name); + } + + [Fact] + public void PveClusterStatus_Deserialize_Pve9_ClusterEntry_HasNodeCount() + { + var json = TestHelper.LoadFixture("pve9_cluster_status.json"); + var data = JObject.Parse(json)["data"]; + var entries = data.ToObject(); + Assert.NotNull(entries); + Assert.Equal(3, entries[0].Nodes); + } + + [Fact] + public void PveClusterStatus_Deserialize_Pve9_ClusterEntry_IsQuorate() + { + var json = TestHelper.LoadFixture("pve9_cluster_status.json"); + var data = JObject.Parse(json)["data"]; + var entries = data.ToObject(); + Assert.NotNull(entries); + Assert.Equal(1, entries[0].Quorate); + } + + [Fact] + public void PveClusterStatus_Deserialize_Pve9_SecondEntry_IsNodeType() + { + var json = TestHelper.LoadFixture("pve9_cluster_status.json"); + var data = JObject.Parse(json)["data"]; + var entries = data.ToObject(); + Assert.NotNull(entries); + Assert.Equal("node", entries[1].Type); + } + + [Fact] + public void PveClusterStatus_Deserialize_Pve9_FirstNodeEntry_HasCorrectIp() + { + var json = TestHelper.LoadFixture("pve9_cluster_status.json"); + var data = JObject.Parse(json)["data"]; + var entries = data.ToObject(); + Assert.NotNull(entries); + Assert.Equal("192.168.1.10", entries[1].Ip); + } + + [Fact] + public void PveClusterStatus_Deserialize_Pve9_FirstNodeEntry_IsOnline() + { + var json = TestHelper.LoadFixture("pve9_cluster_status.json"); + var data = JObject.Parse(json)["data"]; + var entries = data.ToObject(); + Assert.NotNull(entries); + Assert.Equal(1, entries[1].Online); + } + + [Fact] + public void PveClusterStatus_Deserialize_Pve9_FirstNodeEntry_IsLocal() + { + var json = TestHelper.LoadFixture("pve9_cluster_status.json"); + var data = JObject.Parse(json)["data"]; + var entries = data.ToObject(); + Assert.NotNull(entries); + Assert.Equal(1, entries[1].Local); + } + + [Fact] + public void PveClusterStatus_Deserialize_Pve9_ThirdNode_IsOffline() + { + var json = TestHelper.LoadFixture("pve9_cluster_status.json"); + var data = JObject.Parse(json)["data"]; + var entries = data.ToObject(); + Assert.NotNull(entries); + // Fourth entry (index 3) is pve3 which is offline + Assert.Equal("node", entries[3].Type); + Assert.Equal(0, entries[3].Online); + } + + [Fact] + public void PveClusterStatus_Deserialize_Pve9_NodeFields_NullForClusterEntry() + { + var json = TestHelper.LoadFixture("pve9_cluster_status.json"); + var data = JObject.Parse(json)["data"]; + var entries = data.ToObject(); + Assert.NotNull(entries); + // Cluster-type entry should not have node-specific fields + Assert.Null(entries[0].Ip); + Assert.Null(entries[0].Online); + } + + [Fact] + public void PveClusterStatus_Deserialize_Pve9_ClusterFields_NullForNodeEntry() + { + var json = TestHelper.LoadFixture("pve9_cluster_status.json"); + var data = JObject.Parse(json)["data"]; + var entries = data.ToObject(); + Assert.NotNull(entries); + // Node-type entries should not have cluster-specific fields + Assert.Null(entries[1].Nodes); + Assert.Null(entries[1].Quorate); + } + } +} diff --git a/tests/PSProxmoxVE.Core.Tests/Models/ContainerModelTests.cs b/tests/PSProxmoxVE.Core.Tests/Models/ContainerModelTests.cs new file mode 100644 index 0000000..a95d11a --- /dev/null +++ b/tests/PSProxmoxVE.Core.Tests/Models/ContainerModelTests.cs @@ -0,0 +1,125 @@ +using Xunit; +using Newtonsoft.Json.Linq; +using PSProxmoxVE.Core.Models.Containers; + +namespace PSProxmoxVE.Core.Tests.Models +{ + public class ContainerModelTests + { + [Fact] + public void PveContainer_Deserialize_Pve9_ReturnsCorrectCount() + { + var json = TestHelper.LoadFixture("pve9_containers.json"); + var data = JObject.Parse(json)["data"]; + var containers = data.ToObject(); + Assert.NotNull(containers); + Assert.Equal(2, containers.Length); + } + + [Fact] + public void PveContainer_Deserialize_Pve9_FirstContainer_HasCorrectVmId() + { + var json = TestHelper.LoadFixture("pve9_containers.json"); + var data = JObject.Parse(json)["data"]; + var containers = data.ToObject(); + Assert.NotNull(containers); + Assert.Equal(200, containers[0].VmId); + } + + [Fact] + public void PveContainer_Deserialize_Pve9_FirstContainer_HasCorrectName() + { + var json = TestHelper.LoadFixture("pve9_containers.json"); + var data = JObject.Parse(json)["data"]; + var containers = data.ToObject(); + Assert.NotNull(containers); + Assert.Equal("ct-web", containers[0].Name); + } + + [Fact] + public void PveContainer_Deserialize_Pve9_FirstContainer_HasCorrectStatus() + { + var json = TestHelper.LoadFixture("pve9_containers.json"); + var data = JObject.Parse(json)["data"]; + var containers = data.ToObject(); + Assert.NotNull(containers); + Assert.Equal("running", containers[0].Status); + } + + [Fact] + public void PveContainer_Deserialize_Pve9_FirstContainer_HasCorrectNode() + { + var json = TestHelper.LoadFixture("pve9_containers.json"); + var data = JObject.Parse(json)["data"]; + var containers = data.ToObject(); + Assert.NotNull(containers); + Assert.Equal("pve1", containers[0].Node); + } + + [Fact] + public void PveContainer_Deserialize_Pve9_FirstContainer_HasCorrectCpuAndMem() + { + var json = TestHelper.LoadFixture("pve9_containers.json"); + var data = JObject.Parse(json)["data"]; + var containers = data.ToObject(); + Assert.NotNull(containers); + Assert.Equal(2, containers[0].CpuCount); + Assert.Equal(2147483648L, containers[0].MaxMem); + } + + [Fact] + public void PveContainer_Deserialize_Pve9_FirstContainer_HasSwapAndDisk() + { + var json = TestHelper.LoadFixture("pve9_containers.json"); + var data = JObject.Parse(json)["data"]; + var containers = data.ToObject(); + Assert.NotNull(containers); + Assert.Equal(536870912L, containers[0].MaxSwap); + Assert.Equal(8589934592L, containers[0].RootFsSize); + } + + [Fact] + public void PveContainer_Deserialize_Pve9_FirstContainer_IsUnprivileged() + { + var json = TestHelper.LoadFixture("pve9_containers.json"); + var data = JObject.Parse(json)["data"]; + var containers = data.ToObject(); + Assert.NotNull(containers); + Assert.Equal(1, containers[0].Unprivileged); + } + + [Fact] + public void PveContainer_Deserialize_Pve9_FirstContainer_HasOsType() + { + var json = TestHelper.LoadFixture("pve9_containers.json"); + var data = JObject.Parse(json)["data"]; + var containers = data.ToObject(); + Assert.NotNull(containers); + Assert.Equal("ubuntu", containers[0].OsType); + } + + [Fact] + public void PveContainer_Deserialize_Pve9_SecondContainer_IsStopped() + { + var json = TestHelper.LoadFixture("pve9_containers.json"); + var data = JObject.Parse(json)["data"]; + var containers = data.ToObject(); + Assert.NotNull(containers); + Assert.Equal(201, containers[1].VmId); + Assert.Equal("stopped", containers[1].Status); + Assert.Equal(0L, containers[1].Uptime); + } + + [Fact] + public void PveContainer_OptionalFields_NullWhenMissing() + { + var json = TestHelper.LoadFixture("pve9_containers.json"); + var data = JObject.Parse(json)["data"]; + var containers = data.ToObject(); + Assert.NotNull(containers); + // Tags field is only in the first container + Assert.Equal("web", containers[0].Tags); + Assert.Null(containers[1].Tags); + } + } +} diff --git a/tests/PSProxmoxVE.Core.Tests/Models/NetworkModelTests.cs b/tests/PSProxmoxVE.Core.Tests/Models/NetworkModelTests.cs new file mode 100644 index 0000000..b76fd37 --- /dev/null +++ b/tests/PSProxmoxVE.Core.Tests/Models/NetworkModelTests.cs @@ -0,0 +1,104 @@ +using Xunit; +using Newtonsoft.Json.Linq; +using PSProxmoxVE.Core.Models.Network; + +namespace PSProxmoxVE.Core.Tests.Models +{ + public class NetworkModelTests + { + [Fact] + public void PveNetwork_Deserialize_Pve9_ReturnsCorrectCount() + { + var json = TestHelper.LoadFixture("pve9_networks.json"); + var data = JObject.Parse(json)["data"]; + var networks = data.ToObject(); + Assert.NotNull(networks); + Assert.Equal(2, networks.Length); + } + + [Fact] + public void PveNetwork_Deserialize_Pve9_FirstInterface_HasCorrectName() + { + var json = TestHelper.LoadFixture("pve9_networks.json"); + var data = JObject.Parse(json)["data"]; + var networks = data.ToObject(); + Assert.NotNull(networks); + Assert.Equal("vmbr0", networks[0].Iface); + } + + [Fact] + public void PveNetwork_Deserialize_Pve9_FirstInterface_HasCorrectType() + { + var json = TestHelper.LoadFixture("pve9_networks.json"); + var data = JObject.Parse(json)["data"]; + var networks = data.ToObject(); + Assert.NotNull(networks); + Assert.Equal("bridge", networks[0].Type); + } + + [Fact] + public void PveNetwork_Deserialize_Pve9_FirstInterface_HasCorrectAddress() + { + var json = TestHelper.LoadFixture("pve9_networks.json"); + var data = JObject.Parse(json)["data"]; + var networks = data.ToObject(); + Assert.NotNull(networks); + Assert.Equal("192.168.1.10", networks[0].Address); + Assert.Equal("255.255.255.0", networks[0].Netmask); + Assert.Equal("192.168.1.1", networks[0].Gateway); + } + + [Fact] + public void PveNetwork_Deserialize_Pve9_FirstInterface_HasCidr() + { + var json = TestHelper.LoadFixture("pve9_networks.json"); + var data = JObject.Parse(json)["data"]; + var networks = data.ToObject(); + Assert.NotNull(networks); + Assert.Equal("192.168.1.10/24", networks[0].Cidr); + } + + [Fact] + public void PveNetwork_Deserialize_Pve9_FirstInterface_HasBridgePorts() + { + var json = TestHelper.LoadFixture("pve9_networks.json"); + var data = JObject.Parse(json)["data"]; + var networks = data.ToObject(); + Assert.NotNull(networks); + Assert.Equal("enp0s31f6", networks[0].BridgePorts); + } + + [Fact] + public void PveNetwork_Deserialize_Pve9_FirstInterface_Autostart_IsEnabled() + { + var json = TestHelper.LoadFixture("pve9_networks.json"); + var data = JObject.Parse(json)["data"]; + var networks = data.ToObject(); + Assert.NotNull(networks); + Assert.Equal(1, networks[0].Autostart); + } + + [Fact] + public void PveNetwork_Deserialize_Pve9_OptionalFields_NullWhenMissing() + { + var json = TestHelper.LoadFixture("pve9_networks.json"); + var data = JObject.Parse(json)["data"]; + var networks = data.ToObject(); + Assert.NotNull(networks); + // No VLAN on primary bridge + Assert.Null(networks[0].VlanId); + Assert.Null(networks[0].Comments); + } + + [Fact] + public void PveNetwork_Deserialize_Pve9_SecondInterface_HasCorrectAddress() + { + var json = TestHelper.LoadFixture("pve9_networks.json"); + var data = JObject.Parse(json)["data"]; + var networks = data.ToObject(); + Assert.NotNull(networks); + Assert.Equal("vmbr1", networks[1].Iface); + Assert.Equal("10.0.0.1/24", networks[1].Cidr); + } + } +} diff --git a/tests/PSProxmoxVE.Core.Tests/Models/NodeModelTests.cs b/tests/PSProxmoxVE.Core.Tests/Models/NodeModelTests.cs new file mode 100644 index 0000000..8f5b53e --- /dev/null +++ b/tests/PSProxmoxVE.Core.Tests/Models/NodeModelTests.cs @@ -0,0 +1,104 @@ +using Xunit; +using Newtonsoft.Json.Linq; +using PSProxmoxVE.Core.Models.Nodes; + +namespace PSProxmoxVE.Core.Tests.Models +{ + public class NodeModelTests + { + [Fact] + public void PveNode_Deserialize_Pve9() + { + var json = TestHelper.LoadFixture("pve9_nodes.json"); + var data = JObject.Parse(json)["data"]; + var nodes = data.ToObject(); + Assert.NotNull(nodes); + Assert.Equal(2, nodes.Length); + Assert.Equal("pve1", nodes[0].Name); + Assert.Equal("online", nodes[0].Status); + Assert.Equal(16, nodes[0].CpuCount); + Assert.Equal(68719476736L, nodes[0].MemoryTotal); + } + + [Fact] + public void PveNode_Deserialize_Pve9_SecondNode() + { + var json = TestHelper.LoadFixture("pve9_nodes.json"); + var data = JObject.Parse(json)["data"]; + var nodes = data.ToObject(); + Assert.NotNull(nodes); + Assert.Equal("pve2", nodes[1].Name); + Assert.Equal("online", nodes[1].Status); + Assert.Equal(8, nodes[1].CpuCount); + Assert.Equal(34359738368L, nodes[1].MemoryTotal); + } + + [Fact] + public void PveNode_Deserialize_Pve8() + { + var json = TestHelper.LoadFixture("pve8_nodes.json"); + var data = JObject.Parse(json)["data"]; + var nodes = data.ToObject(); + Assert.NotNull(nodes); + Assert.Single(nodes); + Assert.Equal("pve-old", nodes[0].Name); + } + + [Fact] + public void PveNode_Deserialize_Pve8_HasExpectedCpuAndMemory() + { + var json = TestHelper.LoadFixture("pve8_nodes.json"); + var data = JObject.Parse(json)["data"]; + var nodes = data.ToObject(); + Assert.NotNull(nodes); + Assert.Equal(4, nodes[0].CpuCount); + Assert.Equal(16106127360L, nodes[0].MemoryTotal); + } + + [Fact] + public void PveNodeStatus_Deserialize_Pve9() + { + var json = TestHelper.LoadFixture("pve9_node_status.json"); + var data = JObject.Parse(json)["data"]; + var status = data.ToObject(); + Assert.NotNull(status); + Assert.Equal(0.125, status.CpuUsage); + } + + [Fact] + public void PveNodeStatus_Deserialize_Pve9_HasMemoryAndUptime() + { + var json = TestHelper.LoadFixture("pve9_node_status.json"); + var data = JObject.Parse(json)["data"]; + var status = data.ToObject(); + Assert.NotNull(status); + Assert.Equal("pve1", status.Name); + Assert.Equal(68719476736L, status.MemoryTotal); + Assert.Equal(17179869184L, status.MemoryUsed); + Assert.Equal(864000L, status.Uptime); + } + + [Fact] + public void PveNodeStatus_MemoryUsage_IsCalculated() + { + var json = TestHelper.LoadFixture("pve9_node_status.json"); + var data = JObject.Parse(json)["data"]; + var status = data.ToObject(); + Assert.NotNull(status); + Assert.NotNull(status.MemoryUsage); + // 17179869184 / 68719476736 * 100 = 25.0 + Assert.Equal(25.0, status.MemoryUsage!.Value, precision: 5); + } + + [Fact] + public void PveNode_LoadAverage_IsDeserializedAsArray() + { + var json = TestHelper.LoadFixture("pve9_nodes.json"); + var data = JObject.Parse(json)["data"]; + var nodes = data.ToObject(); + Assert.NotNull(nodes); + Assert.NotNull(nodes[0].LoadAverage); + Assert.Equal(3, nodes[0].LoadAverage!.Length); + } + } +} diff --git a/tests/PSProxmoxVE.Core.Tests/Models/SdnModelTests.cs b/tests/PSProxmoxVE.Core.Tests/Models/SdnModelTests.cs new file mode 100644 index 0000000..846272a --- /dev/null +++ b/tests/PSProxmoxVE.Core.Tests/Models/SdnModelTests.cs @@ -0,0 +1,134 @@ +using Xunit; +using Newtonsoft.Json.Linq; +using PSProxmoxVE.Core.Models.Network; + +namespace PSProxmoxVE.Core.Tests.Models +{ + public class SdnModelTests + { + [Fact] + public void PveSdnZone_Deserialize_Pve9_ReturnsCorrectCount() + { + var json = TestHelper.LoadFixture("pve9_sdn_zones.json"); + var data = JObject.Parse(json)["data"]; + var zones = data.ToObject(); + Assert.NotNull(zones); + Assert.Equal(2, zones.Length); + } + + [Fact] + public void PveSdnZone_Deserialize_Pve9_FirstZone_HasCorrectId() + { + var json = TestHelper.LoadFixture("pve9_sdn_zones.json"); + var data = JObject.Parse(json)["data"]; + var zones = data.ToObject(); + Assert.NotNull(zones); + Assert.Equal("localzone", zones[0].Zone); + } + + [Fact] + public void PveSdnZone_Deserialize_Pve9_FirstZone_HasCorrectType() + { + var json = TestHelper.LoadFixture("pve9_sdn_zones.json"); + var data = JObject.Parse(json)["data"]; + var zones = data.ToObject(); + Assert.NotNull(zones); + Assert.Equal("simple", zones[0].Type); + } + + [Fact] + public void PveSdnZone_Deserialize_Pve9_SecondZone_HasCorrectType() + { + var json = TestHelper.LoadFixture("pve9_sdn_zones.json"); + var data = JObject.Parse(json)["data"]; + var zones = data.ToObject(); + Assert.NotNull(zones); + Assert.Equal("vxlanzone", zones[1].Zone); + Assert.Equal("vxlan", zones[1].Type); + } + + [Fact] + public void PveSdnZone_Deserialize_Pve9_OptionalFields_NullWhenMissing() + { + var json = TestHelper.LoadFixture("pve9_sdn_zones.json"); + var data = JObject.Parse(json)["data"]; + var zones = data.ToObject(); + Assert.NotNull(zones); + // Comments field is not present in the fixture + Assert.Null(zones[0].Comments); + } + + [Fact] + public void PveSdnVnet_Deserialize_Pve9_ReturnsCorrectCount() + { + var json = TestHelper.LoadFixture("pve9_sdn_vnets.json"); + var data = JObject.Parse(json)["data"]; + var vnets = data.ToObject(); + Assert.NotNull(vnets); + Assert.Equal(2, vnets.Length); + } + + [Fact] + public void PveSdnVnet_Deserialize_Pve9_FirstVnet_HasCorrectId() + { + var json = TestHelper.LoadFixture("pve9_sdn_vnets.json"); + var data = JObject.Parse(json)["data"]; + var vnets = data.ToObject(); + Assert.NotNull(vnets); + Assert.Equal("myvnet", vnets[0].Vnet); + } + + [Fact] + public void PveSdnVnet_Deserialize_Pve9_FirstVnet_HasCorrectZone() + { + var json = TestHelper.LoadFixture("pve9_sdn_vnets.json"); + var data = JObject.Parse(json)["data"]; + var vnets = data.ToObject(); + Assert.NotNull(vnets); + Assert.Equal("localzone", vnets[0].Zone); + } + + [Fact] + public void PveSdnVnet_Deserialize_Pve9_FirstVnet_HasTag() + { + var json = TestHelper.LoadFixture("pve9_sdn_vnets.json"); + var data = JObject.Parse(json)["data"]; + var vnets = data.ToObject(); + Assert.NotNull(vnets); + Assert.Equal(100, vnets[0].Tag); + } + + [Fact] + public void PveSdnVnet_Deserialize_Pve9_FirstVnet_HasAlias() + { + var json = TestHelper.LoadFixture("pve9_sdn_vnets.json"); + var data = JObject.Parse(json)["data"]; + var vnets = data.ToObject(); + Assert.NotNull(vnets); + Assert.Equal("My VNET", vnets[0].Alias); + } + + [Fact] + public void PveSdnVnet_Deserialize_Pve9_SecondVnet_HasCorrectZoneAndTag() + { + var json = TestHelper.LoadFixture("pve9_sdn_vnets.json"); + var data = JObject.Parse(json)["data"]; + var vnets = data.ToObject(); + Assert.NotNull(vnets); + Assert.Equal("prodnet", vnets[1].Vnet); + Assert.Equal("vxlanzone", vnets[1].Zone); + Assert.Equal(200, vnets[1].Tag); + } + + [Fact] + public void PveSdnVnet_Deserialize_Pve9_OptionalFields_NullWhenMissing() + { + var json = TestHelper.LoadFixture("pve9_sdn_vnets.json"); + var data = JObject.Parse(json)["data"]; + var vnets = data.ToObject(); + Assert.NotNull(vnets); + // Comments not present in fixture entries + Assert.Null(vnets[0].Comments); + } + } +} diff --git a/tests/PSProxmoxVE.Core.Tests/Models/SnapshotModelTests.cs b/tests/PSProxmoxVE.Core.Tests/Models/SnapshotModelTests.cs new file mode 100644 index 0000000..c48b29d --- /dev/null +++ b/tests/PSProxmoxVE.Core.Tests/Models/SnapshotModelTests.cs @@ -0,0 +1,101 @@ +using Xunit; +using Newtonsoft.Json.Linq; +using PSProxmoxVE.Core.Models.Vms; + +namespace PSProxmoxVE.Core.Tests.Models +{ + public class SnapshotModelTests + { + [Fact] + public void PveSnapshot_Deserialize_Pve9_ReturnsCorrectCount() + { + var json = TestHelper.LoadFixture("pve9_snapshots.json"); + var data = JObject.Parse(json)["data"]; + var snapshots = data.ToObject(); + Assert.NotNull(snapshots); + Assert.Equal(3, snapshots.Length); + } + + [Fact] + public void PveSnapshot_Deserialize_Pve9_Current_HasCorrectName() + { + var json = TestHelper.LoadFixture("pve9_snapshots.json"); + var data = JObject.Parse(json)["data"]; + var snapshots = data.ToObject(); + Assert.NotNull(snapshots); + Assert.Equal("current", snapshots[0].Name); + } + + [Fact] + public void PveSnapshot_Deserialize_Pve9_Current_HasDescription() + { + var json = TestHelper.LoadFixture("pve9_snapshots.json"); + var data = JObject.Parse(json)["data"]; + var snapshots = data.ToObject(); + Assert.NotNull(snapshots); + Assert.Equal("You are here!", snapshots[0].Description); + } + + [Fact] + public void PveSnapshot_Deserialize_Pve9_Snap1_HasSnapTime() + { + var json = TestHelper.LoadFixture("pve9_snapshots.json"); + var data = JObject.Parse(json)["data"]; + var snapshots = data.ToObject(); + Assert.NotNull(snapshots); + Assert.Equal("snap1", snapshots[1].Name); + Assert.Equal(1710000000L, snapshots[1].SnapTime); + } + + [Fact] + public void PveSnapshot_Deserialize_Pve9_Snap1_HasVmState() + { + var json = TestHelper.LoadFixture("pve9_snapshots.json"); + var data = JObject.Parse(json)["data"]; + var snapshots = data.ToObject(); + Assert.NotNull(snapshots); + Assert.Equal(1, snapshots[1].VmState); + } + + [Fact] + public void PveSnapshot_Deserialize_Pve9_Snap1_HasParent() + { + var json = TestHelper.LoadFixture("pve9_snapshots.json"); + var data = JObject.Parse(json)["data"]; + var snapshots = data.ToObject(); + Assert.NotNull(snapshots); + Assert.Equal("base", snapshots[1].Parent); + } + + [Fact] + public void PveSnapshot_Deserialize_Pve9_Base_HasNoVmState() + { + var json = TestHelper.LoadFixture("pve9_snapshots.json"); + var data = JObject.Parse(json)["data"]; + var snapshots = data.ToObject(); + Assert.NotNull(snapshots); + Assert.Equal("base", snapshots[2].Name); + Assert.Equal(0, snapshots[2].VmState); + } + + [Fact] + public void PveSnapshot_Deserialize_Pve9_Current_ParentIsSnap1() + { + var json = TestHelper.LoadFixture("pve9_snapshots.json"); + var data = JObject.Parse(json)["data"]; + var snapshots = data.ToObject(); + Assert.NotNull(snapshots); + Assert.Equal("snap1", snapshots[0].Parent); + } + + [Fact] + public void PveSnapshot_Deserialize_Pve9_Base_HasOlderSnapTime() + { + var json = TestHelper.LoadFixture("pve9_snapshots.json"); + var data = JObject.Parse(json)["data"]; + var snapshots = data.ToObject(); + Assert.NotNull(snapshots); + Assert.Equal(1709000000L, snapshots[2].SnapTime); + } + } +} diff --git a/tests/PSProxmoxVE.Core.Tests/Models/StorageModelTests.cs b/tests/PSProxmoxVE.Core.Tests/Models/StorageModelTests.cs new file mode 100644 index 0000000..b472acb --- /dev/null +++ b/tests/PSProxmoxVE.Core.Tests/Models/StorageModelTests.cs @@ -0,0 +1,138 @@ +using Xunit; +using Newtonsoft.Json.Linq; +using PSProxmoxVE.Core.Models.Storage; + +namespace PSProxmoxVE.Core.Tests.Models +{ + public class StorageModelTests + { + [Fact] + public void PveStorage_Deserialize_Pve9_ReturnsCorrectCount() + { + var json = TestHelper.LoadFixture("pve9_storage.json"); + var data = JObject.Parse(json)["data"]; + var storages = data.ToObject(); + Assert.NotNull(storages); + Assert.Equal(3, storages.Length); + } + + [Fact] + public void PveStorage_Deserialize_Pve9_Local_HasCorrectId() + { + var json = TestHelper.LoadFixture("pve9_storage.json"); + var data = JObject.Parse(json)["data"]; + var storages = data.ToObject(); + Assert.NotNull(storages); + Assert.Equal("local", storages[0].Storage); + } + + [Fact] + public void PveStorage_Deserialize_Pve9_Local_HasCorrectType() + { + var json = TestHelper.LoadFixture("pve9_storage.json"); + var data = JObject.Parse(json)["data"]; + var storages = data.ToObject(); + Assert.NotNull(storages); + Assert.Equal("dir", storages[0].Type); + } + + [Fact] + public void PveStorage_Deserialize_Pve9_Local_HasContentTypes() + { + var json = TestHelper.LoadFixture("pve9_storage.json"); + var data = JObject.Parse(json)["data"]; + var storages = data.ToObject(); + Assert.NotNull(storages); + Assert.Equal("iso,vztmpl,backup", storages[0].Content); + } + + [Fact] + public void PveStorage_Deserialize_Pve9_Local_HasSizeFields() + { + var json = TestHelper.LoadFixture("pve9_storage.json"); + var data = JObject.Parse(json)["data"]; + var storages = data.ToObject(); + Assert.NotNull(storages); + Assert.Equal(107374182400L, storages[0].Total); + Assert.Equal(21474836480L, storages[0].Used); + Assert.Equal(85899345920L, storages[0].Available); + } + + [Fact] + public void PveStorage_Deserialize_Pve9_Local_IsEnabled_NotShared() + { + var json = TestHelper.LoadFixture("pve9_storage.json"); + var data = JObject.Parse(json)["data"]; + var storages = data.ToObject(); + Assert.NotNull(storages); + Assert.Equal(1, storages[0].Enabled); + Assert.Equal(0, storages[0].Shared); + } + + [Fact] + public void PveStorage_Deserialize_Pve9_CephPool_IsShared() + { + var json = TestHelper.LoadFixture("pve9_storage.json"); + var data = JObject.Parse(json)["data"]; + var storages = data.ToObject(); + Assert.NotNull(storages); + var ceph = storages[2]; + Assert.Equal("ceph-pool", ceph.Storage); + Assert.Equal("rbd", ceph.Type); + Assert.Equal(1, ceph.Shared); + } + + [Fact] + public void PveStorageContent_Deserialize_Pve9_ReturnsCorrectCount() + { + var json = TestHelper.LoadFixture("pve9_storage_content.json"); + var data = JObject.Parse(json)["data"]; + var items = data.ToObject(); + Assert.NotNull(items); + Assert.Equal(2, items.Length); + } + + [Fact] + public void PveStorageContent_Deserialize_Pve9_FirstItem_HasCorrectVolId() + { + var json = TestHelper.LoadFixture("pve9_storage_content.json"); + var data = JObject.Parse(json)["data"]; + var items = data.ToObject(); + Assert.NotNull(items); + Assert.Equal("local:iso/ubuntu-24.04-live-server-amd64.iso", items[0].VolId); + } + + [Fact] + public void PveStorageContent_Deserialize_Pve9_FirstItem_HasCorrectContentType() + { + var json = TestHelper.LoadFixture("pve9_storage_content.json"); + var data = JObject.Parse(json)["data"]; + var items = data.ToObject(); + Assert.NotNull(items); + Assert.Equal("iso", items[0].Content); + Assert.Equal("iso", items[0].Format); + } + + [Fact] + public void PveStorageContent_Deserialize_Pve9_FirstItem_HasSize() + { + var json = TestHelper.LoadFixture("pve9_storage_content.json"); + var data = JObject.Parse(json)["data"]; + var items = data.ToObject(); + Assert.NotNull(items); + Assert.Equal(2774532096L, items[0].Size); + } + + [Fact] + public void PveStorageContent_Deserialize_Pve9_OptionalFields_NullWhenMissing() + { + var json = TestHelper.LoadFixture("pve9_storage_content.json"); + var data = JObject.Parse(json)["data"]; + var items = data.ToObject(); + Assert.NotNull(items); + // ISO items should not have a VmId or Notes + Assert.Null(items[0].VmId); + Assert.Null(items[0].Notes); + } + } +} diff --git a/tests/PSProxmoxVE.Core.Tests/Models/TaskModelTests.cs b/tests/PSProxmoxVE.Core.Tests/Models/TaskModelTests.cs new file mode 100644 index 0000000..8047e8f --- /dev/null +++ b/tests/PSProxmoxVE.Core.Tests/Models/TaskModelTests.cs @@ -0,0 +1,102 @@ +using Xunit; +using Newtonsoft.Json.Linq; +using PSProxmoxVE.Core.Models.Vms; + +namespace PSProxmoxVE.Core.Tests.Models +{ + public class TaskModelTests + { + [Fact] + public void PveTask_Deserialize_Completed_HasCorrectUpid() + { + var json = TestHelper.LoadFixture("pve9_tasks.json"); + var data = JObject.Parse(json)["data"]; + var task = data.ToObject(); + Assert.NotNull(task); + Assert.Equal("UPID:pve1:00001234:00ABCDEF:65F00000:qmstart:100:root@pam:", task.Upid); + } + + [Fact] + public void PveTask_Deserialize_Completed_StatusIsStopped() + { + var json = TestHelper.LoadFixture("pve9_tasks.json"); + var data = JObject.Parse(json)["data"]; + var task = data.ToObject(); + Assert.NotNull(task); + Assert.Equal("stopped", task.Status); + } + + [Fact] + public void PveTask_Deserialize_Completed_ExitStatusIsOk() + { + var json = TestHelper.LoadFixture("pve9_tasks.json"); + var data = JObject.Parse(json)["data"]; + var task = data.ToObject(); + Assert.NotNull(task); + Assert.Equal("OK", task.ExitStatus); + } + + [Fact] + public void PveTask_Deserialize_Completed_IsSuccessful_IsTrue() + { + var json = TestHelper.LoadFixture("pve9_tasks.json"); + var data = JObject.Parse(json)["data"]; + var task = data.ToObject(); + Assert.NotNull(task); + Assert.True(task.IsSuccessful); + } + + [Fact] + public void PveTask_Deserialize_Running_HasCorrectUpid() + { + var json = TestHelper.LoadFixture("pve9_task_running.json"); + var data = JObject.Parse(json)["data"]; + var task = data.ToObject(); + Assert.NotNull(task); + Assert.Equal("UPID:pve1:00001234:00ABCDEF:65F00000:qmstart:100:root@pam:", task.Upid); + } + + [Fact] + public void PveTask_Deserialize_Running_StatusIsRunning() + { + var json = TestHelper.LoadFixture("pve9_task_running.json"); + var data = JObject.Parse(json)["data"]; + var task = data.ToObject(); + Assert.NotNull(task); + Assert.Equal("running", task.Status); + } + + [Fact] + public void PveTask_Deserialize_Running_ExitStatus_IsNull() + { + var json = TestHelper.LoadFixture("pve9_task_running.json"); + var data = JObject.Parse(json)["data"]; + var task = data.ToObject(); + Assert.NotNull(task); + Assert.Null(task.ExitStatus); + } + + [Fact] + public void PveTask_Deserialize_Running_IsSuccessful_IsFalse() + { + var json = TestHelper.LoadFixture("pve9_task_running.json"); + var data = JObject.Parse(json)["data"]; + var task = data.ToObject(); + Assert.NotNull(task); + Assert.False(task.IsSuccessful); + } + + [Fact] + public void PveTask_IsSuccessful_RequiresBothStoppedAndOk() + { + // Stopped but with an error exit status should not be successful + var task = new PveTask + { + Upid = "UPID:pve1:000:000:000:qmstop:100:root@pam:", + Status = "stopped", + ExitStatus = "Error: some error occurred" + }; + Assert.False(task.IsSuccessful); + } + } +} diff --git a/tests/PSProxmoxVE.Core.Tests/Models/UserModelTests.cs b/tests/PSProxmoxVE.Core.Tests/Models/UserModelTests.cs new file mode 100644 index 0000000..32c8448 --- /dev/null +++ b/tests/PSProxmoxVE.Core.Tests/Models/UserModelTests.cs @@ -0,0 +1,145 @@ +using Xunit; +using Newtonsoft.Json.Linq; +using PSProxmoxVE.Core.Models.Users; + +namespace PSProxmoxVE.Core.Tests.Models +{ + public class UserModelTests + { + [Fact] + public void PveUser_Deserialize_Pve9_ReturnsCorrectCount() + { + var json = TestHelper.LoadFixture("pve9_users.json"); + var data = JObject.Parse(json)["data"]; + var users = data.ToObject(); + Assert.NotNull(users); + Assert.Equal(3, users.Length); + } + + [Fact] + public void PveUser_Deserialize_Pve9_Root_HasCorrectUserId() + { + var json = TestHelper.LoadFixture("pve9_users.json"); + var data = JObject.Parse(json)["data"]; + var users = data.ToObject(); + Assert.NotNull(users); + Assert.Equal("root@pam", users[0].UserId); + } + + [Fact] + public void PveUser_Deserialize_Pve9_Root_HasCorrectName() + { + var json = TestHelper.LoadFixture("pve9_users.json"); + var data = JObject.Parse(json)["data"]; + var users = data.ToObject(); + Assert.NotNull(users); + Assert.Equal("Root", users[0].FirstName); + Assert.Equal("User", users[0].LastName); + } + + [Fact] + public void PveUser_Deserialize_Pve9_Root_HasEmail() + { + var json = TestHelper.LoadFixture("pve9_users.json"); + var data = JObject.Parse(json)["data"]; + var users = data.ToObject(); + Assert.NotNull(users); + Assert.Equal("root@example.com", users[0].Email); + } + + [Fact] + public void PveUser_Deserialize_Pve9_Root_IsEnabled() + { + var json = TestHelper.LoadFixture("pve9_users.json"); + var data = JObject.Parse(json)["data"]; + var users = data.ToObject(); + Assert.NotNull(users); + Assert.Equal(1, users[0].Enabled); + } + + [Fact] + public void PveUser_Deserialize_Pve9_Admin_HasGroups() + { + var json = TestHelper.LoadFixture("pve9_users.json"); + var data = JObject.Parse(json)["data"]; + var users = data.ToObject(); + Assert.NotNull(users); + Assert.Equal("admin@pve", users[1].UserId); + Assert.Equal("admins", users[1].Groups); + } + + [Fact] + public void PveUser_Deserialize_Pve9_ReadOnly_HasComment() + { + var json = TestHelper.LoadFixture("pve9_users.json"); + var data = JObject.Parse(json)["data"]; + var users = data.ToObject(); + Assert.NotNull(users); + Assert.Equal("readonly@pve", users[2].UserId); + Assert.Equal("Read-only audit user", users[2].Comment); + } + + [Fact] + public void PveUser_Deserialize_Pve9_Root_Groups_IsNull() + { + var json = TestHelper.LoadFixture("pve9_users.json"); + var data = JObject.Parse(json)["data"]; + var users = data.ToObject(); + Assert.NotNull(users); + // Root user has no groups in fixture + Assert.Null(users[0].Groups); + } + + [Fact] + public void PveRole_Deserialize_Pve9_ReturnsCorrectCount() + { + var json = TestHelper.LoadFixture("pve9_roles.json"); + var data = JObject.Parse(json)["data"]; + var roles = data.ToObject(); + Assert.NotNull(roles); + Assert.Equal(2, roles.Length); + } + + [Fact] + public void PveRole_Deserialize_Pve9_Administrator_HasCorrectId() + { + var json = TestHelper.LoadFixture("pve9_roles.json"); + var data = JObject.Parse(json)["data"]; + var roles = data.ToObject(); + Assert.NotNull(roles); + Assert.Equal("Administrator", roles[0].RoleId); + } + + [Fact] + public void PveRole_Deserialize_Pve9_Administrator_IsSpecial() + { + var json = TestHelper.LoadFixture("pve9_roles.json"); + var data = JObject.Parse(json)["data"]; + var roles = data.ToObject(); + Assert.NotNull(roles); + Assert.Equal(1, roles[0].Special); + } + + [Fact] + public void PveRole_Deserialize_Pve9_Administrator_HasPrivileges() + { + var json = TestHelper.LoadFixture("pve9_roles.json"); + var data = JObject.Parse(json)["data"]; + var roles = data.ToObject(); + Assert.NotNull(roles); + Assert.NotNull(roles[0].Privileges); + Assert.Contains("VM.Allocate", roles[0].Privileges); + } + + [Fact] + public void PveRole_Deserialize_Pve9_PVEAuditor_HasLimitedPrivileges() + { + var json = TestHelper.LoadFixture("pve9_roles.json"); + var data = JObject.Parse(json)["data"]; + var roles = data.ToObject(); + Assert.NotNull(roles); + Assert.Equal("PVEAuditor", roles[1].RoleId); + Assert.Contains("VM.Audit", roles[1].Privileges); + } + } +} diff --git a/tests/PSProxmoxVE.Core.Tests/Models/VmModelTests.cs b/tests/PSProxmoxVE.Core.Tests/Models/VmModelTests.cs new file mode 100644 index 0000000..ddad0aa --- /dev/null +++ b/tests/PSProxmoxVE.Core.Tests/Models/VmModelTests.cs @@ -0,0 +1,163 @@ +using Xunit; +using Newtonsoft.Json.Linq; +using PSProxmoxVE.Core.Models.Vms; + +namespace PSProxmoxVE.Core.Tests.Models +{ + public class VmModelTests + { + [Fact] + public void PveVm_Deserialize_Pve9_ReturnsCorrectCount() + { + var json = TestHelper.LoadFixture("pve9_vms.json"); + var data = JObject.Parse(json)["data"]; + var vms = data.ToObject(); + Assert.NotNull(vms); + Assert.Equal(3, vms.Length); + } + + [Fact] + public void PveVm_Deserialize_Pve9_FirstVm_HasCorrectVmId() + { + var json = TestHelper.LoadFixture("pve9_vms.json"); + var data = JObject.Parse(json)["data"]; + var vms = data.ToObject(); + Assert.NotNull(vms); + Assert.Equal(100, vms[0].VmId); + } + + [Fact] + public void PveVm_Deserialize_Pve9_FirstVm_HasCorrectName() + { + var json = TestHelper.LoadFixture("pve9_vms.json"); + var data = JObject.Parse(json)["data"]; + var vms = data.ToObject(); + Assert.NotNull(vms); + Assert.Equal("test-vm-1", vms[0].Name); + } + + [Fact] + public void PveVm_Deserialize_Pve9_FirstVm_HasCorrectStatus() + { + var json = TestHelper.LoadFixture("pve9_vms.json"); + var data = JObject.Parse(json)["data"]; + var vms = data.ToObject(); + Assert.NotNull(vms); + Assert.Equal("running", vms[0].Status); + } + + [Fact] + public void PveVm_Deserialize_Pve9_TemplateFlag_IsSet() + { + var json = TestHelper.LoadFixture("pve9_vms.json"); + var data = JObject.Parse(json)["data"]; + var vms = data.ToObject(); + Assert.NotNull(vms); + // Third entry is the template + var template = vms[2]; + Assert.Equal(9000, template.VmId); + Assert.Equal(1, template.Template); + } + + [Fact] + public void PveVm_Deserialize_Pve9_RegularVm_TemplateFlag_IsZero() + { + var json = TestHelper.LoadFixture("pve9_vms.json"); + var data = JObject.Parse(json)["data"]; + var vms = data.ToObject(); + Assert.NotNull(vms); + Assert.Equal(0, vms[0].Template); + } + + [Fact] + public void PveVm_Deserialize_Pve9_FirstVm_HasExpectedMemoryAndDisk() + { + var json = TestHelper.LoadFixture("pve9_vms.json"); + var data = JObject.Parse(json)["data"]; + var vms = data.ToObject(); + Assert.NotNull(vms); + Assert.Equal(8589934592L, vms[0].MaxMem); + Assert.Equal(107374182400L, vms[0].MaxDisk); + } + + [Fact] + public void PveVm_Deserialize_Pve9_StoppedVm_UptimeIsZero() + { + var json = TestHelper.LoadFixture("pve9_vms.json"); + var data = JObject.Parse(json)["data"]; + var vms = data.ToObject(); + Assert.NotNull(vms); + Assert.Equal(0L, vms[1].Uptime); + } + + [Fact] + public void PveVmConfig_Deserialize_Pve9_HasCorrectCoresAndMemory() + { + var json = TestHelper.LoadFixture("pve9_vm_config.json"); + var data = JObject.Parse(json)["data"]; + var config = data.ToObject(); + Assert.NotNull(config); + Assert.Equal(4, config.Cores); + Assert.Equal(1, config.Sockets); + Assert.Equal(8192, config.Memory); + } + + [Fact] + public void PveVmConfig_Deserialize_Pve9_HasCorrectBiosAndMachine() + { + var json = TestHelper.LoadFixture("pve9_vm_config.json"); + var data = JObject.Parse(json)["data"]; + var config = data.ToObject(); + Assert.NotNull(config); + Assert.Equal("ovmf", config.Bios); + Assert.Equal("q35", config.Machine); + } + + [Fact] + public void PveVmConfig_Deserialize_Pve9_HasCpuTypeAndOsType() + { + var json = TestHelper.LoadFixture("pve9_vm_config.json"); + var data = JObject.Parse(json)["data"]; + var config = data.ToObject(); + Assert.NotNull(config); + Assert.Equal("host", config.CpuType); + Assert.Equal("l26", config.OsType); + } + + [Fact] + public void PveVmConfig_Deserialize_Pve9_HasNetworkInterface() + { + var json = TestHelper.LoadFixture("pve9_vm_config.json"); + var data = JObject.Parse(json)["data"]; + var config = data.ToObject(); + Assert.NotNull(config); + Assert.NotNull(config.Net0); + Assert.Contains("vmbr0", config.Net0); + } + + [Fact] + public void PveVmConfig_Deserialize_Pve9_OptionalFields_NullWhenMissing() + { + var json = TestHelper.LoadFixture("pve9_vm_config.json"); + var data = JObject.Parse(json)["data"]; + var config = data.ToObject(); + Assert.NotNull(config); + // Fields not present in fixture should be null + Assert.Null(config.Net1); + Assert.Null(config.Virtio1); + Assert.Null(config.Args); + } + + [Fact] + public void PveVmConfig_Deserialize_Pve9_CloudInitFields() + { + var json = TestHelper.LoadFixture("pve9_vm_config.json"); + var data = JObject.Parse(json)["data"]; + var config = data.ToObject(); + Assert.NotNull(config); + Assert.Equal("admin", config.CiUser); + Assert.Equal("8.8.8.8", config.Nameserver); + Assert.Equal("example.com", config.Searchdomain); + } + } +} diff --git a/tests/PSProxmoxVE.Core.Tests/TestHelper.cs b/tests/PSProxmoxVE.Core.Tests/TestHelper.cs new file mode 100644 index 0000000..fa16deb --- /dev/null +++ b/tests/PSProxmoxVE.Core.Tests/TestHelper.cs @@ -0,0 +1,52 @@ +using System.IO; +using System.Net; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Moq; +using Moq.Protected; + +namespace PSProxmoxVE.Core.Tests +{ + public static class TestHelper + { + public static string LoadFixture(string filename) + { + var path = Path.Combine("Fixtures", filename); + return File.ReadAllText(path); + } + + public static Mock CreateMockHandler(string responseBody, HttpStatusCode statusCode = HttpStatusCode.OK) + { + var mock = new Mock(); + mock.Protected() + .Setup>("SendAsync", + ItExpr.IsAny(), + ItExpr.IsAny()) + .ReturnsAsync(new HttpResponseMessage + { + StatusCode = statusCode, + Content = new StringContent(responseBody) + }); + return mock; + } + + public static Mock CreateMockHandlerSequence(params (string body, HttpStatusCode status)[] responses) + { + var mock = new Mock(); + var setup = mock.Protected() + .SetupSequence>("SendAsync", + ItExpr.IsAny(), + ItExpr.IsAny()); + foreach (var (body, status) in responses) + { + setup = setup.ReturnsAsync(new HttpResponseMessage + { + StatusCode = status, + Content = new StringContent(body) + }); + } + return mock; + } + } +}