Refactored subnet object methods and enhanced related documentation
This commit is contained in:
503
classlib.tests/Helpers/RequestHelperTests.cs
Normal file
503
classlib.tests/Helpers/RequestHelperTests.cs
Normal file
@@ -0,0 +1,503 @@
|
||||
namespace PS.IPAM.Tests.Helpers;
|
||||
|
||||
using System.Net;
|
||||
using FluentAssertions;
|
||||
using PS.IPAM;
|
||||
using PS.IPAM.Helpers;
|
||||
using PS.IPAM.Tests.Mocks;
|
||||
using Xunit;
|
||||
|
||||
[Collection("Sequential")]
|
||||
public class RequestHelperTests : IDisposable
|
||||
{
|
||||
private MockHttpMessageHandler? _mockHandler;
|
||||
|
||||
public RequestHelperTests()
|
||||
{
|
||||
// Clean state before each test
|
||||
SessionManager.CloseSession();
|
||||
RequestHelper.TestHttpHandler = null;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Clean up after each test
|
||||
SessionManager.CloseSession();
|
||||
RequestHelper.TestHttpHandler = null;
|
||||
_mockHandler?.ForceDispose();
|
||||
}
|
||||
|
||||
private void SetupSession(AuthType authType = AuthType.token)
|
||||
{
|
||||
if (authType == AuthType.token)
|
||||
{
|
||||
SessionManager.CreateSessionWithToken("https://ipam.example.com", "testapp", "test-token");
|
||||
}
|
||||
else
|
||||
{
|
||||
// For credentials auth, we need to set up a session manually with future expiry
|
||||
var session = new Session(
|
||||
AuthType.credentials,
|
||||
"cred-token",
|
||||
"testapp",
|
||||
"https://ipam.example.com",
|
||||
DateTime.Now.AddHours(1),
|
||||
null
|
||||
);
|
||||
SessionManager.CurrentSession = session;
|
||||
}
|
||||
}
|
||||
|
||||
private MockHttpMessageHandler SetupMockHandler()
|
||||
{
|
||||
_mockHandler = new MockHttpMessageHandler();
|
||||
RequestHelper.TestHttpHandler = _mockHandler;
|
||||
return _mockHandler;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InvokeRequest_WithNoSession_ThrowsException()
|
||||
{
|
||||
// Arrange - no session set up
|
||||
|
||||
// Act
|
||||
var action = async () => await RequestHelper.InvokeRequest("GET", controllers.addresses);
|
||||
|
||||
// Assert
|
||||
await action.Should().ThrowAsync<Exception>().WithMessage("No session available!");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InvokeRequest_WithValidSession_BuildsCorrectUri()
|
||||
{
|
||||
// Arrange
|
||||
SetupSession();
|
||||
var handler = SetupMockHandler();
|
||||
handler.WithSuccessResponse("{\"id\":1,\"ip\":\"192.168.1.1\"}");
|
||||
|
||||
// Act
|
||||
await RequestHelper.InvokeRequest("GET", controllers.addresses, types.Address, null, null, new[] { "1" });
|
||||
|
||||
// Assert
|
||||
handler.LastRequest.Should().NotBeNull();
|
||||
handler.LastRequest!.RequestUri!.ToString().Should().Be("https://ipam.example.com/api/testapp/addresses/1/");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InvokeRequest_WithSubController_BuildsCorrectUri()
|
||||
{
|
||||
// Arrange
|
||||
SetupSession();
|
||||
var handler = SetupMockHandler();
|
||||
handler.WithSuccessResponse("[]");
|
||||
|
||||
// Act
|
||||
await RequestHelper.InvokeRequest("GET", controllers.subnets, null, subcontrollers.tags, null, new[] { "1" });
|
||||
|
||||
// Assert
|
||||
handler.LastRequest!.RequestUri!.ToString().Should().Contain("/subnets/tags/1/");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InvokeRequest_WithTokenAuth_AddsPhpipamTokenHeader()
|
||||
{
|
||||
// Arrange
|
||||
SetupSession(AuthType.token);
|
||||
var handler = SetupMockHandler();
|
||||
handler.WithSuccessResponse("{}");
|
||||
|
||||
// Act
|
||||
await RequestHelper.InvokeRequest("GET", controllers.addresses);
|
||||
|
||||
// Assert
|
||||
handler.GetLastRequestHeader("phpipam-token").Should().Be("test-token");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InvokeRequest_WithCredentialsAuth_AddsTokenHeader()
|
||||
{
|
||||
// Arrange
|
||||
SetupSession(AuthType.credentials);
|
||||
var handler = SetupMockHandler();
|
||||
handler.WithSuccessResponse("{}");
|
||||
|
||||
// Act
|
||||
await RequestHelper.InvokeRequest("GET", controllers.addresses);
|
||||
|
||||
// Assert
|
||||
handler.GetLastRequestHeader("token").Should().Be("cred-token");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InvokeRequest_GetMethod_UsesHttpGet()
|
||||
{
|
||||
// Arrange
|
||||
SetupSession();
|
||||
var handler = SetupMockHandler();
|
||||
handler.WithSuccessResponse("{}");
|
||||
|
||||
// Act
|
||||
await RequestHelper.InvokeRequest("GET", controllers.addresses);
|
||||
|
||||
// Assert
|
||||
handler.LastRequest!.Method.Should().Be(HttpMethod.Get);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InvokeRequest_PostMethod_UsesHttpPost()
|
||||
{
|
||||
// Arrange
|
||||
SetupSession();
|
||||
var handler = SetupMockHandler();
|
||||
handler.WithSuccessResponse("{\"id\":1}");
|
||||
|
||||
// Act
|
||||
await RequestHelper.InvokeRequest("POST", controllers.addresses, null, null, new { ip = "10.0.0.1" });
|
||||
|
||||
// Assert
|
||||
handler.LastRequest!.Method.Should().Be(HttpMethod.Post);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InvokeRequest_PatchMethod_UsesHttpPatch()
|
||||
{
|
||||
// Arrange
|
||||
SetupSession();
|
||||
var handler = SetupMockHandler();
|
||||
handler.WithSuccessResponse("{\"id\":1}");
|
||||
|
||||
// Act
|
||||
await RequestHelper.InvokeRequest("PATCH", controllers.addresses, null, null, new { description = "updated" }, new[] { "1" });
|
||||
|
||||
// Assert
|
||||
handler.LastRequest!.Method.Should().Be(new HttpMethod("PATCH"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InvokeRequest_DeleteMethod_UsesHttpDelete()
|
||||
{
|
||||
// Arrange
|
||||
SetupSession();
|
||||
var handler = SetupMockHandler();
|
||||
handler.WithSuccessResponse("{}");
|
||||
|
||||
// Act
|
||||
await RequestHelper.InvokeRequest("DELETE", controllers.addresses, null, null, null, new[] { "1" });
|
||||
|
||||
// Assert
|
||||
handler.LastRequest!.Method.Should().Be(HttpMethod.Delete);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InvokeRequest_With404Response_ReturnsNull()
|
||||
{
|
||||
// Arrange
|
||||
SetupSession();
|
||||
var handler = SetupMockHandler();
|
||||
handler.WithNotFoundResponse();
|
||||
|
||||
// Act
|
||||
var result = await RequestHelper.InvokeRequest("GET", controllers.addresses, types.Address, null, null, new[] { "999" });
|
||||
|
||||
// Assert
|
||||
result.Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InvokeRequest_WithErrorResponse_ThrowsHttpRequestException()
|
||||
{
|
||||
// Arrange
|
||||
SetupSession();
|
||||
var handler = SetupMockHandler();
|
||||
handler.WithErrorResponse(HttpStatusCode.InternalServerError, "Server error");
|
||||
|
||||
// Act
|
||||
var action = async () => await RequestHelper.InvokeRequest("GET", controllers.addresses);
|
||||
|
||||
// Assert
|
||||
await action.Should().ThrowAsync<HttpRequestException>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InvokeRequest_WithAddressType_ReturnsAddressObject()
|
||||
{
|
||||
// Arrange
|
||||
SetupSession();
|
||||
var handler = SetupMockHandler();
|
||||
var addressJson = @"{
|
||||
""id"": 1,
|
||||
""subnetId"": 10,
|
||||
""ip"": ""192.168.1.100"",
|
||||
""is_gateway"": false,
|
||||
""description"": ""Test server"",
|
||||
""hostname"": ""server01"",
|
||||
""mac"": ""00:11:22:33:44:55"",
|
||||
""owner"": ""admin"",
|
||||
""tag"": 2,
|
||||
""deviceId"": 0,
|
||||
""location"": """",
|
||||
""port"": """",
|
||||
""note"": """",
|
||||
""lastSeen"": null,
|
||||
""excludePing"": false,
|
||||
""PTRignore"": false,
|
||||
""PTR"": 0,
|
||||
""firewallAddressObject"": """",
|
||||
""editDate"": null,
|
||||
""customer_id"": 0
|
||||
}";
|
||||
handler.WithSuccessResponse(addressJson);
|
||||
|
||||
// Act
|
||||
var result = await RequestHelper.InvokeRequest("GET", controllers.addresses, types.Address, null, null, new[] { "1" });
|
||||
|
||||
// Assert
|
||||
result.Should().BeOfType<Address>();
|
||||
var address = (Address)result!;
|
||||
address.Id.Should().Be(1);
|
||||
address.Ip.Should().Be("192.168.1.100");
|
||||
address.Hostname.Should().Be("server01");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InvokeRequest_WithAddressArray_ReturnsListOfAddresses()
|
||||
{
|
||||
// Arrange
|
||||
SetupSession();
|
||||
var handler = SetupMockHandler();
|
||||
var addressArrayJson = @"[
|
||||
{""id"": 1, ""subnetId"": 10, ""ip"": ""192.168.1.1"", ""hostname"": ""host1""},
|
||||
{""id"": 2, ""subnetId"": 10, ""ip"": ""192.168.1.2"", ""hostname"": ""host2""}
|
||||
]";
|
||||
handler.WithSuccessResponse(addressArrayJson);
|
||||
|
||||
// Act
|
||||
var result = await RequestHelper.InvokeRequest("GET", controllers.subnets, types.Address, null, null, new[] { "10", "addresses" });
|
||||
|
||||
// Assert
|
||||
result.Should().BeAssignableTo<IEnumerable<object>>();
|
||||
var addresses = ((IEnumerable<object>)result!).ToList();
|
||||
addresses.Should().HaveCount(2);
|
||||
addresses[0].Should().BeOfType<Address>();
|
||||
((Address)addresses[0]).Ip.Should().Be("192.168.1.1");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InvokeRequest_WithVlanType_ReturnsVlanObject()
|
||||
{
|
||||
// Arrange
|
||||
SetupSession();
|
||||
var handler = SetupMockHandler();
|
||||
var vlanJson = @"{
|
||||
""vlanId"": 100,
|
||||
""domainId"": 1,
|
||||
""name"": ""Production"",
|
||||
""number"": 100,
|
||||
""description"": ""Production VLAN"",
|
||||
""editDate"": null,
|
||||
""customer_id"": 0
|
||||
}";
|
||||
handler.WithSuccessResponse(vlanJson);
|
||||
|
||||
// Act
|
||||
var result = await RequestHelper.InvokeRequest("GET", controllers.vlan, types.Vlan, null, null, new[] { "100" });
|
||||
|
||||
// Assert
|
||||
result.Should().BeOfType<Vlan>();
|
||||
var vlan = (Vlan)result!;
|
||||
vlan.Name.Should().Be("Production");
|
||||
vlan.Number.Should().Be(100);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InvokeRequest_WithSubnetworkType_ReturnsSubnetworkObject()
|
||||
{
|
||||
// Arrange
|
||||
SetupSession();
|
||||
var handler = SetupMockHandler();
|
||||
var subnetJson = @"{
|
||||
""id"": 1,
|
||||
""subnet"": ""192.168.1.0"",
|
||||
""mask"": 24,
|
||||
""sectionId"": 1,
|
||||
""description"": ""Test subnet"",
|
||||
""linked_subnet"": """",
|
||||
""firewallAddressObject"": """",
|
||||
""vrfId"": 0,
|
||||
""masterSubnetId"": 0,
|
||||
""allowRequests"": false,
|
||||
""vlanId"": 100,
|
||||
""showName"": false,
|
||||
""deviceId"": 0,
|
||||
""permissions"": """",
|
||||
""pingSubnet"": false,
|
||||
""discoverSubnet"": false,
|
||||
""resolveDNS"": false,
|
||||
""DNSrecursive"": false,
|
||||
""DNSrecords"": false,
|
||||
""nameserverId"": 0,
|
||||
""scanAgent"": false,
|
||||
""isFolder"": false,
|
||||
""isFull"": false,
|
||||
""isPool"": false,
|
||||
""state"": 1,
|
||||
""threshold"": 0,
|
||||
""location"": 0,
|
||||
""editDate"": null,
|
||||
""lastScan"": null,
|
||||
""lastDiscovery"": null,
|
||||
""calculation"": {}
|
||||
}";
|
||||
handler.WithSuccessResponse(subnetJson);
|
||||
|
||||
// Act
|
||||
var result = await RequestHelper.InvokeRequest("GET", controllers.subnets, types.Subnetwork, null, null, new[] { "1" });
|
||||
|
||||
// Assert
|
||||
result.Should().BeOfType<Subnetwork>();
|
||||
var subnet = (Subnetwork)result!;
|
||||
subnet.Subnet.Should().Be("192.168.1.0");
|
||||
subnet.Mask.Should().Be(24);
|
||||
subnet.GetCIDR().Should().Be("192.168.1.0/24");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InvokeRequest_WithSectionType_ReturnsSectionObject()
|
||||
{
|
||||
// Arrange
|
||||
SetupSession();
|
||||
var handler = SetupMockHandler();
|
||||
var sectionJson = @"{
|
||||
""id"": 1,
|
||||
""name"": ""Production"",
|
||||
""description"": ""Production section"",
|
||||
""masterSection"": 0,
|
||||
""permissions"": """",
|
||||
""strictMode"": false,
|
||||
""subnetOrdering"": ""default"",
|
||||
""order"": 1,
|
||||
""editDate"": null,
|
||||
""showSubnet"": true,
|
||||
""showVlan"": true,
|
||||
""showVRF"": false,
|
||||
""showSupernetOnly"": false,
|
||||
""DNS"": 0
|
||||
}";
|
||||
handler.WithSuccessResponse(sectionJson);
|
||||
|
||||
// Act
|
||||
var result = await RequestHelper.InvokeRequest("GET", controllers.sections, types.Section, null, null, new[] { "1" });
|
||||
|
||||
// Assert
|
||||
result.Should().BeOfType<Section>();
|
||||
var section = (Section)result!;
|
||||
section.Name.Should().Be("Production");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InvokeRequest_WithCustomFields_ParsesExtendedData()
|
||||
{
|
||||
// Arrange
|
||||
SetupSession();
|
||||
var handler = SetupMockHandler();
|
||||
var addressJson = @"{
|
||||
""id"": 1,
|
||||
""subnetId"": 10,
|
||||
""ip"": ""192.168.1.100"",
|
||||
""hostname"": ""server01"",
|
||||
""custom_environment"": ""production"",
|
||||
""custom_owner"": ""team-a""
|
||||
}";
|
||||
handler.WithSuccessResponse(addressJson);
|
||||
|
||||
// Act
|
||||
var result = await RequestHelper.InvokeRequest("GET", controllers.addresses, types.Address, null, null, new[] { "1" });
|
||||
|
||||
// Assert
|
||||
var address = (Address)result!;
|
||||
address.ExtendedData.Should().NotBeNull();
|
||||
var extendedData = (Dictionary<string, object>)address.ExtendedData!;
|
||||
extendedData.Should().ContainKey("custom_environment");
|
||||
extendedData.Should().ContainKey("custom_owner");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InvokeRequest_WithNoType_ReturnsDynamicData()
|
||||
{
|
||||
// Arrange
|
||||
SetupSession();
|
||||
var handler = SetupMockHandler();
|
||||
handler.WithSuccessResponse(@"{""some"": ""data""}");
|
||||
|
||||
// Act
|
||||
var result = await RequestHelper.InvokeRequest("GET", controllers.tools);
|
||||
|
||||
// Assert
|
||||
result.Should().NotBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InvokeRequest_PostWithParameters_SerializesJsonBody()
|
||||
{
|
||||
// Arrange
|
||||
SetupSession();
|
||||
var handler = SetupMockHandler();
|
||||
handler.WithSuccessResponse(@"{""id"": 1}");
|
||||
|
||||
var parameters = new { ip = "10.0.0.1", subnetId = 5, description = "New address" };
|
||||
|
||||
// Act
|
||||
await RequestHelper.InvokeRequest("POST", controllers.addresses, null, null, parameters);
|
||||
|
||||
// Assert
|
||||
handler.LastRequest!.Content.Should().NotBeNull();
|
||||
var content = await handler.LastRequest.Content!.ReadAsStringAsync();
|
||||
content.Should().Contain("10.0.0.1");
|
||||
content.Should().Contain("subnetId");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InvokeRequest_SetsAcceptJsonHeader()
|
||||
{
|
||||
// Arrange
|
||||
SetupSession();
|
||||
var handler = SetupMockHandler();
|
||||
handler.WithSuccessResponse("{}");
|
||||
|
||||
// Act
|
||||
await RequestHelper.InvokeRequest("GET", controllers.addresses);
|
||||
|
||||
// Assert
|
||||
handler.LastRequest!.Headers.Accept.Should().Contain(h => h.MediaType == "application/json");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InvokeRequest_WithEmptyResponse_ReturnsNull()
|
||||
{
|
||||
// Arrange
|
||||
SetupSession();
|
||||
var handler = SetupMockHandler();
|
||||
handler.WithResponse(HttpStatusCode.OK, "", "application/json");
|
||||
|
||||
// Act
|
||||
var result = await RequestHelper.InvokeRequest("GET", controllers.addresses);
|
||||
|
||||
// Assert
|
||||
result.Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InvokeRequest_WithNullDataInResponse_ReturnsNull()
|
||||
{
|
||||
// Arrange
|
||||
SetupSession();
|
||||
var handler = SetupMockHandler();
|
||||
handler.WithJsonResponse(@"{""code"": 200, ""success"": true, ""data"": null}");
|
||||
|
||||
// Act
|
||||
var result = await RequestHelper.InvokeRequest("GET", controllers.addresses, types.Address);
|
||||
|
||||
// Assert
|
||||
result.Should().BeNull();
|
||||
}
|
||||
}
|
||||
193
classlib.tests/Helpers/SessionManagerTests.cs
Normal file
193
classlib.tests/Helpers/SessionManagerTests.cs
Normal file
@@ -0,0 +1,193 @@
|
||||
namespace PS.IPAM.Tests.Helpers;
|
||||
|
||||
using FluentAssertions;
|
||||
using PS.IPAM;
|
||||
using PS.IPAM.Helpers;
|
||||
using Xunit;
|
||||
|
||||
[Collection("Sequential")]
|
||||
public class SessionManagerTests : IDisposable
|
||||
{
|
||||
public SessionManagerTests()
|
||||
{
|
||||
// Ensure clean state before each test
|
||||
SessionManager.CloseSession();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Clean up after each test
|
||||
SessionManager.CloseSession();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestSession_WhenNoSession_ReturnsNoToken()
|
||||
{
|
||||
// Arrange
|
||||
SessionManager.CurrentSession = null;
|
||||
|
||||
// Act
|
||||
var result = SessionManager.TestSession();
|
||||
|
||||
// Assert
|
||||
result.Should().Be("NoToken");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestSession_WhenSessionWithNullExpires_ReturnsValid()
|
||||
{
|
||||
// Arrange
|
||||
var session = new Session(AuthType.token, "test-token", "app", "https://test.com", null, null);
|
||||
SessionManager.CurrentSession = session;
|
||||
|
||||
// Act
|
||||
var result = SessionManager.TestSession();
|
||||
|
||||
// Assert
|
||||
result.Should().Be("Valid");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestSession_WhenSessionNotExpired_ReturnsValid()
|
||||
{
|
||||
// Arrange
|
||||
var futureExpiry = DateTime.Now.AddHours(1);
|
||||
var session = new Session(AuthType.credentials, "test-token", "app", "https://test.com", futureExpiry, null);
|
||||
SessionManager.CurrentSession = session;
|
||||
|
||||
// Act
|
||||
var result = SessionManager.TestSession();
|
||||
|
||||
// Assert
|
||||
result.Should().Be("Valid");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestSession_WhenSessionExpired_ReturnsExpired()
|
||||
{
|
||||
// Arrange
|
||||
var pastExpiry = DateTime.Now.AddHours(-1);
|
||||
var session = new Session(AuthType.credentials, "test-token", "app", "https://test.com", pastExpiry, null);
|
||||
SessionManager.CurrentSession = session;
|
||||
|
||||
// Act
|
||||
var result = SessionManager.TestSession();
|
||||
|
||||
// Assert
|
||||
result.Should().Be("Expired");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateSessionWithToken_CreatesValidSession()
|
||||
{
|
||||
// Arrange
|
||||
var url = "https://ipam.example.com";
|
||||
var appId = "myApp";
|
||||
var token = "static-api-token";
|
||||
|
||||
// Act
|
||||
var session = SessionManager.CreateSessionWithToken(url, appId, token);
|
||||
|
||||
// Assert
|
||||
session.Should().NotBeNull();
|
||||
session.URL.Should().Be(url);
|
||||
session.AppID.Should().Be(appId);
|
||||
session.Token.Should().Be(token);
|
||||
session.AuthType.Should().Be(AuthType.token);
|
||||
session.Expires.Should().BeNull();
|
||||
session.Credentials.Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateSessionWithToken_SetsCurrentSession()
|
||||
{
|
||||
// Arrange
|
||||
var url = "https://ipam.example.com";
|
||||
var appId = "myApp";
|
||||
var token = "api-token";
|
||||
|
||||
// Act
|
||||
var session = SessionManager.CreateSessionWithToken(url, appId, token);
|
||||
|
||||
// Assert
|
||||
SessionManager.CurrentSession.Should().BeSameAs(session);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CloseSession_ClearsCurrentSession()
|
||||
{
|
||||
// Arrange
|
||||
SessionManager.CreateSessionWithToken("https://test.com", "app", "token");
|
||||
SessionManager.CurrentSession.Should().NotBeNull();
|
||||
|
||||
// Act
|
||||
SessionManager.CloseSession();
|
||||
|
||||
// Assert
|
||||
SessionManager.CurrentSession.Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CloseSession_WhenNoSession_DoesNotThrow()
|
||||
{
|
||||
// Arrange
|
||||
SessionManager.CurrentSession = null;
|
||||
|
||||
// Act
|
||||
var action = () => SessionManager.CloseSession();
|
||||
|
||||
// Assert
|
||||
action.Should().NotThrow();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CurrentSession_CanBeSetDirectly()
|
||||
{
|
||||
// Arrange
|
||||
var session = new Session(AuthType.token, "token", "app", "https://test.com", null, null);
|
||||
|
||||
// Act
|
||||
SessionManager.CurrentSession = session;
|
||||
|
||||
// Assert
|
||||
SessionManager.CurrentSession.Should().BeSameAs(session);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateHttpClient_WithoutIgnoreSsl_ReturnsHttpClient()
|
||||
{
|
||||
// Act
|
||||
using var client = SessionManager.CreateHttpClient(false);
|
||||
|
||||
// Assert
|
||||
client.Should().NotBeNull();
|
||||
client.Should().BeOfType<HttpClient>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateHttpClient_WithIgnoreSsl_ReturnsHttpClient()
|
||||
{
|
||||
// Act
|
||||
using var client = SessionManager.CreateHttpClient(true);
|
||||
|
||||
// Assert
|
||||
client.Should().NotBeNull();
|
||||
client.Should().BeOfType<HttpClient>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateSessionWithToken_ReplacesExistingSession()
|
||||
{
|
||||
// Arrange
|
||||
SessionManager.CreateSessionWithToken("https://old.com", "oldApp", "oldToken");
|
||||
|
||||
// Act
|
||||
var newSession = SessionManager.CreateSessionWithToken("https://new.com", "newApp", "newToken");
|
||||
|
||||
// Assert
|
||||
SessionManager.CurrentSession.Should().BeSameAs(newSession);
|
||||
SessionManager.CurrentSession!.URL.Should().Be("https://new.com");
|
||||
SessionManager.CurrentSession.AppID.Should().Be("newApp");
|
||||
SessionManager.CurrentSession.Token.Should().Be("newToken");
|
||||
}
|
||||
}
|
||||
32
classlib.tests/TestCollections.cs
Normal file
32
classlib.tests/TestCollections.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
namespace PS.IPAM.Tests;
|
||||
|
||||
using Xunit;
|
||||
|
||||
/// <summary>
|
||||
/// Collection definition for tests that share static state (SessionManager, RequestHelper.TestHttpHandler).
|
||||
/// Tests in this collection will run sequentially, not in parallel.
|
||||
/// </summary>
|
||||
[CollectionDefinition("Sequential")]
|
||||
public class SequentialCollection : ICollectionFixture<SequentialTestFixture>
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fixture for sequential test collection.
|
||||
/// </summary>
|
||||
public class SequentialTestFixture : IDisposable
|
||||
{
|
||||
public SequentialTestFixture()
|
||||
{
|
||||
// Clean up before tests
|
||||
PS.IPAM.Helpers.SessionManager.CloseSession();
|
||||
PS.IPAM.Helpers.RequestHelper.TestHttpHandler = null;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Clean up after tests
|
||||
PS.IPAM.Helpers.SessionManager.CloseSession();
|
||||
PS.IPAM.Helpers.RequestHelper.TestHttpHandler = null;
|
||||
}
|
||||
}
|
||||
374
classlib/Helpers/RequestHelper.cs
Normal file
374
classlib/Helpers/RequestHelper.cs
Normal file
@@ -0,0 +1,374 @@
|
||||
namespace PS.IPAM.Helpers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Management.Automation;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using PS.IPAM;
|
||||
|
||||
public static class RequestHelper
|
||||
{
|
||||
// Handler for testing - allows injecting a mock HTTP handler
|
||||
public static HttpMessageHandler? TestHttpHandler { get; set; }
|
||||
|
||||
public static async Task<object?> InvokeRequest(
|
||||
string method,
|
||||
controllers controller,
|
||||
types? type = null,
|
||||
subcontrollers? subController = null,
|
||||
object? parameters = null,
|
||||
string[]? identifiers = null,
|
||||
bool ignoreSsl = false)
|
||||
{
|
||||
var tokenStatus = SessionManager.TestSession();
|
||||
if (tokenStatus == "NoToken")
|
||||
{
|
||||
throw new Exception("No session available!");
|
||||
}
|
||||
|
||||
if (tokenStatus == "Expired")
|
||||
{
|
||||
await UpdateSession();
|
||||
}
|
||||
|
||||
var session = SessionManager.CurrentSession;
|
||||
if (session == null)
|
||||
{
|
||||
throw new Exception("No session available!");
|
||||
}
|
||||
|
||||
var uri = $"{session.URL}/api/{session.AppID}/{controller}";
|
||||
if (subController != null)
|
||||
{
|
||||
uri += $"/{subController}";
|
||||
}
|
||||
if (identifiers != null && identifiers.Length > 0)
|
||||
{
|
||||
uri += $"/{string.Join("/", identifiers)}/";
|
||||
}
|
||||
|
||||
using var client = SessionManager.CreateHttpClient(ignoreSsl, TestHttpHandler);
|
||||
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
|
||||
|
||||
switch (session.AuthType)
|
||||
{
|
||||
case AuthType.credentials:
|
||||
client.DefaultRequestHeaders.Add("token", session.Token);
|
||||
break;
|
||||
case AuthType.token:
|
||||
client.DefaultRequestHeaders.Add("phpipam-token", session.Token);
|
||||
break;
|
||||
}
|
||||
|
||||
HttpResponseMessage? response = null;
|
||||
try
|
||||
{
|
||||
if (method == "GET")
|
||||
{
|
||||
response = await client.GetAsync(uri);
|
||||
}
|
||||
else if (method == "POST")
|
||||
{
|
||||
var jsonContent = parameters != null ? JsonConvert.SerializeObject(parameters) : "{}";
|
||||
var content = new StringContent(jsonContent, Encoding.UTF8, "application/json");
|
||||
response = await client.PostAsync(uri, content);
|
||||
}
|
||||
else if (method == "PATCH")
|
||||
{
|
||||
var jsonContent = parameters != null ? JsonConvert.SerializeObject(parameters) : "{}";
|
||||
var content = new StringContent(jsonContent, Encoding.UTF8, "application/json");
|
||||
var request = new HttpRequestMessage(new HttpMethod("PATCH"), uri)
|
||||
{
|
||||
Content = content
|
||||
};
|
||||
response = await client.SendAsync(request);
|
||||
}
|
||||
else if (method == "DELETE")
|
||||
{
|
||||
response = await client.DeleteAsync(uri);
|
||||
}
|
||||
|
||||
if (response == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var responseContent = await response.Content.ReadAsStringAsync();
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
if (response.StatusCode == System.Net.HttpStatusCode.NotFound)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
throw new HttpRequestException($"Request failed with status {response.StatusCode}");
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(responseContent))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var jsonResponse = JsonConvert.DeserializeObject<dynamic>(responseContent);
|
||||
if (jsonResponse == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (type.HasValue)
|
||||
{
|
||||
return ConvertToTypedObjects(jsonResponse, type.Value);
|
||||
}
|
||||
|
||||
return jsonResponse.data;
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
if (ex.Message.Contains("404"))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private static object? ConvertToTypedObjects(dynamic jsonResponse, types type)
|
||||
{
|
||||
if (jsonResponse?.data == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var data = jsonResponse.data;
|
||||
if (data is JArray array)
|
||||
{
|
||||
return array.Select(item => ConvertSingleObject(item, type)).ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
return ConvertSingleObject(data, type);
|
||||
}
|
||||
}
|
||||
|
||||
private static object? ConvertSingleObject(dynamic item, types type)
|
||||
{
|
||||
var jobject = item as JObject;
|
||||
if (jobject == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var customFields = new Dictionary<string, object>();
|
||||
foreach (var prop in jobject.Properties())
|
||||
{
|
||||
if (prop.Name.StartsWith("custom_"))
|
||||
{
|
||||
customFields[prop.Name] = prop.Value?.ToObject<object>() ?? new object();
|
||||
}
|
||||
}
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case types.Address:
|
||||
return CreateAddress(jobject, customFields);
|
||||
case types.Vlan:
|
||||
return CreateVlan(jobject, customFields);
|
||||
case types.Subnetwork:
|
||||
return CreateSubnetwork(jobject, customFields);
|
||||
case types.Vrf:
|
||||
return CreateVrf(jobject, customFields);
|
||||
case types.Section:
|
||||
return CreateSection(jobject);
|
||||
case types.Tag:
|
||||
return CreateTag(jobject);
|
||||
case types.Nameserver:
|
||||
return CreateNameserver(jobject);
|
||||
case types.Domain:
|
||||
return CreateDomain(jobject);
|
||||
default:
|
||||
return jobject.ToObject<object>();
|
||||
}
|
||||
}
|
||||
|
||||
private static Address CreateAddress(JObject jobject, Dictionary<string, object> customFields)
|
||||
{
|
||||
return new Address(
|
||||
jobject["id"]?.ToObject<int>() ?? 0,
|
||||
jobject["subnetId"]?.ToObject<int>() ?? 0,
|
||||
jobject["ip"]?.ToString() ?? "",
|
||||
jobject["is_gateway"]?.ToObject<bool>() ?? false,
|
||||
jobject["description"]?.ToString() ?? "",
|
||||
jobject["hostname"]?.ToString() ?? "",
|
||||
jobject["mac"]?.ToString() ?? "",
|
||||
jobject["owner"]?.ToString() ?? "",
|
||||
jobject["tag"]?.ToObject<int>() ?? 0,
|
||||
jobject["deviceId"]?.ToObject<int>() ?? 0,
|
||||
jobject["location"]?.ToString() ?? "",
|
||||
jobject["port"]?.ToString() ?? "",
|
||||
jobject["note"]?.ToString() ?? "",
|
||||
jobject["lastSeen"]?.ToObject<DateTime?>(),
|
||||
jobject["excludePing"]?.ToObject<bool>() ?? false,
|
||||
jobject["PTRignore"]?.ToObject<bool>() ?? false,
|
||||
jobject["PTR"]?.ToObject<int>() ?? 0,
|
||||
jobject["firewallAddressObject"]?.ToString() ?? "",
|
||||
jobject["editDate"]?.ToObject<DateTime?>(),
|
||||
jobject["customer_id"]?.ToObject<int>() ?? 0,
|
||||
customFields.Count > 0 ? customFields : null
|
||||
);
|
||||
}
|
||||
|
||||
private static Vlan CreateVlan(JObject jobject, Dictionary<string, object> customFields)
|
||||
{
|
||||
return new Vlan(
|
||||
jobject["vlanId"]?.ToObject<int>() ?? 0,
|
||||
jobject["domainId"]?.ToObject<int>() ?? 0,
|
||||
jobject["name"]?.ToString() ?? "",
|
||||
jobject["number"]?.ToObject<int>() ?? 0,
|
||||
jobject["description"]?.ToString() ?? "",
|
||||
jobject["editDate"]?.ToObject<DateTime?>(),
|
||||
jobject["customer_id"]?.ToObject<int>() ?? 0,
|
||||
customFields.Count > 0 ? customFields : null
|
||||
);
|
||||
}
|
||||
|
||||
private static Subnetwork CreateSubnetwork(JObject jobject, Dictionary<string, object> customFields)
|
||||
{
|
||||
var props = jobject.Properties().ToList();
|
||||
return new Subnetwork(
|
||||
jobject["id"]?.ToObject<int>() ?? 0,
|
||||
jobject["subnet"]?.ToString() ?? "",
|
||||
jobject["mask"]?.ToObject<int>() ?? 0,
|
||||
jobject["sectionId"]?.ToObject<int>() ?? 0,
|
||||
jobject["description"]?.ToString() ?? "",
|
||||
jobject["linked_subnet"]?.ToString() ?? "",
|
||||
jobject["firewallAddressObject"]?.ToString() ?? "",
|
||||
jobject["vrfId"]?.ToObject<int>() ?? 0,
|
||||
jobject["masterSubnetId"]?.ToObject<int>() ?? 0,
|
||||
jobject["allowRequests"]?.ToObject<bool>() ?? false,
|
||||
jobject["vlanId"]?.ToObject<int>() ?? 0,
|
||||
jobject["showName"]?.ToObject<bool>() ?? false,
|
||||
jobject["deviceId"]?.ToObject<int>() ?? 0,
|
||||
jobject["permissions"]?.ToString() ?? "",
|
||||
jobject["pingSubnet"]?.ToObject<bool>() ?? false,
|
||||
jobject["discoverSubnet"]?.ToObject<bool>() ?? false,
|
||||
jobject["resolveDNS"]?.ToObject<bool>() ?? false,
|
||||
jobject["DNSrecursive"]?.ToObject<bool>() ?? false,
|
||||
jobject["DNSrecords"]?.ToObject<bool>() ?? false,
|
||||
jobject["nameserverId"]?.ToObject<int>() ?? 0,
|
||||
jobject["scanAgent"]?.ToObject<bool>() ?? false,
|
||||
jobject["isFolder"]?.ToObject<bool>() ?? false,
|
||||
jobject["isFull"]?.ToObject<bool>() ?? false,
|
||||
jobject["isPool"]?.ToObject<bool>() ?? false,
|
||||
jobject["state"]?.ToObject<int>() ?? 0,
|
||||
jobject["threshold"]?.ToObject<int>() ?? 0,
|
||||
jobject["location"]?.ToObject<int>() ?? 0,
|
||||
jobject["editDate"]?.ToObject<DateTime?>(),
|
||||
jobject["lastScan"]?.ToObject<DateTime?>(),
|
||||
jobject["lastDiscovery"]?.ToObject<DateTime?>(),
|
||||
jobject["calculation"]?.ToObject<object>() ?? new object(),
|
||||
customFields.Count > 0 ? customFields : null
|
||||
);
|
||||
}
|
||||
|
||||
private static Vrf CreateVrf(JObject jobject, Dictionary<string, object> customFields)
|
||||
{
|
||||
return new Vrf(
|
||||
jobject["id"]?.ToObject<int>() ?? 0,
|
||||
jobject["name"]?.ToString() ?? "",
|
||||
jobject["rd"]?.ToString() ?? "",
|
||||
jobject["description"]?.ToString() ?? "",
|
||||
jobject["sections"]?.ToString() ?? "",
|
||||
jobject["editDate"]?.ToObject<DateTime?>(),
|
||||
customFields.Count > 0 ? customFields : null
|
||||
);
|
||||
}
|
||||
|
||||
private static Section CreateSection(JObject jobject)
|
||||
{
|
||||
return new Section(
|
||||
jobject["id"]?.ToObject<int>() ?? 0,
|
||||
jobject["name"]?.ToString() ?? "",
|
||||
jobject["description"]?.ToString() ?? "",
|
||||
jobject["masterSection"]?.ToObject<int>() ?? 0,
|
||||
jobject["permissions"]?.ToString() ?? "",
|
||||
jobject["strictMode"]?.ToObject<bool>() ?? false,
|
||||
jobject["subnetOrdering"]?.ToString() ?? "",
|
||||
jobject["order"]?.ToObject<int>() ?? 0,
|
||||
jobject["editDate"]?.ToObject<DateTime?>(),
|
||||
jobject["showSubnet"]?.ToObject<bool>() ?? false,
|
||||
jobject["showVlan"]?.ToObject<bool>() ?? false,
|
||||
jobject["showVRF"]?.ToObject<bool>() ?? false,
|
||||
jobject["showSupernetOnly"]?.ToObject<bool>() ?? false,
|
||||
jobject["DNS"]?.ToObject<int>() ?? 0
|
||||
);
|
||||
}
|
||||
|
||||
private static Tag CreateTag(JObject jobject)
|
||||
{
|
||||
return new Tag(
|
||||
jobject["id"]?.ToObject<int>() ?? 0,
|
||||
jobject["type"]?.ToString() ?? "",
|
||||
jobject["showtag"]?.ToObject<bool>() ?? false,
|
||||
jobject["bgcolor"]?.ToString() ?? "",
|
||||
jobject["fgcolor"]?.ToString() ?? "",
|
||||
jobject["compress"]?.ToString() ?? "",
|
||||
jobject["locked"]?.ToString() ?? "",
|
||||
jobject["updateTag"]?.ToObject<bool>() ?? false
|
||||
);
|
||||
}
|
||||
|
||||
private static Nameserver CreateNameserver(JObject jobject)
|
||||
{
|
||||
return new Nameserver(
|
||||
jobject["id"]?.ToObject<int>() ?? 0,
|
||||
jobject["name"]?.ToString() ?? "",
|
||||
jobject["nameservers"]?.ToString() ?? "",
|
||||
jobject["description"]?.ToString() ?? "",
|
||||
jobject["permissions"]?.ToString() ?? "",
|
||||
jobject["editDate"]?.ToObject<DateTime?>()
|
||||
);
|
||||
}
|
||||
|
||||
private static Domain CreateDomain(JObject jobject)
|
||||
{
|
||||
return new Domain(
|
||||
jobject["id"]?.ToObject<int>() ?? 0,
|
||||
jobject["name"]?.ToString() ?? "",
|
||||
jobject["description"]?.ToString() ?? "",
|
||||
jobject["sections"]?.ToString() ?? ""
|
||||
);
|
||||
}
|
||||
|
||||
private static async Task UpdateSession()
|
||||
{
|
||||
var session = SessionManager.CurrentSession;
|
||||
if (session == null)
|
||||
{
|
||||
throw new Exception("No session available!");
|
||||
}
|
||||
|
||||
var tokenStatus = SessionManager.TestSession();
|
||||
if (tokenStatus == "Valid")
|
||||
{
|
||||
// Just refresh the token
|
||||
var result = await InvokeRequest("PATCH", controllers.user, null, null, null, null);
|
||||
// Token refresh doesn't return expires in the same format, so we'll skip updating expires
|
||||
return;
|
||||
}
|
||||
|
||||
if (tokenStatus == "Expired" && session.Credentials is PSCredential creds)
|
||||
{
|
||||
await SessionManager.CreateSessionWithCredentials(
|
||||
session.URL,
|
||||
session.AppID,
|
||||
creds,
|
||||
false
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user