Remove Jenkinsfile and add unit tests for various models including Address, Domain, Nameserver, Section, Session, Subnetwork, Tag, Vlan, and Vrf. Introduce mock classes for HTTP requests and cmdlet testing.

This commit is contained in:
2026-01-19 14:46:09 +03:00
parent 114267b1d5
commit 40e3c31c6f
13 changed files with 1109 additions and 21 deletions

View File

@@ -0,0 +1,139 @@
namespace PS.IPAM.Tests.Mocks;
using System.Management.Automation;
using System.Management.Automation.Host;
using System.Reflection;
/// <summary>
/// Helper class for testing PowerShell cmdlets.
/// </summary>
public class CmdletTestHelper<T> : IDisposable where T : PSCmdlet, new()
{
private readonly T _cmdlet;
private readonly List<object> _output = new();
private readonly List<ErrorRecord> _errors = new();
private bool _disposed;
public CmdletTestHelper()
{
_cmdlet = new T();
}
/// <summary>
/// Gets the cmdlet instance for setting parameters.
/// </summary>
public T Cmdlet => _cmdlet;
/// <summary>
/// Gets the output objects written by the cmdlet.
/// </summary>
public IReadOnlyList<object> Output => _output.AsReadOnly();
/// <summary>
/// Gets the errors written by the cmdlet.
/// </summary>
public IReadOnlyList<ErrorRecord> Errors => _errors.AsReadOnly();
/// <summary>
/// Gets whether any errors were written.
/// </summary>
public bool HasErrors => _errors.Count > 0;
/// <summary>
/// Invokes the cmdlet and captures output.
/// </summary>
public void Invoke()
{
// Use reflection to call the protected ProcessRecord method
var processMethod = typeof(T).GetMethod("ProcessRecord",
BindingFlags.NonPublic | BindingFlags.Instance);
if (processMethod == null)
{
throw new InvalidOperationException("ProcessRecord method not found on cmdlet.");
}
// Set up a mock command runtime to capture output
var runtime = new MockCommandRuntime(_output, _errors);
var runtimeProperty = typeof(Cmdlet).GetProperty("CommandRuntime",
BindingFlags.Public | BindingFlags.Instance);
runtimeProperty?.SetValue(_cmdlet, runtime);
// Invoke the method
try
{
processMethod.Invoke(_cmdlet, null);
}
catch (TargetInvocationException ex) when (ex.InnerException != null)
{
// Unwrap the exception
throw ex.InnerException;
}
}
public void Dispose()
{
if (!_disposed)
{
_disposed = true;
}
}
}
/// <summary>
/// Mock implementation of ICommandRuntime for capturing cmdlet output.
/// This is a minimal implementation that only handles the methods we need for testing.
/// </summary>
internal class MockCommandRuntime : ICommandRuntime
{
private readonly List<object> _output;
private readonly List<ErrorRecord> _errors;
public MockCommandRuntime(List<object> output, List<ErrorRecord> errors)
{
_output = output;
_errors = errors;
}
public PSHost Host => null!;
public PSTransactionContext CurrentPSTransaction => null!;
public bool ShouldContinue(string query, string caption) => true;
public bool ShouldContinue(string query, string caption, ref bool yesToAll, ref bool noToAll) => true;
public bool ShouldProcess(string target) => true;
public bool ShouldProcess(string target, string action) => true;
public bool ShouldProcess(string verboseDescription, string verboseWarning, string caption) => true;
public bool ShouldProcess(string verboseDescription, string verboseWarning, string caption, out ShouldProcessReason shouldProcessReason)
{
shouldProcessReason = ShouldProcessReason.None;
return true;
}
public bool TransactionAvailable() => false;
public void ThrowTerminatingError(ErrorRecord errorRecord) => throw errorRecord.Exception;
public void WriteCommandDetail(string text) { }
public void WriteDebug(string text) { }
public void WriteError(ErrorRecord errorRecord) => _errors.Add(errorRecord);
public void WriteObject(object sendToPipeline) => _output.Add(sendToPipeline);
public void WriteObject(object sendToPipeline, bool enumerateCollection)
{
if (enumerateCollection && sendToPipeline is System.Collections.IEnumerable enumerable)
{
foreach (var item in enumerable)
{
_output.Add(item);
}
}
else
{
_output.Add(sendToPipeline);
}
}
public void WriteProgress(ProgressRecord progressRecord) { }
public void WriteProgress(long sourceId, ProgressRecord progressRecord) { }
public void WriteVerbose(string text) { }
public void WriteWarning(string text) { }
}

