tests/Networking.Module.Tests.ps1
|
#Requires -Modules @{ ModuleName = 'Pester'; ModuleVersion = '5.0.0' } #Requires -Modules ImportExcel <# .SYNOPSIS Pester tests for all Networking inventory modules. .DESCRIPTION Tests both Processing and Reporting phases for each Networking module using synthetic mock data. No live Azure authentication is required. .NOTES Author: AzureScout Contributors Version: 1.0.0 Created: 2026-02-24 Phase: 8.3.6, 19.5.13 — VPN / Networking Testing #> # =================================================================== # DISCOVERY-TIME # =================================================================== $NetworkingPath = Join-Path (Split-Path -Parent $PSScriptRoot) 'Modules' 'Public' 'InventoryModules' 'Networking' $NetworkingModules = @( @{ Name = 'VirtualNetwork'; File = 'VirtualNetwork.ps1'; Type = 'microsoft.network/virtualnetworks'; Worksheet = 'Virtual Networks' } @{ Name = 'vNETPeering'; File = 'vNETPeering.ps1'; Type = 'microsoft.network/virtualnetworks'; Worksheet = 'VNet Peerings' } @{ Name = 'VirtualNetworkGateways'; File = 'VirtualNetworkGateways.ps1';Type = 'microsoft.network/virtualnetworkgateways'; Worksheet = 'VPN Gateways' } @{ Name = 'Connections'; File = 'Connections.ps1'; Type = 'microsoft.network/connections'; Worksheet = 'VPN Connections' } @{ Name = 'LoadBalancer'; File = 'LoadBalancer.ps1'; Type = 'microsoft.network/loadbalancers'; Worksheet = 'Load Balancers' } @{ Name = 'ApplicationGateways'; File = 'ApplicationGateways.ps1'; Type = 'microsoft.network/applicationgateways'; Worksheet = 'Application Gateways' } @{ Name = 'NetworkSecurityGroup'; File = 'NetworkSecurityGroup.ps1'; Type = 'microsoft.network/networksecuritygroups'; Worksheet = 'Network Security Groups' } @{ Name = 'PublicIP'; File = 'PublicIP.ps1'; Type = 'microsoft.network/publicipaddresses'; Worksheet = 'Public IPs' } @{ Name = 'RouteTables'; File = 'RouteTables.ps1'; Type = 'microsoft.network/routetables'; Worksheet = 'Route Tables' } @{ Name = 'NetworkInterface'; File = 'NetworkInterface.ps1'; Type = 'microsoft.network/networkinterfaces'; Worksheet = 'Network Interfaces' } @{ Name = 'PrivateEndpoint'; File = 'PrivateEndpoint.ps1'; Type = 'microsoft.network/privateendpoints'; Worksheet = 'Private Endpoints' } @{ Name = 'PrivateDNS'; File = 'PrivateDNS.ps1'; Type = 'microsoft.network/privatednszones'; Worksheet = 'Private DNS Zones' } @{ Name = 'AzureFirewall'; File = 'AzureFirewall.ps1'; Type = 'microsoft.network/azurefirewalls'; Worksheet = 'Azure Firewall' } @{ Name = 'BastionHosts'; File = 'BastionHosts.ps1'; Type = 'microsoft.network/bastionhosts'; Worksheet = 'Bastion Hosts' } @{ Name = 'NATGateway'; File = 'NATGateway.ps1'; Type = 'microsoft.network/natgateways'; Worksheet = 'NAT Gateways' } @{ Name = 'TrafficManager'; File = 'TrafficManager.ps1'; Type = 'microsoft.network/trafficmanagerprofiles'; Worksheet = 'Traffic Manager' } @{ Name = 'VirtualWAN'; File = 'VirtualWAN.ps1'; Type = 'microsoft.network/virtualwans'; Worksheet = 'Virtual WANs' } @{ Name = 'ExpressRoute'; File = 'ExpressRoute.ps1'; Type = 'microsoft.network/expressroutecircuits'; Worksheet = 'ExpressRoute Circuits' } @{ Name = 'NetworkWatchers'; File = 'NetworkWatchers.ps1'; Type = 'microsoft.network/networkwatchers'; Worksheet = 'Network Watchers' } @{ Name = 'Frontdoor'; File = 'Frontdoor.ps1'; Type = 'microsoft.network/frontdoors'; Worksheet = 'Front Doors' } @{ Name = 'PublicDNS'; File = 'PublicDNS.ps1'; Type = 'microsoft.network/dnszones'; Worksheet = 'Public DNS Zones' } ) # =================================================================== # EXECUTION-TIME SETUP # =================================================================== BeforeAll { $script:ModuleRoot = Split-Path -Parent $PSScriptRoot # Collectors are executed here as bare scriptblocks, NOT through the imported module, so any # private helper they call has to be dot-sourced in explicitly. Get-AZSCSafeProperty is the # StrictMode-safe dotted-path read that converted collectors use instead of a raw `$data.a.b` # chain (which throws when an intermediate segment is genuinely ABSENT rather than $null), and # Get-AZSCCollectedValue is its member-ENUMERATION counterpart for a read over a collection # that may be empty. Both are needed here from AB#5671 onwards. . (Join-Path $script:ModuleRoot 'Modules' 'Private' 'Main' 'Get-AZSCSafeProperty.ps1') . (Join-Path $script:ModuleRoot 'Modules' 'Private' 'Main' 'Get-AZTICollectedValue.ps1') # Get-AZSCIdSegment (AB#5671) guards the FIXED .split('/')[8] index ~30 collectors use to pull # a name out of a related resource id -- an out-of-range index THROWS under StrictMode, where # without it the same expression quietly returned $null. . (Join-Path $script:ModuleRoot 'Modules' 'Private' 'Main' 'Get-AZSCIdSegment.ps1') $script:NetworkingPath = Join-Path $script:ModuleRoot 'Modules' 'Public' 'InventoryModules' 'Networking' $script:TempDir = Join-Path $env:TEMP 'AZSC_NetworkingTests' if (Test-Path $script:TempDir) { Remove-Item $script:TempDir -Recurse -Force } New-Item -ItemType Directory -Path $script:TempDir -Force | Out-Null function New-MockNetResource { param([string]$Id, [string]$Name, [string]$Type, [string]$Kind = '', [string]$Location = 'eastus', [string]$RG = 'rg-net', [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 = @() # Virtual Network $script:MockResources += New-MockNetResource -Id '/net/vnet1' -Name 'vnet-prod' ` -Type 'microsoft.network/virtualnetworks' -Props ([PSCustomObject]@{ addressSpace = [PSCustomObject]@{ addressPrefixes = @('10.0.0.0/16') } subnets = @( [PSCustomObject]@{ name = 'default'; properties = [PSCustomObject]@{ addressPrefix = '10.0.0.0/24'; provisioningState = 'Succeeded' } } ) virtualNetworkPeerings = @() enableDdosProtection = $false; provisioningState = 'Succeeded' dhcpOptions = [PSCustomObject]@{ dnsServers = @('168.63.129.16') } }) # VNet Peering (piggybacked on VNet resource) $vnetWithPeering = New-MockNetResource -Id '/net/vnet2' -Name 'vnet-spoke' ` -Type 'microsoft.network/virtualnetworks' -Props ([PSCustomObject]@{ addressSpace = [PSCustomObject]@{ addressPrefixes = @('10.1.0.0/16') } subnets = @() virtualNetworkPeerings = @( [PSCustomObject]@{ name = 'peer-to-hub' properties = [PSCustomObject]@{ remoteVirtualNetwork = [PSCustomObject]@{ id = '/subscriptions/sub-00000001/resourceGroups/rg-net/providers/Microsoft.Network/virtualNetworks/vnet-hub' } remoteAddressSpace = [PSCustomObject]@{ addressPrefixes = @('10.0.0.0/16') } peeringState = 'Connected'; allowVirtualNetworkAccess = $true allowForwardedTraffic = $true; allowGatewayTransit = $false; useRemoteGateways = $true provisioningState = 'Succeeded' } } ) provisioningState = 'Succeeded'; enableDdosProtection = $false }) $script:MockResources += $vnetWithPeering # VPN Gateway $script:MockResources += New-MockNetResource -Id '/net/vpngw1' -Name 'vpngw-prod' ` -Type 'microsoft.network/virtualnetworkgateways' -Props ([PSCustomObject]@{ gatewayType = 'Vpn'; vpnType = 'RouteBased'; sku = [PSCustomObject]@{ name = 'VpnGw1'; tier = 'VpnGw1' } provisioningState = 'Succeeded'; enableBgp = $false; activeActive = $false; enablePrivateIpAddress = $false ipConfigurations = @([PSCustomObject]@{name='ipconf1';properties=[PSCustomObject]@{ publicIPAddress=[PSCustomObject]@{id='/subscriptions/sub-00000001/resourceGroups/rg-net/providers/Microsoft.Network/publicIPAddresses/pip-vpngw'} subnet=[PSCustomObject]@{id='/subscriptions/sub-00000001/resourceGroups/rg-net/providers/Microsoft.Network/virtualNetworks/vnet-prod/subnets/GatewaySubnet'} }}) vpnClientConfiguration = [PSCustomObject]@{ vpnClientAddressPool = [PSCustomObject]@{ addressPrefixes = @('172.16.0.0/24') } vpnClientProtocols = @('IkeV2','SSTP'); vpnAuthenticationTypes = @('Certificate') vpnClientRootCertificates = @(@{name='root1'}); vpnClientRevokedCertificates = @() radiusServers = $null; aadTenant = $null } bgpSettings = [PSCustomObject]@{ asn = 65515; bgpPeeringAddress = '10.0.0.30'; peerWeight = 0 } natRules = @(); customDnsServers = @('10.0.0.10'); vpnGatewayGeneration = 'Generation1' }) # VPN Connection $script:MockResources += New-MockNetResource -Id '/net/conn1' -Name 'conn-s2s-onprem' ` -Type 'microsoft.network/connections' -Props ([PSCustomObject]@{ connectionType = 'IPsec'; connectionStatus = 'Connected' virtualNetworkGateway1 = [PSCustomObject]@{ id = '/net/vpngw1' } localNetworkGateway2 = [PSCustomObject]@{ id = '/net/lng1' } sharedKeyPresent = $true ingressBytesTransferred = 1048576; egressBytesTransferred = 2097152 ipsecPolicies = @() trafficSelectorPolicies = @() dpdTimeoutSeconds = 45 usePolicyBasedTrafficSelectors = $false provisioningState = 'Succeeded' }) # Load Balancer $script:MockResources += New-MockNetResource -Id '/net/lb1' -Name 'lb-web' ` -Type 'microsoft.network/loadbalancers' -Props ([PSCustomObject]@{ sku = [PSCustomObject]@{ name = 'Standard'; tier = 'Regional' } frontendIPConfigurations = @(@{ name = 'frontend1'; properties = [PSCustomObject]@{ privateIPAddress = $null; publicIPAddress = [PSCustomObject]@{id='/pip/pip2'} } }) backendAddressPools = @(@{ name = 'pool1' }) loadBalancingRules = @(@{ name = 'rule-http'; properties = [PSCustomObject]@{ protocol = 'Tcp'; frontendPort = 80; backendPort = 80 } }) probes = @(@{ name = 'probe-http'; properties = [PSCustomObject]@{ protocol = 'Http'; port = 80 } }) provisioningState = 'Succeeded' }) # Application Gateway $script:MockResources += New-MockNetResource -Id '/net/appgw1' -Name 'appgw-prod' ` -Type 'microsoft.network/applicationgateways' -Props ([PSCustomObject]@{ sku = [PSCustomObject]@{ name = 'Standard_v2'; tier = 'Standard_v2'; capacity = 2 } operationalState = 'Running'; provisioningState = 'Succeeded' gatewayIPConfigurations = @(@{name='gwip1';properties=[PSCustomObject]@{subnet=[PSCustomObject]@{id='/vnet/vnet1/subnets/gwsubnet'}}}) backendAddressPools = @(@{ name = 'pool1'; properties = [PSCustomObject]@{backendAddresses=@()} }) httpListeners = @(@{ name = 'listener-http'; properties = [PSCustomObject]@{ protocol='Http'; frontendPort=[PSCustomObject]@{id='/port/80'} } }) requestRoutingRules = @() }) # NSG $script:MockResources += New-MockNetResource -Id '/net/nsg1' -Name 'nsg-web' ` -Type 'microsoft.network/networksecuritygroups' -Props ([PSCustomObject]@{ securityRules = @( [PSCustomObject]@{ name = 'allow-http'; properties = [PSCustomObject]@{ priority = 100; direction = 'Inbound'; access = 'Allow'; protocol = 'Tcp'; sourcePortRange = '*'; destinationPortRange = '80'; sourceAddressPrefix = '*'; destinationAddressPrefix = '*'; provisioningState = 'Succeeded' } } ) defaultSecurityRules = @() networkInterfaces = @() subnets = @([PSCustomObject]@{id='/vnet/vnet1/subnets/default'}) provisioningState = 'Succeeded' }) # Public IP $script:MockResources += New-MockNetResource -Id '/net/pip1' -Name 'pip-vpngw' ` -Type 'microsoft.network/publicipaddresses' -Props ([PSCustomObject]@{ sku = [PSCustomObject]@{ name = 'Standard'; tier = 'Regional' } publicIPAllocationMethod = 'Static'; ipAddress = '20.1.2.3' dnsSettings = [PSCustomObject]@{ domainNameLabel = 'vpngw-pip'; fqdn = 'vpngw-pip.eastus.cloudapp.azure.com' } provisioningState = 'Succeeded'; ipConfiguration = [PSCustomObject]@{id='/net/vpngw1/ipcfg1'} }) # Route Table $script:MockResources += New-MockNetResource -Id '/net/rt1' -Name 'rt-custom' ` -Type 'microsoft.network/routetables' -Props ([PSCustomObject]@{ routes = @( [PSCustomObject]@{ name = 'route-fw'; properties = [PSCustomObject]@{ addressPrefix = '0.0.0.0/0'; nextHopType = 'VirtualAppliance'; nextHopIpAddress = '10.0.0.4' } } ) disableBgpRoutePropagation = $true; provisioningState = 'Succeeded' subnets = @() }) # Network Interface $script:MockResources += New-MockNetResource -Id '/net/nic1' -Name 'nic-vm01' ` -Type 'microsoft.network/networkinterfaces' -Props ([PSCustomObject]@{ ipConfigurations = @(@{ name='ipconf1'; properties = [PSCustomObject]@{ subnet = [PSCustomObject]@{ id = '/vnet/vnet1/subnets/default' }; privateIPAddress = '10.0.0.5'; publicIPAddress = $null } }) enableAcceleratedNetworking = $true; enableIPForwarding = $false; networkSecurityGroup = [PSCustomObject]@{id='/net/nsg1'} virtualMachine = [PSCustomObject]@{id='/vm/vm01'}; provisioningState = 'Succeeded' }) # Private Endpoint $script:MockResources += New-MockNetResource -Id '/net/pe1' -Name 'pe-storage' ` -Type 'microsoft.network/privateendpoints' -Props ([PSCustomObject]@{ subnet = [PSCustomObject]@{ id = '/vnet/vnet1/subnets/default' } privateLinkServiceConnections = @([PSCustomObject]@{ name = 'plsc-1'; properties = [PSCustomObject]@{ privateLinkServiceId = '/sa/sa1'; groupIds = @('blob'); privateLinkServiceConnectionState = [PSCustomObject]@{status='Approved';description=''} } }) networkInterfaces = @([PSCustomObject]@{id='/nic/nic-pe1'}) provisioningState = 'Succeeded' }) # Private DNS Zone $script:MockResources += New-MockNetResource -Id '/net/pdns1' -Name 'privatelink.blob.core.windows.net' ` -Type 'microsoft.network/privatednszones' -Props ([PSCustomObject]@{ numberOfRecordSets = 2; maxNumberOfRecordSets = 25000 numberOfVirtualNetworkLinks = 1; maxNumberOfVirtualNetworkLinks = 1000 provisioningState = 'Succeeded' }) # Azure Firewall $script:MockResources += New-MockNetResource -Id '/net/fw1' -Name 'fw-hub' ` -Type 'microsoft.network/azurefirewalls' -Props ([PSCustomObject]@{ sku = [PSCustomObject]@{ name = 'AZFW_VNet'; tier = 'Premium' } threatIntelMode = 'Alert'; provisioningState = 'Succeeded' ipConfigurations = @(@{ name='ipconf1'; properties = [PSCustomObject]@{ privateIPAddress = '10.0.1.4'; subnet = [PSCustomObject]@{id='/vnet/hub/subnets/AzureFirewallSubnet'} } }) firewallPolicy = [PSCustomObject]@{ id = '/net/fwpolicy1' } additionalProperties = @{} }) # Bastion $script:MockResources += New-MockNetResource -Id '/net/bastion1' -Name 'bastion-hub' ` -Type 'microsoft.network/bastionhosts' -Props ([PSCustomObject]@{ ipConfigurations = @(@{ name='ipconf1'; properties = [PSCustomObject]@{ subnet = [PSCustomObject]@{id='/vnet/hub/subnets/AzureBastionSubnet'}; publicIPAddress = [PSCustomObject]@{id='/pip/pip-bas'} } }) sku = [PSCustomObject]@{ name = 'Standard' }; dnsName = 'bst-001.bastion.azure.com' scaleUnits = 2; disableCopyPaste = $false; enableFileCopy = $true; enableIpConnect = $true enableShareableLink = $false; enableTunneling = $true; provisioningState = 'Succeeded' }) # NAT Gateway $script:MockResources += New-MockNetResource -Id '/net/nat1' -Name 'nat-prod' ` -Type 'microsoft.network/natgateways' -Props ([PSCustomObject]@{ sku = [PSCustomObject]@{ name = 'Standard' }; idleTimeoutInMinutes = 4 publicIpAddresses = @([PSCustomObject]@{id='/pip/pip-nat'}); publicIpPrefixes = @() subnets = @([PSCustomObject]@{id='/vnet/vnet1/subnets/default'}) provisioningState = 'Succeeded' }) # Traffic Manager $script:MockResources += New-MockNetResource -Id '/net/tm1' -Name 'tm-global' ` -Type 'microsoft.network/trafficmanagerprofiles' -Props ([PSCustomObject]@{ profileStatus = 'Enabled'; trafficRoutingMethod = 'Performance' dnsConfig = [PSCustomObject]@{ relativeName = 'tm-global'; fqdn = 'tm-global.trafficmanager.net'; ttl = 60 } monitorConfig = [PSCustomObject]@{ protocol = 'HTTPS'; port = 443; path = '/health'; intervalInSeconds = 30 } endpoints = @(@{ name='ep-eastus'; properties = [PSCustomObject]@{ endpointStatus='Enabled'; weight=50; priority=$null } }) provisioningState = 'Succeeded' }) # Virtual WAN $script:MockResources += New-MockNetResource -Id '/net/vwan1' -Name 'vwan-prod' ` -Type 'microsoft.network/virtualwans' -Props ([PSCustomObject]@{ type = 'Standard'; allowVnetToVnetTraffic = $true; allowBranchToBranchTraffic = $true disableVpnEncryption = $false virtualHubs = @([PSCustomObject]@{id='/net/vhub1'}) vpnSites = @([PSCustomObject]@{id='/net/vpnsite1'}) provisioningState = 'Succeeded' }) # Virtual Hub (needed by VirtualWAN module) $script:MockResources += New-MockNetResource -Id '/net/vhub1' -Name 'vhub-eastus' ` -Type 'microsoft.network/virtualhubs' -Props ([PSCustomObject]@{ addressPrefix = '10.10.0.0/24'; preferredRoutingGateway = 'ExpressRoute' virtualRouterAsn = 65515; virtualRouterIps = @('10.10.0.1','10.10.0.2') provisioningState = 'Succeeded' }) # VPN Site (needed by VirtualWAN module) $script:MockResources += New-MockNetResource -Id '/net/vpnsite1' -Name 'vpnsite-branch1' ` -Type 'microsoft.network/vpnsites' -Props ([PSCustomObject]@{ deviceProperties = [PSCustomObject]@{ deviceVendor = 'Cisco'; deviceModel = 'ISR 4451'; linkSpeedInMbps = 500 } vpnSiteLinks = @([PSCustomObject]@{ properties = [PSCustomObject]@{ ipAddress = '203.0.113.1' linkProperties = [PSCustomObject]@{ linkProviderName = 'Contoso ISP'; linkSpeedInMbps = 500 } }}) addressSpace = [PSCustomObject]@{ addressPrefixes = @('192.168.1.0/24') } provisioningState = 'Succeeded' }) # ExpressRoute $script:MockResources += New-MockNetResource -Id '/net/er1' -Name 'er-prod' ` -Type 'microsoft.network/expressroutecircuits' -Props ([PSCustomObject]@{ sku = [PSCustomObject]@{ name = 'Standard_MeteredData'; tier = 'Standard'; family = 'MeteredData' } serviceProviderProperties = [PSCustomObject]@{ serviceProviderName = 'Equinix'; peeringLocation = 'Silicon Valley'; bandwidthInMbps = 1000 } circuitProvisioningState = 'Enabled'; serviceProviderProvisioningState = 'Provisioned' provisioningState = 'Succeeded' }) # Network Watcher $script:MockResources += New-MockNetResource -Id '/net/nw1' -Name 'nw-eastus' ` -Type 'microsoft.network/networkwatchers' -Props ([PSCustomObject]@{ provisioningState = 'Succeeded' }) # Front Door (classic) $script:MockResources += New-MockNetResource -Id '/net/fd1' -Name 'fd-prod' ` -Type 'microsoft.network/frontdoors' -Props ([PSCustomObject]@{ frontendEndpoints = @(@{name='fe-prod';properties=[PSCustomObject]@{hostName='fd-prod.azurefd.net';sessionAffinityEnabledState='Disabled'}}) routingRules = @(@{name='rr-default';properties=[PSCustomObject]@{enabledState='Enabled';patternsToMatch=@('/*')}}) backendPools = @(@{name='pool1'}) provisioningState = 'Succeeded'; resourceState = 'Enabled' }) # Public DNS Zone $script:MockResources += New-MockNetResource -Id '/net/dns1' -Name 'contoso.com' ` -Type 'microsoft.network/dnszones' -Props ([PSCustomObject]@{ zoneType = 'Public'; numberOfRecordSets = 15; maxNumberofRecordSets = 10000 nameServers = @('ns1-01.azure-dns.com','ns2-01.azure-dns.net') }) } AfterAll { if (Test-Path $script:TempDir) { Remove-Item $script:TempDir -Recurse -Force } } # =================================================================== # TESTS # =================================================================== Describe 'Networking Module Files Exist' { It 'Networking module folder exists' { $script:NetworkingPath | Should -Exist } It '<Name> module file exists' -ForEach $NetworkingModules { Join-Path $script:NetworkingPath $File | Should -Exist } } Describe 'Networking Module Processing Phase — <Name>' -ForEach $NetworkingModules { BeforeAll { $script:ModFile = Join-Path $script:NetworkingPath $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 'Networking Module Reporting Phase — <Name>' -ForEach $NetworkingModules { BeforeAll { $script:ModFile = Join-Path $script:NetworkingPath $File $script:ResType = $Type $script:WsName = $Worksheet $script:XlsxFile = Join-Path $script:TempDir ("Net_{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 'VPN Gateway — Enhanced P2S and NAT fields present' { It 'VirtualNetworkGateways processing produces P2S config fields' { $modFile = Join-Path $script:NetworkingPath 'VirtualNetworkGateways.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 # Validate field presence — exact key names defined in the module $row.Keys | Should -Contain 'Name' $row.Keys | Should -Contain 'Subscription' $row.Keys | Should -Contain 'Resource Group' } } Describe 'VPN Connections — Enhanced IPsec fields present' { It 'Connections processing produces Shared Key Present and Ingress/Egress fields' { $modFile = Join-Path $script:NetworkingPath 'Connections.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 'Type' $row.Keys | Should -Contain 'Status' } } |