tests/Hybrid.Module.Tests.ps1
|
#Requires -Modules @{ ModuleName = 'Pester'; ModuleVersion = '5.0.0' } #Requires -Modules ImportExcel <# .SYNOPSIS Pester tests for all Hybrid (Arc + Azure Local) inventory modules. .DESCRIPTION Tests both Processing and Reporting phases for each Hybrid module using synthetic mock data. No live Azure authentication is required. .NOTES Author: AzureScout Contributors Version: 1.0.0 Created: 2026-02-24 Phase: 16.5, 8.1.8, 8.2.5, 19.3 — Hybrid / Arc / Azure Local Testing #> # =================================================================== # DISCOVERY-TIME # =================================================================== $HybridPath = Join-Path (Split-Path -Parent $PSScriptRoot) 'Modules' 'Public' 'InventoryModules' 'Hybrid' $HybridModules = @( @{ Name = 'ARCServers'; File = 'ARCServers.ps1'; Type = 'microsoft.hybridcompute/machines'; Worksheet = 'Arc Servers' } @{ Name = 'ArcGateways'; File = 'ArcGateways.ps1'; Type = 'microsoft.hybridcompute/gateways'; Worksheet = 'Arc Gateways' } @{ Name = 'ArcKubernetes'; File = 'ArcKubernetes.ps1'; Type = 'microsoft.kubernetes/connectedclusters'; Worksheet = 'Arc Kubernetes' } @{ Name = 'ArcResourceBridge'; File = 'ArcResourceBridge.ps1'; Type = 'microsoft.resourceconnector/appliances'; Worksheet = 'Arc Resource Bridge' } @{ Name = 'ArcExtensions'; File = 'ArcExtensions.ps1'; Type = 'microsoft.hybridcompute/machines/extensions'; Worksheet = 'Arc Extensions' } @{ Name = 'ArcSQLServers'; File = 'ArcSQLServers.ps1'; Type = 'microsoft.azurearcdata/sqlserverinstances'; Worksheet = 'Arc SQL Servers' } @{ Name = 'ArcSQLManagedInstances';File = 'ArcSQLManagedInstances.ps1';Type = 'microsoft.azurearcdata/sqlmanagedinstances'; Worksheet = 'Arc SQL Managed Instances' } @{ Name = 'ArcDataControllers'; File = 'ArcDataControllers.ps1'; Type = 'microsoft.azurearcdata/datacontrollers'; Worksheet = 'Arc Data Controllers' } @{ Name = 'ArcSites'; File = 'ArcSites.ps1'; Type = 'microsoft.hybridcompute/sites'; Worksheet = 'Arc Sites' } @{ Name = 'Clusters'; File = 'Clusters.ps1'; Type = 'microsoft.azurestackhci/clusters'; Worksheet = 'Azure Local Clusters' } @{ Name = 'VirtualMachines'; File = 'VirtualMachines.ps1'; Type = 'microsoft.azurestackhci/virtualmachineinstances'; Worksheet = 'Azure Local VMs' } @{ Name = 'LogicalNetworks'; File = 'LogicalNetworks.ps1'; Type = 'microsoft.azurestackhci/logicalnetworks'; Worksheet = 'Azure Local Logical Networks' } @{ Name = 'StorageContainers'; File = 'StorageContainers.ps1'; Type = 'microsoft.azurestackhci/storagecontainers'; Worksheet = 'Azure Local Storage Paths' } @{ Name = 'GalleryImages'; File = 'GalleryImages.ps1'; Type = 'microsoft.azurestackhci/galleryimages'; Worksheet = 'Azure Local Gallery Images' } @{ Name = 'MarketplaceGalleryImages'; File = 'MarketplaceGalleryImages.ps1'; Type = 'microsoft.azurestackhci/marketplacegalleryimages'; Worksheet = 'Azure Local Marketplace Images' } @{ Name = 'ArcServerOperationalData'; File = 'ArcServerOperationalData.ps1'; Type = 'microsoft.hybridcompute/machines'; Worksheet = 'Arc Server Ops' } ) # =================================================================== # EXECUTION-TIME SETUP # =================================================================== BeforeAll { $script:ModuleRoot = Split-Path -Parent $PSScriptRoot $script:HybridPath = Join-Path $script:ModuleRoot 'Modules' 'Public' 'InventoryModules' 'Hybrid' $script:TempDir = Join-Path $env:TEMP 'AZSC_HybridTests' if (Test-Path $script:TempDir) { Remove-Item $script:TempDir -Recurse -Force } New-Item -ItemType Directory -Path $script:TempDir -Force | Out-Null function New-MockHybridResource { param([string]$Id, [string]$Name, [string]$Type, [string]$Kind = '', [string]$Location = 'eastus', [string]$RG = 'rg-hybrid', [string]$SubscriptionId = 'sub-00000001', [object]$Props) [PSCustomObject]@{ id = $Id NAME = $Name TYPE = $Type KIND = $Kind LOCATION = $Location RESOURCEGROUP = $RG subscriptionId = $SubscriptionId tags = [PSCustomObject]@{} PROPERTIES = $Props } } $script:MockResources = @() # Arc Server $script:MockResources += New-MockHybridResource -Id '/hybrid/sub-00000001/arc/srv01' -Name 'arc-server-01' ` -Type 'microsoft.hybridcompute/machines' -Props ([PSCustomObject]@{ agentVersion = '1.21.0'; status = 'Connected'; osType = 'Windows'; osName = 'Windows Server 2019' osSku = 'Windows Server 2019 Datacenter'; osVersion = '10.0.17763' privateLinkScopeResourceId = $null; cloudMetadata = $null locationData = [PSCustomObject]@{ name = 'Datacenter 1'; city = 'Atlanta'; countryOrRegion = 'US' } }) # Arc Extension $script:MockResources += New-MockHybridResource -Id '/hybrid/sub-00000001/arc/srv01/ext/mma' -Name 'MicrosoftMonitoringAgent' ` -Type 'microsoft.hybridcompute/machines/extensions' -Props ([PSCustomObject]@{ publisher = 'Microsoft.EnterpriseCloud.Monitoring'; typeHandlerVersion = '1.0.18069.0' autoUpgradeMinorVersion = $true; enableAutomaticUpgrade = $true provisioningState = 'Succeeded'; settings = '{"workspaceId":"ws-001"}' }) # Arc Gateway $script:MockResources += New-MockHybridResource -Id '/hybrid/sub-00000001/arc/gw1' -Name 'arc-gw-1' ` -Type 'microsoft.hybridcompute/gateways' -Props ([PSCustomObject]@{ gatewayId = 'gw-id-001'; gatewayType = 'Public'; gatewayEndpoint = 'https://gw.arc.azure.com' provisioningState = 'Succeeded'; allowedFeatures = @('*') }) # Arc Kubernetes $script:MockResources += New-MockHybridResource -Id '/hybrid/sub-00000001/k8s/k8s1' -Name 'arc-k8s-prod' ` -Type 'microsoft.kubernetes/connectedclusters' -Props ([PSCustomObject]@{ connectivityStatus = 'Connected'; agentVersion = '1.14.5'; kubernetesVersion = '1.28.3' distribution = 'AKSEdge'; provisioningState = 'Succeeded' }) # Arc Resource Bridge $script:MockResources += New-MockHybridResource -Id '/hybrid/sub-00000001/rb/rb1' -Name 'arc-bridge-1' ` -Type 'microsoft.resourceconnector/appliances' -Props ([PSCustomObject]@{ status = 'Running'; provisioningState = 'Succeeded'; distro = 'AKSEdge' infrastructureConfig = [PSCustomObject]@{ provider = 'VMWare' } }) # Arc SQL Server $script:MockResources += New-MockHybridResource -Id '/hybrid/sub-00000001/arcsql/sql1' -Name 'arc-sql-01' ` -Type 'microsoft.azurearcdata/sqlserverinstances' -Props ([PSCustomObject]@{ version = 'SQL Server 2019'; edition = 'Enterprise'; licenseType = 'HADR' vCore = 8; patchLevel = '15.0.4123.1'; collation = 'SQL_Latin1_General_CP1_CI_AS' containerResourceId = '/hybrid/sub-00000001/arc/srv01'; azureDefenderStatus = 'Protected' provisioningState = 'Succeeded' }) # Arc SQL Managed Instance $script:MockResources += New-MockHybridResource -Id '/hybrid/sub-00000001/arcsqlmi/mi1' -Name 'arc-sqlmi-01' ` -Type 'microsoft.azurearcdata/sqlmanagedinstances' -Props ([PSCustomObject]@{ licenseType = 'LicenseIncluded'; tier = 'GeneralPurpose'; vCores = '4' dataControllerId = '/hybrid/sub-00000001/dc/dc1'; provisioningState = 'Succeeded' k8sRaw = [PSCustomObject]@{ spec = [PSCustomObject]@{ replicas = 1 } } }) # Arc Data Controller $script:MockResources += New-MockHybridResource -Id '/hybrid/sub-00000001/dc/dc1' -Name 'arc-dc-01' ` -Type 'microsoft.azurearcdata/datacontrollers' -Props ([PSCustomObject]@{ k8sRaw = [PSCustomObject]@{ metadata = [PSCustomObject]@{ namespace = 'arc' } } infrastructure = 'onpremises'; basicLoginInformation = $null logsDashboardCredential = $null; onPremiseProperty = [PSCustomObject]@{ id = 'dc-id-001' } provisioningState = 'Succeeded' }) # Arc Sites $script:MockResources += New-MockHybridResource -Id '/hybrid/sub-00000001/sites/site1' -Name 'hq-site' ` -Type 'microsoft.hybridcompute/sites' -Props ([PSCustomObject]@{ provisioningState = 'Succeeded' }) # Azure Local Cluster $script:MockResources += New-MockHybridResource -Id '/hybrid/sub-00000001/hci/cluster1' -Name 'hci-cluster-01' ` -Type 'microsoft.azurestackhci/clusters' -Props ([PSCustomObject]@{ cloudId = 'cloud-id-001'; provisioningState = 'Succeeded'; status = 'Connected' nodeCount = 4; operatingSystem = 'Azure Local' reportedProperties = [PSCustomObject]@{ clusterVersion = '10.2405.0'; isMixedOSCluster = $false } }) # Azure Local VM $script:MockResources += New-MockHybridResource -Id '/hybrid/sub-00000001/hci/cluster1/vm/vm01' -Name 'hci-vm-01' ` -Type 'microsoft.azurestackhci/virtualmachineinstances' -Props ([PSCustomObject]@{ provisioningState = 'Succeeded'; vmId = 'vm-id-001' storageProfile = [PSCustomObject]@{ vmConfigStoragePathId = '/hci/storage1' } hardwareProfile = [PSCustomObject]@{ vmSize = 'Standard' } osProfile = [PSCustomObject]@{ computerName = 'hci-vm-01'; windowsConfiguration = $null; linuxConfiguration = $null } networkProfile = [PSCustomObject]@{ networkInterfaces = @() } securityProfile = [PSCustomObject]@{ enableTPM = $true; uefiSettings = [PSCustomObject]@{ secureBootEnabled = $true } } }) # Azure Local Logical Network $script:MockResources += New-MockHybridResource -Id '/hybrid/sub-00000001/hci/cluster1/net/net1' -Name 'hci-net-01' ` -Type 'microsoft.azurestackhci/logicalnetworks' -Props ([PSCustomObject]@{ provisioningState = 'Succeeded'; dhcpOptions = $null subnets = @(@{ properties = [PSCustomObject]@{ addressPrefix = '10.10.0.0/24'; ipAllocationMethod = 'Dynamic' }; name = 'default' }) vmSwitchName = 'ConvergedSwitch' }) # Azure Local Storage Container $script:MockResources += New-MockHybridResource -Id '/hybrid/sub-00000001/hci/cluster1/stor/stor1' -Name 'hci-stor-01' ` -Type 'microsoft.azurestackhci/storagecontainers' -Props ([PSCustomObject]@{ provisioningState = 'Succeeded'; storagePath = 'C:\ClusterStorage\Volume01' status = [PSCustomObject]@{ availableSizeGB = 500; totalSizeGB = 1000 } }) # Azure Local Gallery Image $script:MockResources += New-MockHybridResource -Id '/hybrid/sub-00000001/hci/cluster1/img/img1' -Name 'hci-img-01' ` -Type 'microsoft.azurestackhci/galleryimages' -Props ([PSCustomObject]@{ provisioningState = 'Succeeded'; osType = 'Windows'; hyperVGeneration = 'V2' identifier = [PSCustomObject]@{ publisher = 'MicrosoftWindowsServer'; offer = 'WindowsServer'; sku = '2022-Datacenter' } version = [PSCustomObject]@{ name = '20348.2340.240303' } }) # Azure Local Marketplace Gallery Image $script:MockResources += New-MockHybridResource -Id '/hybrid/sub-00000001/hci/cluster1/mktimg/mktimg1' -Name 'hci-mktimg-01' ` -Type 'microsoft.azurestackhci/marketplacegalleryimages' -Props ([PSCustomObject]@{ provisioningState = 'Succeeded'; osType = 'Windows'; hyperVGeneration = 'V2' identifier = [PSCustomObject]@{ publisher = 'MicrosoftWindowsServer'; offer = 'WindowsServer'; sku = '2022-Datacenter' } version = [PSCustomObject]@{ name = '20348.2340.240303' } }) # Mock Invoke-AzRestMethod for ArcServerOperationalData function Invoke-AzRestMethod { param([string]$Path, [string]$Method = 'GET') $mockResponse = @{ value = @(); properties = @{ startDateTime = '2025-06-01T00:00:00Z'; availablePatchCountByClassification = @{ critical = 0; security = 1 }; lastModifiedDateTime = '2025-06-01T12:00:00Z' } } [PSCustomObject]@{ Content = ($mockResponse | ConvertTo-Json -Depth 10); StatusCode = 200 } } } AfterAll { if (Test-Path $script:TempDir) { Remove-Item $script:TempDir -Recurse -Force } } # =================================================================== # TESTS # =================================================================== Describe 'Hybrid Module Files Exist' { It 'Hybrid module folder exists' { $script:HybridPath | Should -Exist } It '<Name> module file exists' -ForEach $HybridModules { Join-Path $script:HybridPath $File | Should -Exist } } Describe 'Hybrid Module Processing Phase — <Name>' -ForEach $HybridModules { BeforeAll { $script:ModFile = Join-Path $script:HybridPath $File $script:ResType = $Type } It 'Processing returns results when matching resources are present' { $matchedResources = $script:MockResources | Where-Object { $_.TYPE -eq $script:ResType } if ($matchedResources) { $content = Get-Content -Path $script:ModFile -Raw $sb = [ScriptBlock]::Create($content) $result = Invoke-Command -ScriptBlock $sb -ArgumentList $null, $null, $null, $script:MockResources, $null, 'Processing', $null, $null, 'Light20', $null $result | Should -Not -BeNullOrEmpty } else { Set-ItResult -Skipped -Because "No mock resource of type '$script:ResType'" } } It 'Processing does not throw when given an empty resource list' { $content = Get-Content -Path $script:ModFile -Raw $sb = [ScriptBlock]::Create($content) { Invoke-Command -ScriptBlock $sb -ArgumentList $null, $null, $null, @(), $null, 'Processing', $null, $null, 'Light20', $null } | Should -Not -Throw } } Describe 'Hybrid Module Reporting Phase — <Name>' -ForEach $HybridModules { BeforeAll { $script:ModFile = Join-Path $script:HybridPath $File $script:ResType = $Type $script:WsName = $Worksheet $script:XlsxFile = Join-Path $script:TempDir ("Hybrid_{0}_{1}.xlsx" -f $Name, [System.IO.Path]::GetRandomFileName()) $matchedResources = $script:MockResources | Where-Object { $_.TYPE -eq $script:ResType } if ($matchedResources) { $content = Get-Content -Path $script:ModFile -Raw $sb = [ScriptBlock]::Create($content) $script:ProcessedData = Invoke-Command -ScriptBlock $sb -ArgumentList $null, $null, $null, $script:MockResources, $null, 'Processing', $null, $null, 'Light20', $null } else { $script:ProcessedData = $null } } It 'Reporting phase does not throw' { if ($script:ProcessedData) { $content = Get-Content -Path $script:ModFile -Raw $sb = [ScriptBlock]::Create($content) { Invoke-Command -ScriptBlock $sb -ArgumentList $null, $null, $null, $null, $null, 'Reporting', $script:XlsxFile, $script:ProcessedData, 'Light20', $null } | Should -Not -Throw } else { Set-ItResult -Skipped -Because "No mock resource of type '$script:ResType'" } } It 'Excel file is created' { if ($script:ProcessedData) { $script:XlsxFile | Should -Exist } else { Set-ItResult -Skipped -Because "No mock resource of type '$script:ResType'" } } } Describe 'Arc SQL Server — Processing produces required fields' { It 'ArcSQLServers output includes SQL Version, Edition, Licensing fields' { $modFile = Join-Path $script:HybridPath 'ArcSQLServers.ps1' $content = Get-Content -Path $modFile -Raw $sb = [ScriptBlock]::Create($content) $result = Invoke-Command -ScriptBlock $sb -ArgumentList $null, $null, $null, $script:MockResources, $null, 'Processing', $null, $null, 'Light20', $null $result | Should -Not -BeNullOrEmpty $row = $result | Select-Object -First 1 $row.Keys | Should -Contain 'SQL Version' $row.Keys | Should -Contain 'Edition' $row.Keys | Should -Contain 'Licensing Type' $row.Keys | Should -Contain 'vCores' } } Describe 'Azure Local — Module Processing completeness' { It 'Azure Local Clusters output contains cluster fields' { $modFile = Join-Path $script:HybridPath 'Clusters.ps1' $content = Get-Content -Path $modFile -Raw $sb = [ScriptBlock]::Create($content) $result = Invoke-Command -ScriptBlock $sb -ArgumentList $null, $null, $null, $script:MockResources, $null, 'Processing', $null, $null, 'Light20', $null $result | Should -Not -BeNullOrEmpty } It 'Azure Local VMs output contains VM fields' { $modFile = Join-Path $script:HybridPath 'VirtualMachines.ps1' $content = Get-Content -Path $modFile -Raw $sb = [ScriptBlock]::Create($content) $result = Invoke-Command -ScriptBlock $sb -ArgumentList $null, $null, $null, $script:MockResources, $null, 'Processing', $null, $null, 'Light20', $null $result | Should -Not -BeNullOrEmpty } } |