View File

@@ -0,0 +1,172 @@
namespace PS.IPAM.Tests.Mocks;
using System.Net;
/// <summary>
/// A mock HTTP message handler for testing HTTP requests without making actual network calls.
/// This handler does not dispose itself when the HttpClient is disposed, allowing reuse in tests.
/// </summary>
public class MockHttpMessageHandler : HttpMessageHandler
{
private readonly Queue<MockResponse> _responses = new();
private readonly List<HttpRequestMessage> _requests = new();
private bool _disposed = false;
/// <summary>
/// Gets all requests that were sent through this handler.
/// </summary>
public IReadOnlyList<HttpRequestMessage> Requests => _requests.AsReadOnly();
/// <summary>
/// Gets the last request that was sent through this handler.
/// </summary>
public HttpRequestMessage? LastRequest => _requests.LastOrDefault();
/// <summary>
/// Queues a response to be returned for the next request.
/// </summary>
public MockHttpMessageHandler WithResponse(HttpStatusCode statusCode, string content, string contentType = "application/json")
{
_responses.Enqueue(new MockResponse(statusCode, content, contentType));
return this;
}
/// <summary>
/// Queues a successful JSON response.
/// </summary>
public MockHttpMessageHandler WithJsonResponse(string jsonContent)
{
return WithResponse(HttpStatusCode.OK, jsonContent, "application/json");
}
/// <summary>
/// Queues a successful response with phpIPAM-style wrapper.
/// </summary>
public MockHttpMessageHandler WithSuccessResponse(string dataJson)
{
var response = $"{{\"code\":200,\"success\":true,\"data\":{dataJson}}}";
return WithJsonResponse(response);
}
/// <summary>
/// Queues a 404 Not Found response.
/// </summary>
public MockHttpMessageHandler WithNotFoundResponse()
{
return WithResponse(HttpStatusCode.NotFound, "{\"code\":404,\"success\":false,\"message\":\"Not found\"}", "application/json");
}
/// <summary>
/// Queues an error response.
/// </summary>
public MockHttpMessageHandler WithErrorResponse(HttpStatusCode statusCode, string message)
{
var response = $"{{\"code\":{(int)statusCode},\"success\":false,\"message\":\"{message}\"}}";
return WithResponse(statusCode, response, "application/json");
}
/// <summary>
/// Queues an exception to be thrown on the next request.
/// </summary>
public MockHttpMessageHandler WithException(Exception exception)
{
_responses.Enqueue(new MockResponse(exception));
return this;
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if (_disposed)
{
throw new ObjectDisposedException(nameof(MockHttpMessageHandler));
}
_requests.Add(request);
if (_responses.Count == 0)
{
throw new InvalidOperationException("No mock response configured. Call WithResponse() before making requests.");
}
var mockResponse = _responses.Dequeue();
if (mockResponse.Exception != null)
{
throw mockResponse.Exception;
}
var response = new HttpResponseMessage(mockResponse.StatusCode)
{
Content = new StringContent(mockResponse.Content, System.Text.Encoding.UTF8, mockResponse.ContentType),
RequestMessage = request
};
return Task.FromResult(response);
}
protected override void Dispose(bool disposing)
{
// Don't actually dispose - allow reuse in tests
// The test itself is responsible for cleanup
}
/// <summary>
/// Actually disposes the handler. Call this in test cleanup.
/// </summary>
public void ForceDispose()
{
_disposed = true;
base.Dispose(true);
}
/// <summary>
/// Verifies that a request was made to the expected URL.
/// </summary>
public bool WasRequestMadeTo(string urlContains)
{
return _requests.Any(r => r.RequestUri?.ToString().Contains(urlContains) == true);
}
/// <summary>
/// Verifies that a request with the expected method was made.
/// </summary>
public bool WasRequestMadeWithMethod(HttpMethod method)
{
return _requests.Any(r => r.Method == method);
}
/// <summary>
/// Gets the value of a header from the last request.
/// </summary>
public string? GetLastRequestHeader(string headerName)
{
if (LastRequest?.Headers.TryGetValues(headerName, out var values) == true)
{
return values.FirstOrDefault();
}
return null;
}
private class MockResponse
{
public HttpStatusCode StatusCode { get; }
public string Content { get; }
public string ContentType { get; }
public Exception? Exception { get; }
public MockResponse(HttpStatusCode statusCode, string content, string contentType)
{
StatusCode = statusCode;
Content = content;
ContentType = contentType;
}
public MockResponse(Exception exception)
{
Exception = exception;
StatusCode = HttpStatusCode.InternalServerError;
Content = string.Empty;
ContentType = string.Empty;
}
}
}

