From c423cca25a7a3dba63c7ef50eccbdf166e914b56 Mon Sep 17 00:00:00 2001 From: GraceSolutions Date: Tue, 15 Apr 2025 07:40:23 -0400 Subject: [PATCH] Rename Upgrade-OPNSenseFirmware to Start-OPNSenseFirmwareUpgrade to resolve cmdlet name conflict --- CHANGELOG.md | 6 +- README.md | 3 + docs/examples/Firmware-Management.ps1 | 3 +- output/PSOPNSenseAPI/PSOPNSenseAPI.psd1 | 2 +- src/PSOPNSenseAPI/PSOPNSenseAPI.psd1 | 2 +- .../FirmwareCmdletTests.cs | 184 ++++++++++++++++++ .../FirmwareServiceTests.cs | 123 ++++++++++++ 7 files changed, 319 insertions(+), 4 deletions(-) create mode 100644 tests/PSOPNSenseAPI.Tests/FirmwareCmdletTests.cs create mode 100644 tests/PSOPNSenseAPI.Tests/FirmwareServiceTests.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ed1d69..9e57dd7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,11 @@ All notable changes to the PSOPNSenseAPI module will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [Unreleased] +## [2025.04.15.0739] - 2025-04-15 + +### Changed +- Renamed `Upgrade-OPNSenseFirmware` to `Start-OPNSenseFirmwareUpgrade` to resolve cmdlet name conflict +- Added unit tests for firmware management cmdlets ## [0.6.0] - 2025-04-14 diff --git a/README.md b/README.md index 751466a..d9fe29f 100644 --- a/README.md +++ b/README.md @@ -135,6 +135,9 @@ Invoke-OPNSenseNetworkCalculation -Network "10.0.0.0/24" -Operation SupernetSumm # Firmware management Update-OPNSenseFirmware -Wait +# Major firmware upgrade +Start-OPNSenseFirmwareUpgrade -Wait -Timeout 1200 + # Reboot firewall Restart-OPNSenseFirewall -Wait -Timeout 300 diff --git a/docs/examples/Firmware-Management.ps1 b/docs/examples/Firmware-Management.ps1 index 2ab71a3..bb08a5d 100644 --- a/docs/examples/Firmware-Management.ps1 +++ b/docs/examples/Firmware-Management.ps1 @@ -27,7 +27,8 @@ Write-Output "Firmware updated successfully" # Upgrade firmware (major version upgrade) # Note: This will restart the firewall -# Upgrade-OPNSenseFirmware -Wait -Timeout 1200 +Start-OPNSenseFirmwareUpgrade -Wait -Timeout 1200 +Write-Output "Firmware upgraded successfully" # Restart the firewall Restart-OPNSenseFirewall -Wait -Timeout 300 diff --git a/output/PSOPNSenseAPI/PSOPNSenseAPI.psd1 b/output/PSOPNSenseAPI/PSOPNSenseAPI.psd1 index dcc1e13..305cbd6 100644 --- a/output/PSOPNSenseAPI/PSOPNSenseAPI.psd1 +++ b/output/PSOPNSenseAPI/PSOPNSenseAPI.psd1 @@ -1,6 +1,6 @@ @{ RootModule = 'PSOPNSenseAPI.psm1' - ModuleVersion = '2025.04.14.2340' + ModuleVersion = '2025.04.15.0739' GUID = '1f0e4b77-cc7c-4a1e-b45a-d7c51a3c562e' Author = 'PSOPNSenseAPI Contributors' CompanyName = 'PSOPNSenseAPI' diff --git a/src/PSOPNSenseAPI/PSOPNSenseAPI.psd1 b/src/PSOPNSenseAPI/PSOPNSenseAPI.psd1 index 5a2a196..d554578 100644 --- a/src/PSOPNSenseAPI/PSOPNSenseAPI.psd1 +++ b/src/PSOPNSenseAPI/PSOPNSenseAPI.psd1 @@ -3,7 +3,7 @@ RootModule = 'PSOPNSenseAPI.psm1' # Version number of this module. - ModuleVersion = '2025.04.14.2340' + ModuleVersion = '2025.04.15.0739' # Supported PSEditions CompatiblePSEditions = @('Desktop', 'Core') diff --git a/tests/PSOPNSenseAPI.Tests/FirmwareCmdletTests.cs b/tests/PSOPNSenseAPI.Tests/FirmwareCmdletTests.cs new file mode 100644 index 0000000..33f9742 --- /dev/null +++ b/tests/PSOPNSenseAPI.Tests/FirmwareCmdletTests.cs @@ -0,0 +1,184 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using PSOPNSenseAPI.Cmdlets; +using PSOPNSenseAPI.Services; +using PSOPNSenseAPI.Logging; +using System; +using System.Management.Automation; +using System.Threading.Tasks; +using System.Collections.Generic; +using System.Reflection; + +namespace PSOPNSenseAPI.Tests +{ + [TestClass] + public class FirmwareCmdletTests + { + private Mock _mockApiClient; + private Mock _mockLogger; + + [TestInitialize] + public void Setup() + { + _mockApiClient = new Mock("https://example.com", "key", "secret", false, Mock.Of()); + _mockLogger = new Mock(); + } + + [TestMethod] + public void UpdateOPNSenseFirmwareCmdlet_ProcessRecord_CallsUpdateAsync() + { + // Arrange + _mockApiClient.Setup(c => c.PostAsync("core/firmware/update", It.IsAny())) + .ReturnsAsync(new { status = "ok" }); + + // Create and configure the cmdlet + var cmdlet = new UpdateOPNSenseFirmwareCmdlet(); + + // Use reflection to set the protected properties + SetProtectedProperty(cmdlet, "ApiClient", _mockApiClient.Object); + SetProtectedProperty(cmdlet, "Logger", _mockLogger.Object); + + // Act + InvokeMethod(cmdlet, "ProcessRecord"); + + // Assert + _mockApiClient.Verify(c => c.PostAsync("core/firmware/update", It.IsAny()), Times.Once); + } + + [TestMethod] + public void StartOPNSenseFirmwareUpgradeCmdlet_ProcessRecord_CallsUpgradeAsync() + { + // Arrange + _mockApiClient.Setup(c => c.PostAsync("core/firmware/upgrade", It.IsAny())) + .ReturnsAsync(new { status = "ok" }); + + // Create and configure the cmdlet + var cmdlet = new UpgradeOPNSenseFirmwareCmdlet(); + + // Use reflection to set the protected properties + SetProtectedProperty(cmdlet, "ApiClient", _mockApiClient.Object); + SetProtectedProperty(cmdlet, "Logger", _mockLogger.Object); + + // Set Force parameter to skip confirmation + typeof(UpgradeOPNSenseFirmwareCmdlet).GetProperty("Force").SetValue(cmdlet, new SwitchParameter(true)); + + // Act + InvokeMethod(cmdlet, "ProcessRecord"); + + // Assert + _mockApiClient.Verify(c => c.PostAsync("core/firmware/upgrade", It.IsAny()), Times.Once); + } + + [TestMethod] + public void GetOPNSenseFirmwareCmdlet_ProcessRecord_CallsGetStatusAsync() + { + // Arrange + _mockApiClient.Setup(c => c.GetAsync("core/firmware/status")) + .ReturnsAsync(new { status = "ok" }); + + // Create and configure the cmdlet + var cmdlet = new GetOPNSenseFirmwareCmdlet(); + + // Use reflection to set the protected properties + SetProtectedProperty(cmdlet, "ApiClient", _mockApiClient.Object); + SetProtectedProperty(cmdlet, "Logger", _mockLogger.Object); + + // Act + InvokeMethod(cmdlet, "ProcessRecord"); + + // Assert + _mockApiClient.Verify(c => c.GetAsync("core/firmware/status"), Times.Once); + } + + [TestMethod] + public void GetOPNSenseFirmwareCmdlet_WithChangelog_CallsGetChangelogAsync() + { + // Arrange + _mockApiClient.Setup(c => c.GetAsync("core/firmware/changelog")) + .ReturnsAsync(new { changelog = "Changes in version X.Y.Z" }); + + // Create and configure the cmdlet + var cmdlet = new GetOPNSenseFirmwareCmdlet(); + + // Use reflection to set the protected properties + SetProtectedProperty(cmdlet, "ApiClient", _mockApiClient.Object); + SetProtectedProperty(cmdlet, "Logger", _mockLogger.Object); + + // Set Changelog parameter + typeof(GetOPNSenseFirmwareCmdlet).GetProperty("Changelog").SetValue(cmdlet, new SwitchParameter(true)); + + // Act + InvokeMethod(cmdlet, "ProcessRecord"); + + // Assert + _mockApiClient.Verify(c => c.GetAsync("core/firmware/changelog"), Times.Once); + } + + [TestMethod] + public void GetOPNSenseFirmwareCmdlet_WithAudit_CallsGetAuditAsync() + { + // Arrange + _mockApiClient.Setup(c => c.GetAsync("core/firmware/audit")) + .ReturnsAsync(new { audit = new[] { new { name = "package1", version = "1.0.0" } } }); + + // Create and configure the cmdlet + var cmdlet = new GetOPNSenseFirmwareCmdlet(); + + // Use reflection to set the protected properties + SetProtectedProperty(cmdlet, "ApiClient", _mockApiClient.Object); + SetProtectedProperty(cmdlet, "Logger", _mockLogger.Object); + + // Set Audit parameter + typeof(GetOPNSenseFirmwareCmdlet).GetProperty("Audit").SetValue(cmdlet, new SwitchParameter(true)); + + // Act + InvokeMethod(cmdlet, "ProcessRecord"); + + // Assert + _mockApiClient.Verify(c => c.GetAsync("core/firmware/audit"), Times.Once); + } + + private void SetProtectedProperty(object obj, string propertyName, object value) + { + var propertyInfo = obj.GetType().BaseType.GetProperty( + propertyName, + BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); + + if (propertyInfo == null) + { + throw new ArgumentException($"Property {propertyName} not found on {obj.GetType().FullName}"); + } + + var backingField = obj.GetType().BaseType.GetField( + $"<{propertyName}>k__BackingField", + BindingFlags.Instance | BindingFlags.NonPublic); + + if (backingField != null) + { + backingField.SetValue(obj, value); + } + else if (propertyInfo.CanWrite) + { + propertyInfo.SetValue(obj, value); + } + else + { + throw new ArgumentException($"Cannot set property {propertyName} on {obj.GetType().FullName}"); + } + } + + private void InvokeMethod(object obj, string methodName) + { + var methodInfo = obj.GetType().GetMethod( + methodName, + BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); + + if (methodInfo == null) + { + throw new ArgumentException($"Method {methodName} not found on {obj.GetType().FullName}"); + } + + methodInfo.Invoke(obj, null); + } + } +} diff --git a/tests/PSOPNSenseAPI.Tests/FirmwareServiceTests.cs b/tests/PSOPNSenseAPI.Tests/FirmwareServiceTests.cs new file mode 100644 index 0000000..401c220 --- /dev/null +++ b/tests/PSOPNSenseAPI.Tests/FirmwareServiceTests.cs @@ -0,0 +1,123 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using PSOPNSenseAPI.Cmdlets; +using PSOPNSenseAPI.Services; +using PSOPNSenseAPI.Logging; +using System.Threading.Tasks; +using System.Management.Automation; +using System.Collections.ObjectModel; + +namespace PSOPNSenseAPI.Tests +{ + [TestClass] + public class FirmwareServiceTests + { + private Mock _mockApiClient; + private Mock _mockLogger; + private FirmwareService _firmwareService; + + [TestInitialize] + public void Setup() + { + _mockApiClient = new Mock("https://example.com", "key", "secret", false, Mock.Of()); + _mockLogger = new Mock(); + _firmwareService = new FirmwareService(_mockApiClient.Object, _mockLogger.Object); + } + + [TestMethod] + public async Task GetStatusAsync_ReturnsStatus() + { + // Arrange + var expectedResponse = new { status = "ok" }; + _mockApiClient.Setup(c => c.GetAsync("core/firmware/status")) + .ReturnsAsync(expectedResponse); + + // Act + var result = await _firmwareService.GetStatusAsync(); + + // Assert + Assert.AreEqual("ok", result.Status); + _mockApiClient.Verify(c => c.GetAsync("core/firmware/status"), Times.Once); + } + + [TestMethod] + public async Task UpdateAsync_CallsCorrectEndpoint() + { + // Arrange + var expectedResponse = new { status = "ok" }; + _mockApiClient.Setup(c => c.PostAsync("core/firmware/update", It.IsAny())) + .ReturnsAsync(expectedResponse); + + // Act + var result = await _firmwareService.UpdateAsync(); + + // Assert + Assert.AreEqual("ok", result.Status); + _mockApiClient.Verify(c => c.PostAsync("core/firmware/update", It.IsAny()), Times.Once); + } + + [TestMethod] + public async Task UpgradeAsync_CallsCorrectEndpoint() + { + // Arrange + var expectedResponse = new { status = "ok" }; + _mockApiClient.Setup(c => c.PostAsync("core/firmware/upgrade", It.IsAny())) + .ReturnsAsync(expectedResponse); + + // Act + var result = await _firmwareService.UpgradeAsync(); + + // Assert + Assert.AreEqual("ok", result.Status); + _mockApiClient.Verify(c => c.PostAsync("core/firmware/upgrade", It.IsAny()), Times.Once); + } + + [TestMethod] + public async Task GetChangelogAsync_ReturnsChangelog() + { + // Arrange + var expectedResponse = new { changelog = "Changes in version X.Y.Z" }; + _mockApiClient.Setup(c => c.GetAsync("core/firmware/changelog")) + .ReturnsAsync(expectedResponse); + + // Act + var result = await _firmwareService.GetChangelogAsync(); + + // Assert + Assert.AreEqual("Changes in version X.Y.Z", result.Changelog); + _mockApiClient.Verify(c => c.GetAsync("core/firmware/changelog"), Times.Once); + } + + [TestMethod] + public async Task GetHealthAsync_ReturnsHealth() + { + // Arrange + var expectedResponse = new { health = "healthy" }; + _mockApiClient.Setup(c => c.GetAsync("core/firmware/health")) + .ReturnsAsync(expectedResponse); + + // Act + var result = await _firmwareService.GetHealthAsync(); + + // Assert + Assert.AreEqual("healthy", result.Health); + _mockApiClient.Verify(c => c.GetAsync("core/firmware/health"), Times.Once); + } + + [TestMethod] + public async Task GetAuditAsync_ReturnsAudit() + { + // Arrange + var expectedResponse = new { audit = new[] { new { name = "package1", version = "1.0.0" } } }; + _mockApiClient.Setup(c => c.GetAsync("core/firmware/audit")) + .ReturnsAsync(expectedResponse); + + // Act + var result = await _firmwareService.GetAuditAsync(); + + // Assert + Assert.IsNotNull(result.Audit); + _mockApiClient.Verify(c => c.GetAsync("core/firmware/audit"), Times.Once); + } + } +}