View File

@@ -0,0 +1,116 @@
namespace PS.IPAM.Tests.Models;
using FluentAssertions;
using PS.IPAM;
using Xunit;
public class AddressTests
{
[Fact]
public void Constructor_SetsAllProperties()
{
// Arrange
var id = 1;
var subnetId = 10;
var ip = "192.168.1.100";
var isGateway = true;
var description = "Test server";
var hostname = "server01.example.com";
var mac = "00:11:22:33:44:55";
var owner = "admin";
var tagId = 2;
var deviceId = 5;
var location = "DC1";
var port = "eth0";
var note = "Production server";
var lastSeen = new DateTime(2026, 1, 15, 10, 30, 0);
var excludePing = false;
var ptrIgnore = true;
var ptr = 1;
var firewallObject = "FW_SERVER01";
var editDate = new DateTime(2026, 1, 10, 8, 0, 0);
var customerId = 100;
var extendedData = new Dictionary<string, object> { { "custom_field1", "value1" } };
// Act
var address = new Address(
id, subnetId, ip, isGateway, description, hostname, mac, owner,
tagId, deviceId, location, port, note, lastSeen, excludePing,
ptrIgnore, ptr, firewallObject, editDate, customerId, extendedData
);
// Assert
address.Id.Should().Be(id);
address.SubnetId.Should().Be(subnetId);
address.Ip.Should().Be(ip);
address.IsGateway.Should().Be(isGateway);
address.Description.Should().Be(description);
address.Hostname.Should().Be(hostname);
address.MAC.Should().Be(mac);
address.Owner.Should().Be(owner);
address.TagId.Should().Be(tagId);
address.DeviceId.Should().Be(deviceId);
address.Location.Should().Be(location);
address.Port.Should().Be(port);
address.Note.Should().Be(note);
address.LastSeen.Should().Be(lastSeen);
address.ExcludePing.Should().Be(excludePing);
address.PTRignore.Should().Be(ptrIgnore);
address.PTR.Should().Be(ptr);
address.FirewallAddressObject.Should().Be(firewallObject);
address.EditDate.Should().Be(editDate);
address.CustomerId.Should().Be(customerId);
address.ExtendedData.Should().BeEquivalentTo(extendedData);
}
[Fact]
public void Constructor_WithNullOptionalFields_SetsNullValues()
{
// Act
var address = new Address(
1, 10, "10.0.0.1", false, "", "", "", "",
0, 0, "", "", "", null, false,
false, 0, "", null, 0, null
);
// Assert
address.LastSeen.Should().BeNull();
address.EditDate.Should().BeNull();
address.ExtendedData.Should().BeNull();
}
[Fact]
public void ToString_ReturnsIpAddress()
{
// Arrange
var address = new Address(
1, 10, "192.168.1.50", false, "Test", "host.local", "", "",
0, 0, "", "", "", null, false,
false, 0, "", null, 0, null
);
// Act
var result = address.ToString();
// Assert
result.Should().Be("192.168.1.50");
}
[Theory]
[InlineData("10.0.0.1")]
[InlineData("172.16.0.100")]
[InlineData("192.168.255.255")]
[InlineData("2001:db8::1")]
public void ToString_ReturnsCorrectIp_ForVariousAddresses(string expectedIp)
{
// Arrange
var address = new Address(
1, 1, expectedIp, false, "", "", "", "",
0, 0, "", "", "", null, false,
false, 0, "", null, 0, null
);
// Act & Assert
address.ToString().Should().Be(expectedIp);
}
}

View File

@@ -0,0 +1,43 @@
namespace PS.IPAM.Tests.Models;
using FluentAssertions;
using PS.IPAM;
using Xunit;
public class DomainTests
{
[Fact]
public void Constructor_SetsAllProperties()
{
// Arrange
var id = 1;
var name = "Default";
var description = "Default L2 domain";
var sections = "1;2;3";
// Act
var domain = new Domain(id, name, description, sections);
// Assert
domain.Id.Should().Be(id);
domain.Name.Should().Be(name);
domain.Description.Should().Be(description);
domain.Sections.Should().Be(sections);
}
[Theory]
[InlineData("Default")]
[InlineData("Datacenter1")]
[InlineData("Branch_Office")]
public void ToString_ReturnsDomainName(string domainName)
{
// Arrange
var domain = new Domain(1, domainName, "", "");
// Act
var result = domain.ToString();
// Assert
result.Should().Be(domainName);
}
}

View File

@@ -0,0 +1,82 @@
namespace PS.IPAM.Tests.Models;
using FluentAssertions;
using PS.IPAM;
using Xunit;
public class NameserverTests
{
[Fact]
public void Constructor_SetsAllProperties()
{
// Arrange
var id = 1;
var name = "Google DNS";
var nameServers = "8.8.8.8;8.8.4.4";
var description = "Google Public DNS";
var permissions = "{\"3\":\"2\"}";
var editDate = new DateTime(2026, 1, 10);
// Act
var nameserver = new Nameserver(id, name, nameServers, description, permissions, editDate);
// Assert
nameserver.Id.Should().Be(id);
nameserver.Name.Should().Be(name);
nameserver.Description.Should().Be(description);
nameserver.Permissions.Should().Be(permissions);
nameserver.EditDate.Should().Be(editDate);
}
[Fact]
public void Constructor_ParsesNameservers_BySemicolon()
{
// Arrange
var nameServersString = "8.8.8.8;8.8.4.4;1.1.1.1";
// Act
var nameserver = new Nameserver(1, "Test", nameServersString, "", "", null);
// Assert
nameserver.NameServers.Should().HaveCount(3);
nameserver.NameServers.Should().ContainInOrder("8.8.8.8", "8.8.4.4", "1.1.1.1");
}
[Fact]
public void Constructor_WithSingleNameserver_ReturnsArrayWithOneElement()
{
// Arrange
var nameServersString = "8.8.8.8";
// Act
var nameserver = new Nameserver(1, "Test", nameServersString, "", "", null);
// Assert
nameserver.NameServers.Should().HaveCount(1);
nameserver.NameServers[0].Should().Be("8.8.8.8");
}
[Fact]
public void Constructor_WithEmptyNameservers_ReturnsArrayWithEmptyString()
{
// Arrange
var nameServersString = "";
// Act
var nameserver = new Nameserver(1, "Test", nameServersString, "", "", null);
// Assert
nameserver.NameServers.Should().HaveCount(1);
nameserver.NameServers[0].Should().BeEmpty();
}
[Fact]
public void Constructor_WithNullEditDate_SetsNull()
{
// Act
var nameserver = new Nameserver(1, "Test", "8.8.8.8", "", "", null);
// Assert
nameserver.EditDate.Should().BeNull();
}
}

View File

@@ -0,0 +1,77 @@
namespace PS.IPAM.Tests.Models;
using FluentAssertions;
using PS.IPAM;
using Xunit;
public class SectionTests
{
[Fact]
public void Constructor_SetsAllProperties()
{
// Arrange
var id = 1;
var name = "Production";
var description = "Production networks section";
var masterSectionId = 0;
var permissions = "{\"3\":\"2\"}";
var strictMode = true;
var subnetOrdering = "subnet,asc";
var order = 1;
var editDate = new DateTime(2026, 1, 5);
var showSubnet = true;
var showVlan = true;
var showVRF = false;
var showSupernetOnly = false;
var dnsId = 1;
// Act
var section = new Section(
id, name, description, masterSectionId, permissions, strictMode,
subnetOrdering, order, editDate, showSubnet, showVlan, showVRF,
showSupernetOnly, dnsId
);
// Assert
section.Id.Should().Be(id);
section.Name.Should().Be(name);
section.Description.Should().Be(description);
section.MasterSectionId.Should().Be(masterSectionId);
section.Permissions.Should().Be(permissions);
section.StrictMode.Should().Be(strictMode);
section.SubnetOrdering.Should().Be(subnetOrdering);
section.Order.Should().Be(order);
section.EditDate.Should().Be(editDate);
section.ShowSubnet.Should().Be(showSubnet);
section.ShowVlan.Should().Be(showVlan);
section.ShowVRF.Should().Be(showVRF);
section.ShowSupernetOnly.Should().Be(showSupernetOnly);
section.DNSId.Should().Be(dnsId);
}
[Theory]
[InlineData("Production")]
[InlineData("Development")]
[InlineData("DMZ")]
public void ToString_ReturnsSectionName(string sectionName)
{
// Arrange
var section = new Section(1, sectionName, "", 0, "", false, "", 0, null, false, false, false, false, 0);
// Act
var result = section.ToString();
// Assert
result.Should().Be(sectionName);
}
[Fact]
public void Constructor_WithNullEditDate_SetsNull()
{
// Act
var section = new Section(1, "Test", "", 0, "", false, "", 0, null, false, false, false, false, 0);
// Assert
section.EditDate.Should().BeNull();
}
}

View File

@@ -0,0 +1,92 @@
namespace PS.IPAM.Tests.Models;
using FluentAssertions;
using PS.IPAM;
using Xunit;
public class SessionTests
{
[Fact]
public void Constructor_WithCredentialsAuth_SetsAllProperties()
{
// Arrange
var authType = AuthType.credentials;
var token = "test-token-123";
var appId = "myApp";
var url = "https://ipam.example.com";
var expires = new DateTime(2026, 12, 31, 23, 59, 59);
var credentials = new object(); // Mock credentials
// Act
var session = new Session(authType, token, appId, url, expires, credentials);
// Assert
session.AuthType.Should().Be(AuthType.credentials);
session.Token.Should().Be(token);
session.AppID.Should().Be(appId);
session.URL.Should().Be(url);
session.Expires.Should().Be(expires);
session.Credentials.Should().BeSameAs(credentials);
}
[Fact]
public void Constructor_WithTokenAuth_SetsAllProperties()
{
// Arrange
var authType = AuthType.token;
var token = "static-api-token";
var appId = "apiApp";
var url = "https://ipam.test.com";
// Act
var session = new Session(authType, token, appId, url, null, null);
// Assert
session.AuthType.Should().Be(AuthType.token);
session.Token.Should().Be(token);
session.AppID.Should().Be(appId);
session.URL.Should().Be(url);
session.Expires.Should().BeNull();
session.Credentials.Should().BeNull();
}
[Fact]
public void Token_CanBeModified()
{
// Arrange
var session = new Session(AuthType.credentials, "old-token", "app", "https://test.com", null, null);
// Act
session.Token = "new-token";
// Assert
session.Token.Should().Be("new-token");
}
[Fact]
public void Expires_CanBeModified()
{
// Arrange
var session = new Session(AuthType.credentials, "token", "app", "https://test.com", null, null);
var newExpiry = new DateTime(2027, 1, 1);
// Act
session.Expires = newExpiry;
// Assert
session.Expires.Should().Be(newExpiry);
}
[Fact]
public void ToString_ReturnsDefaultObjectString()
{
// Arrange
var session = new Session(AuthType.token, "token", "app", "https://test.com", null, null);
// Act
var result = session.ToString();
// Assert
result.Should().Contain("PS.IPAM.Session");
}
}

View File

@@ -0,0 +1,142 @@
namespace PS.IPAM.Tests.Models;
using FluentAssertions;
using PS.IPAM;
using Xunit;
public class SubnetworkTests
{
[Fact]
public void Constructor_SetsAllProperties()
{
// Arrange
var id = 1;
var subnet = "192.168.1.0";
var mask = 24;
var sectionId = 5;
var description = "Production network";
var linkedSubnet = "linked-123";
var firewallObject = "FW_PROD";
var vrfId = 2;
var masterSubnetId = 0;
var allowRequests = true;
var vlanId = 100;
var showName = true;
var deviceId = 10;
var permissions = "rw";
var pingSubnet = true;
var discoverSubnet = false;
var resolveDNS = true;
var dnsRecursive = false;
var dnsRecords = true;
var nameserverId = 3;
var scanAgent = false;
var isFolder = false;
var isFull = false;
var isPool = true;
var tagId = 1;
var threshold = 80;
var locationId = 4;
var editDate = new DateTime(2026, 1, 10);
var lastScan = new DateTime(2026, 1, 9);
var lastDiscovery = new DateTime(2026, 1, 8);
var calculation = new { maxhosts = 254 };
var customFields = new Dictionary<string, object> { { "custom_env", "prod" } };
// Act
var subnetwork = new Subnetwork(
id, subnet, mask, sectionId, description, linkedSubnet, firewallObject,
vrfId, masterSubnetId, allowRequests, vlanId, showName, deviceId,
permissions, pingSubnet, discoverSubnet, resolveDNS, dnsRecursive,
dnsRecords, nameserverId, scanAgent, isFolder, isFull, isPool,
tagId, threshold, locationId, editDate, lastScan, lastDiscovery,
calculation, customFields
);
// Assert
subnetwork.Id.Should().Be(id);
subnetwork.Subnet.Should().Be(subnet);
subnetwork.Mask.Should().Be(mask);
subnetwork.SectionId.Should().Be(sectionId);
subnetwork.Description.Should().Be(description);
subnetwork.LinkedSubnet.Should().Be(linkedSubnet);
subnetwork.FirewallAddressObject.Should().Be(firewallObject);
subnetwork.VrfId.Should().Be(vrfId);
subnetwork.MasterSubnetId.Should().Be(masterSubnetId);
subnetwork.AllowRequests.Should().Be(allowRequests);
subnetwork.VlanId.Should().Be(vlanId);
subnetwork.ShowName.Should().Be(showName);
subnetwork.DeviceId.Should().Be(deviceId);
subnetwork.Permissions.Should().Be(permissions);
subnetwork.PingSubnet.Should().Be(pingSubnet);
subnetwork.DiscoverSubnet.Should().Be(discoverSubnet);
subnetwork.ResolveDNS.Should().Be(resolveDNS);
subnetwork.DNSRecursive.Should().Be(dnsRecursive);
subnetwork.DNSRecords.Should().Be(dnsRecords);
subnetwork.NameserverId.Should().Be(nameserverId);
subnetwork.ScanAgent.Should().Be(scanAgent);
subnetwork.IsFolder.Should().Be(isFolder);
subnetwork.IsFull.Should().Be(isFull);
subnetwork.IsPool.Should().Be(isPool);
subnetwork.TagId.Should().Be(tagId);
subnetwork.Threshold.Should().Be(threshold);
subnetwork.LocationId.Should().Be(locationId);
subnetwork.EditDate.Should().Be(editDate);
subnetwork.LastScan.Should().Be(lastScan);
subnetwork.LastDiscovery.Should().Be(lastDiscovery);
subnetwork.Calculation.Should().BeEquivalentTo(calculation);
subnetwork.ExtendedData.Should().BeEquivalentTo(customFields);
}
[Theory]
[InlineData("192.168.1.0", 24, "192.168.1.0/24")]
[InlineData("10.0.0.0", 8, "10.0.0.0/8")]
[InlineData("172.16.0.0", 16, "172.16.0.0/16")]
[InlineData("192.168.100.0", 30, "192.168.100.0/30")]
public void GetCIDR_ReturnsCidrNotation(string subnet, int mask, string expectedCidr)
{
// Arrange
var subnetwork = CreateSubnetwork(subnet, mask);
// Act
var result = subnetwork.GetCIDR();
// Assert
result.Should().Be(expectedCidr);
}
[Fact]
public void ToString_ReturnsCidrNotation()
{
// Arrange
var subnetwork = CreateSubnetwork("10.10.0.0", 16);
// Act
var result = subnetwork.ToString();
// Assert
result.Should().Be("10.10.0.0/16");
}
[Fact]
public void ToString_EqualsGetCIDR()
{
// Arrange
var subnetwork = CreateSubnetwork("172.20.0.0", 12);
// Act & Assert
subnetwork.ToString().Should().Be(subnetwork.GetCIDR());
}
private static Subnetwork CreateSubnetwork(string subnet, int mask)
{
return new Subnetwork(
1, subnet, mask, 1, "", "", "",
0, 0, false, 0, false, 0,
"", false, false, false, false,
false, 0, false, false, false, false,
0, 0, 0, null, null, null,
new object(), null
);
}
}

View File

@@ -0,0 +1,77 @@
namespace PS.IPAM.Tests.Models;
using FluentAssertions;
using PS.IPAM;
using Xunit;
public class TagTests
{
[Fact]
public void Constructor_SetsAllProperties()
{
// Arrange
var id = 1;
var type = "Used";
var showTag = true;
var bgColor = "#5cb85c";
var fgColor = "#ffffff";
var compress = "Yes";
var locked = "No";
var updateTag = true;
// Act
var tag = new Tag(id, type, showTag, bgColor, fgColor, compress, locked, updateTag);
// Assert
tag.Id.Should().Be(id);
tag.Type.Should().Be(type);
tag.ShowTag.Should().Be(showTag);
tag.BGColor.Should().Be(bgColor);
tag.FGColor.Should().Be(fgColor);
tag.Compress.Should().BeTrue();
tag.Locked.Should().BeFalse();
tag.UpdateTag.Should().Be(updateTag);
}
[Theory]
[InlineData("Yes", true)]
[InlineData("No", false)]
[InlineData("", false)]
[InlineData("yes", false)] // Case sensitive
[InlineData("true", false)] // Only "Yes" is true
public void StringToBool_ConvertsCorrectly(string input, bool expected)
{
// The StringToBool is private, so we test through the constructor
// Using Compress field which uses StringToBool
var tag = new Tag(1, "Test", false, "", "", input, "No", false);
tag.Compress.Should().Be(expected);
}
[Theory]
[InlineData("Used")]
[InlineData("Available")]
[InlineData("Reserved")]
[InlineData("DHCP")]
public void ToString_ReturnsTagType(string tagType)
{
// Arrange
var tag = new Tag(1, tagType, false, "", "", "No", "No", false);
// Act
var result = tag.ToString();
// Assert
result.Should().Be(tagType);
}
[Fact]
public void Locked_WithYes_IsTrue()
{
// Arrange & Act
var tag = new Tag(1, "Test", false, "", "", "No", "Yes", false);
// Assert
tag.Locked.Should().BeTrue();
}
}

View File

@@ -0,0 +1,76 @@
namespace PS.IPAM.Tests.Models;
using FluentAssertions;
using PS.IPAM;
using Xunit;
public class VlanTests
{
[Fact]
public void Constructor_SetsAllProperties()
{
// Arrange
var vlanId = 100;
var domainId = 1;
var name = "Production VLAN";
var number = 100;
var description = "Production network VLAN";
var editDate = new DateTime(2026, 1, 15);
var customerId = 50;
var customFields = new Dictionary<string, object> { { "custom_location", "DC1" } };
// Act
var vlan = new Vlan(vlanId, domainId, name, number, description, editDate, customerId, customFields);
// Assert
vlan.Id.Should().Be(vlanId);
vlan.VlanId.Should().Be(vlanId);
vlan.DomainId.Should().Be(domainId);
vlan.Name.Should().Be(name);
vlan.Number.Should().Be(number);
vlan.Description.Should().Be(description);
vlan.EditDate.Should().Be(editDate);
vlan.CustomerId.Should().Be(customerId);
vlan.ExtendedData.Should().BeEquivalentTo(customFields);
}
[Fact]
public void Id_And_VlanId_AreSameValue()
{
// Arrange
var vlanId = 200;
// Act
var vlan = new Vlan(vlanId, 1, "Test", 200, "", null, 0, null);
// Assert
vlan.Id.Should().Be(vlan.VlanId);
}
[Theory]
[InlineData(1)]
[InlineData(100)]
[InlineData(4094)]
public void ToString_ReturnsVlanNumber(int vlanNumber)
{
// Arrange
var vlan = new Vlan(1, 1, "Test", vlanNumber, "", null, 0, null);
// Act
var result = vlan.ToString();
// Assert
result.Should().Be(vlanNumber.ToString());
}
[Fact]
public void Constructor_WithNullOptionalFields_SetsNullValues()
{
// Act
var vlan = new Vlan(1, 1, "Test", 10, "", null, 0, null);
// Assert
vlan.EditDate.Should().BeNull();
vlan.ExtendedData.Should().BeNull();
}
}

View File

@@ -0,0 +1,60 @@
namespace PS.IPAM.Tests.Models;
using FluentAssertions;
using PS.IPAM;
using Xunit;
public class VrfTests
{
[Fact]
public void Constructor_SetsAllProperties()
{
// Arrange
var id = 1;
var name = "VRF_PROD";
var rd = "65000:100";
var description = "Production VRF";
var sections = "1;2;3";
var editDate = new DateTime(2026, 1, 10);
var customFields = new Dictionary<string, object> { { "custom_tenant", "CustomerA" } };
// Act
var vrf = new Vrf(id, name, rd, description, sections, editDate, customFields);
// Assert
vrf.Id.Should().Be(id);
vrf.Name.Should().Be(name);
vrf.RouteDistinguisher.Should().Be(rd);
vrf.Description.Should().Be(description);
vrf.Sections.Should().Be(sections);
vrf.EditDate.Should().Be(editDate);
vrf.ExtendedData.Should().BeEquivalentTo(customFields);
}
[Theory]
[InlineData("VRF_DEFAULT")]
[InlineData("Production_VRF")]
[InlineData("CUSTOMER_A")]
public void ToString_ReturnsVrfName(string vrfName)
{
// Arrange
var vrf = new Vrf(1, vrfName, "65000:1", "", "", null, null);
// Act
var result = vrf.ToString();
// Assert
result.Should().Be(vrfName);
}
[Fact]
public void Constructor_WithNullOptionalFields_SetsNullValues()
{
// Act
var vrf = new Vrf(1, "Test", "65000:1", "", "", null, null);
// Assert
vrf.EditDate.Should().BeNull();
vrf.ExtendedData.Should().BeNull();
}
}

View File

@@ -0,0 +1,33 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<LangVersion>latest</LangVersion>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
<RootNamespace>PS.IPAM.Tests</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="xunit" Version="2.6.4" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="6.0.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Moq" Version="4.20.70" />
<PackageReference Include="FluentAssertions" Version="6.12.0" />
<PackageReference Include="PowerShellStandard.Library" Version="5.1.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\classlib\classlib.csproj" />
</ItemGroup>
</Project>