Tests/AksArc.DeploymentReadiness.Tests.ps1
|
#Requires -Modules Pester Describe 'AksArc.DeploymentReadiness Module' { BeforeAll { $modulePath = Join-Path $PSScriptRoot '..' $manifestPath = Join-Path $modulePath 'AksArc.DeploymentReadiness.psd1' Import-Module $manifestPath -Force -ErrorAction Stop } Context 'Module Structure' { It 'Module manifest is valid' { $manifest = Test-ModuleManifest -Path (Join-Path $PSScriptRoot '..' 'AksArc.DeploymentReadiness.psd1') $manifest | Should -Not -BeNullOrEmpty $manifest.Version | Should -Be '0.5.0' } It 'Exports exactly 10 functions' { $commands = Get-Command -Module AksArc.DeploymentReadiness $commands.Count | Should -Be 10 } It 'Exports expected function names' { $expected = @( 'Connect-AksArcServicePrincipal' 'Export-AksArcFirewallRules' 'Get-AksArcEndpointReference' 'Get-AksArcFleetProgress' 'Initialize-AksArcValidation' 'New-AksArcDeploymentPlan' 'New-AksArcReadinessReport' 'Test-AksArcDeploymentReadiness' 'Test-AksArcFleetReadiness' 'Test-AksArcNetworkConnectivity' ) $actual = (Get-Command -Module AksArc.DeploymentReadiness).Name | Sort-Object $actual | Should -Be $expected } } Context 'Endpoint Data' { It 'Loads endpoint data without errors' { $endpoints = Get-AksArcEndpointReference $endpoints | Should -Not -BeNullOrEmpty } It 'Contains 86 endpoints' { $endpoints = Get-AksArcEndpointReference $endpoints.Count | Should -Be 86 } It 'Each endpoint has required properties' { $endpoints = Get-AksArcEndpointReference foreach ($ep in $endpoints) { $ep.id | Should -Not -BeNullOrEmpty $ep.url | Should -Not -BeNullOrEmpty $ep.port | Should -BeGreaterThan 0 $ep.protocol | Should -Not -BeNullOrEmpty $ep.component | Should -Not -BeNullOrEmpty } } It 'Filters by component' { $aks = Get-AksArcEndpointReference -Component 'Azure Local AKS infra' $aks | Should -Not -BeNullOrEmpty $aks | ForEach-Object { $_.component | Should -Be 'Azure Local AKS infra' } } It 'Component filter is case-insensitive' { $aks = Get-AksArcEndpointReference -Component 'azure local aks infra' $aks | Should -Not -BeNullOrEmpty } It 'Contains expected upstream component names' { $endpoints = Get-AksArcEndpointReference $components = $endpoints | Select-Object -ExpandProperty component -Unique $components | Should -Contain 'Azure Local AKS infra' $components | Should -Contain 'Azure Local ARB infra' $components | Should -Contain 'Azure Local Arc agent' $components | Should -Contain 'Azure Local authentication' $components | Should -Contain 'Azure Local deployment' } It 'Contains customer-specific endpoints' { $endpoints = Get-AksArcEndpointReference $customerSpecific = @($endpoints | Where-Object { $_.customerSpecific -eq $true }) $customerSpecific.Count | Should -Be 2 } It 'Contains region-specific endpoints' { $endpoints = Get-AksArcEndpointReference $regionSpecific = @($endpoints | Where-Object { $_.regionSpecific -eq $true }) $regionSpecific.Count | Should -BeGreaterThan 0 } It 'Filters by ArcGatewaySupported' { $notCovered = Get-AksArcEndpointReference -ArcGatewaySupported $false $notCovered | ForEach-Object { $_.arcGatewaySupported | Should -Be $false } } It 'Filters by RequiredFor' { $deploy = Get-AksArcEndpointReference -RequiredFor 'deployment' $deploy | Should -Not -BeNullOrEmpty $deploy | ForEach-Object { ($_.requiredFor -eq 'deployment' -or $_.requiredFor -eq 'both') | Should -Be $true } } } Context 'Export-AksArcFirewallRules' { It 'Exports CSV' { $path = Join-Path $TestDrive 'rules.csv' Export-AksArcFirewallRules -Path $path Test-Path $path | Should -Be $true $csv = Import-Csv $path $csv.Count | Should -BeGreaterThan 0 } It 'Exports JSON' { $path = Join-Path $TestDrive 'rules.json' Export-AksArcFirewallRules -Path $path Test-Path $path | Should -Be $true $json = Get-Content $path -Raw | ConvertFrom-Json $json.endpoints | Should -Not -BeNullOrEmpty } It 'Exports Markdown' { $path = Join-Path $TestDrive 'rules.md' Export-AksArcFirewallRules -Path $path Test-Path $path | Should -Be $true $content = Get-Content $path -Raw $content | Should -Match 'AKS Arc Firewall Requirements' $content | Should -Match '\|.*\|.*\|' } It 'Rejects unsupported format' { { Export-AksArcFirewallRules -Path (Join-Path $TestDrive 'rules.txt') } | Should -Throw '*Unsupported*' } } Context 'Test-AksArcNetworkConnectivity' { It 'Has Component parameter' { $cmd = Get-Command Test-AksArcNetworkConnectivity $cmd.Parameters.ContainsKey('Component') | Should -Be $true } It 'Has Region parameter' { $cmd = Get-Command Test-AksArcNetworkConnectivity $cmd.Parameters.ContainsKey('Region') | Should -Be $true } It 'Has ExportPath parameter' { $cmd = Get-Command Test-AksArcNetworkConnectivity $cmd.Parameters.ContainsKey('ExportPath') | Should -Be $true } It 'Has PassThru parameter' { $cmd = Get-Command Test-AksArcNetworkConnectivity $cmd.Parameters.ContainsKey('PassThru') | Should -Be $true } } Context 'Test-AksArcDeploymentReadiness' { It 'Supports ShouldProcess' { $cmd = Get-Command Test-AksArcDeploymentReadiness $cmd.Parameters.ContainsKey('WhatIf') | Should -Be $true } } Context 'Test-AksArcFleetReadiness' { It 'Has ScopeByTag parameter' { $cmd = Get-Command Test-AksArcFleetReadiness $cmd.Parameters.ContainsKey('ScopeByTag') | Should -Be $true } It 'Has BatchSize parameter with default 50' { $cmd = Get-Command Test-AksArcFleetReadiness $cmd.Parameters.ContainsKey('BatchSize') | Should -Be $true } } Context 'Initialize-AksArcValidation' { It 'Has ManagementNetwork parameter' { $cmd = Get-Command Initialize-AksArcValidation $cmd.Parameters.ContainsKey('ManagementNetwork') | Should -Be $true } It 'Has AksNetwork parameter' { $cmd = Get-Command Initialize-AksArcValidation $cmd.Parameters.ContainsKey('AksNetwork') | Should -Be $true } } Context 'Connect-AksArcServicePrincipal' { It 'Has UseManagedIdentity parameter' { $cmd = Get-Command Connect-AksArcServicePrincipal $cmd.Parameters.ContainsKey('UseManagedIdentity') | Should -Be $true } It 'Has ServicePrincipalId parameter' { $cmd = Get-Command Connect-AksArcServicePrincipal $cmd.Parameters.ContainsKey('ServicePrincipalId') | Should -Be $true } } Context 'New-AksArcDeploymentPlan' { It 'Has PlannedClusters parameter' { $cmd = Get-Command New-AksArcDeploymentPlan $cmd.Parameters.ContainsKey('PlannedClusters') | Should -Be $true } It 'Has ControlPlaneNodes parameter' { $cmd = Get-Command New-AksArcDeploymentPlan $cmd.Parameters.ContainsKey('ControlPlaneNodes') | Should -Be $true } It 'Has WorkerNodes parameter' { $cmd = Get-Command New-AksArcDeploymentPlan $cmd.Parameters.ContainsKey('WorkerNodes') | Should -Be $true } It 'Has EnableAutoScale parameter' { $cmd = Get-Command New-AksArcDeploymentPlan $cmd.Parameters.ContainsKey('EnableAutoScale') | Should -Be $true } It 'Has AksNetworkName parameter' { $cmd = Get-Command New-AksArcDeploymentPlan $cmd.Parameters.ContainsKey('AksNetworkName') | Should -Be $true } It 'Calculates IP requirements for single cluster defaults' { $plan = New-AksArcDeploymentPlan -PlannedClusters 1 -ControlPlaneNodes 3 -WorkerNodes 3 -LoadBalancerIPs 3 $plan.PlannedClusters | Should -Be 1 $plan.ControlPlaneNodes | Should -Be 3 $plan.WorkerNodes | Should -Be 3 $plan.NodesPerCluster | Should -Be 6 $plan.TotalNodeVMs | Should -Be 6 $plan.TotalUpgradeIPs | Should -Be 1 $plan.TotalControlPlaneIPs | Should -Be 1 $plan.TotalRequiredIPs | Should -Be 8 # 6 nodes + 1 upgrade + 1 CP $plan.TotalWithLoadBalancer | Should -Be 11 # 8 + 3 LB } It 'Calculates IP requirements for multi-cluster deployment' { $plan = New-AksArcDeploymentPlan -PlannedClusters 3 -ControlPlaneNodes 3 -WorkerNodes 5 -LoadBalancerIPs 5 $plan.TotalNodeVMs | Should -Be 24 # (3+5) * 3 $plan.TotalUpgradeIPs | Should -Be 3 $plan.TotalControlPlaneIPs | Should -Be 3 $plan.TotalRequiredIPs | Should -Be 30 # 24 + 3 + 3 $plan.TotalWithLoadBalancer | Should -Be 35 # 30 + 5 } It 'Calculates autoscale headroom correctly' { $plan = New-AksArcDeploymentPlan -PlannedClusters 2 -ControlPlaneNodes 3 -WorkerNodes 3 -LoadBalancerIPs 0 -EnableAutoScale -MaxAutoScaleNodes 10 $plan.AutoScaleHeadroom | Should -Be 14 # (10-3) * 2 $plan.TotalRequiredIPs | Should -Be 30 # 12 node VMs + 2 upgrade + 2 CP + 14 autoscale } It 'Autoscale headroom is zero when MaxAutoScaleNodes <= WorkerNodes' { $plan = New-AksArcDeploymentPlan -PlannedClusters 1 -ControlPlaneNodes 1 -WorkerNodes 5 -LoadBalancerIPs 0 -EnableAutoScale -MaxAutoScaleNodes 3 $plan.AutoScaleHeadroom | Should -Be 0 } It 'Returns a plan object with Timestamp' { $plan = New-AksArcDeploymentPlan -PlannedClusters 1 -ControlPlaneNodes 1 -WorkerNodes 1 -LoadBalancerIPs 0 $plan.Timestamp | Should -Not -BeNullOrEmpty } } Context 'Test-AksArcDeploymentReadiness DeploymentPlan parameter' { It 'Has DeploymentPlan parameter' { $cmd = Get-Command Test-AksArcDeploymentReadiness $cmd.Parameters.ContainsKey('DeploymentPlan') | Should -Be $true } } Context 'Initialize-AksArcValidation v0.4.0 parameters' { It 'Has ManagementIPs parameter' { $cmd = Get-Command Initialize-AksArcValidation $cmd.Parameters.ContainsKey('ManagementIPs') | Should -Be $true } It 'Has AksSubnetTestIP parameter' { $cmd = Get-Command Initialize-AksArcValidation $cmd.Parameters.ContainsKey('AksSubnetTestIP') | Should -Be $true } It 'Has ClusterIP parameter' { $cmd = Get-Command Initialize-AksArcValidation $cmd.Parameters.ContainsKey('ClusterIP') | Should -Be $true } It 'ManagementIPs accepts string array' { $cmd = Get-Command Initialize-AksArcValidation $cmd.Parameters['ManagementIPs'].ParameterType.Name | Should -Be 'String[]' } } Context 'Endpoint Data - Cross-Subnet Port Directions' { It 'Cross-subnet ports have testDirection property' { $dataPath = Join-Path $PSScriptRoot '..' 'data' 'endpoints.json' $data = Get-Content $dataPath -Raw | ConvertFrom-Json foreach ($port in $data.crossSubnetPorts) { $port.testDirection | Should -Not -BeNullOrEmpty } } It 'testDirection values are valid' { $dataPath = Join-Path $PSScriptRoot '..' 'data' 'endpoints.json' $data = Get-Content $dataPath -Raw | ConvertFrom-Json $validDirections = @('toAks', 'toCluster') foreach ($port in $data.crossSubnetPorts) { $port.testDirection | Should -BeIn $validDirections } } It 'Ports 22, 443, 6443, 9440 test toAks direction' { $dataPath = Join-Path $PSScriptRoot '..' 'data' 'endpoints.json' $data = Get-Content $dataPath -Raw | ConvertFrom-Json $toAksPorts = @(22, 443, 6443, 9440) foreach ($p in $toAksPorts) { $entry = $data.crossSubnetPorts | Where-Object { $_.port -eq $p } $entry.testDirection | Should -Be 'toAks' } } It 'Ports 55000, 65000 test toCluster direction' { $dataPath = Join-Path $PSScriptRoot '..' 'data' 'endpoints.json' $data = Get-Content $dataPath -Raw | ConvertFrom-Json $toClusterPorts = @(55000, 65000) foreach ($p in $toClusterPorts) { $entry = $data.crossSubnetPorts | Where-Object { $_.port -eq $p } $entry.testDirection | Should -Be 'toCluster' } } It 'Port 40343 is marked conditional' { $dataPath = Join-Path $PSScriptRoot '..' 'data' 'endpoints.json' $data = Get-Content $dataPath -Raw | ConvertFrom-Json $arcGw = $data.crossSubnetPorts | Where-Object { $_.port -eq 40343 } $arcGw.conditional | Should -Be $true } } Context 'Invoke-AzRestCall enhanced error handling' { It 'Invoke-AzRestCall is an internal function (not exported)' { $exported = (Get-Command -Module AksArc.DeploymentReadiness).Name $exported | Should -Not -Contain 'Invoke-AzRestCall' } } Context 'Gate 7 and Gate 8 integration (parameter-level)' { It 'Test-AksArcDeploymentReadiness has Context parameter' { $cmd = Get-Command Test-AksArcDeploymentReadiness $cmd.Parameters.ContainsKey('Context') | Should -Be $true } It 'Test-AksArcDeploymentReadiness has SkipNetworkTests parameter' { $cmd = Get-Command Test-AksArcDeploymentReadiness $cmd.Parameters.ContainsKey('SkipNetworkTests') | Should -Be $true } It 'Test-AksArcDeploymentReadiness has Region parameter' { $cmd = Get-Command Test-AksArcDeploymentReadiness $cmd.Parameters.ContainsKey('Region') | Should -Be $true } } Context 'Test-AksArcFleetReadiness v0.5.0 enhancements' { It 'Has DeploymentPlan parameter' { $cmd = Get-Command Test-AksArcFleetReadiness $cmd.Parameters.ContainsKey('DeploymentPlan') | Should -Be $true } It 'Has MinReadyPercent parameter' { $cmd = Get-Command Test-AksArcFleetReadiness $cmd.Parameters.ContainsKey('MinReadyPercent') | Should -Be $true } It 'Has MaxWarningPercent parameter' { $cmd = Get-Command Test-AksArcFleetReadiness $cmd.Parameters.ContainsKey('MaxWarningPercent') | Should -Be $true } It 'Has ThrottleLimit parameter' { $cmd = Get-Command Test-AksArcFleetReadiness $cmd.Parameters.ContainsKey('ThrottleLimit') | Should -Be $true } It 'MinReadyPercent has ValidateRange 0-100' { $cmd = Get-Command Test-AksArcFleetReadiness $attrs = $cmd.Parameters['MinReadyPercent'].Attributes | Where-Object { $_ -is [System.Management.Automation.ValidateRangeAttribute] } $attrs | Should -Not -BeNullOrEmpty } It 'ThrottleLimit has ValidateRange 1-20' { $cmd = Get-Command Test-AksArcFleetReadiness $attrs = $cmd.Parameters['ThrottleLimit'].Attributes | Where-Object { $_ -is [System.Management.Automation.ValidateRangeAttribute] } $attrs | Should -Not -BeNullOrEmpty } } Context 'New-AksArcReadinessReport' { It 'Function exists and is exported' { $cmd = Get-Command New-AksArcReadinessReport -ErrorAction SilentlyContinue $cmd | Should -Not -BeNullOrEmpty } It 'Has OutputPath as mandatory parameter' { $cmd = Get-Command New-AksArcReadinessReport $cmd.Parameters.ContainsKey('OutputPath') | Should -Be $true $mandatory = $cmd.Parameters['OutputPath'].Attributes | Where-Object { $_ -is [System.Management.Automation.ParameterAttribute] -and $_.Mandatory } $mandatory | Should -Not -BeNullOrEmpty } It 'Has Results parameter' { $cmd = Get-Command New-AksArcReadinessReport $cmd.Parameters.ContainsKey('Results') | Should -Be $true } It 'Has FleetResults parameter' { $cmd = Get-Command New-AksArcReadinessReport $cmd.Parameters.ContainsKey('FleetResults') | Should -Be $true } It 'Has DeploymentPlan parameter' { $cmd = Get-Command New-AksArcReadinessReport $cmd.Parameters.ContainsKey('DeploymentPlan') | Should -Be $true } It 'Has PassThru parameter' { $cmd = Get-Command New-AksArcReadinessReport $cmd.Parameters.ContainsKey('PassThru') | Should -Be $true } It 'Has Title parameter' { $cmd = Get-Command New-AksArcReadinessReport $cmd.Parameters.ContainsKey('Title') | Should -Be $true } It 'Generates HTML from mock single-site results' { $mockResults = @( [PSCustomObject]@{ Gate = 'ClusterHealth'; Check = 'Provisioning'; Status = 'Passed'; Message = 'Cluster OK'; Remediation = $null; Detail = $null } [PSCustomObject]@{ Gate = 'ARB'; Check = 'Status'; Status = 'Failed'; Message = 'ARB not running'; Remediation = 'Restart ARB'; Detail = $null } [PSCustomObject]@{ Gate = 'Network'; Check = 'DNS'; Status = 'Warning'; Message = 'Slow DNS'; Remediation = $null; Detail = $null } ) $path = Join-Path $TestDrive 'report.html' New-AksArcReadinessReport -Results $mockResults -OutputPath $path Test-Path $path | Should -Be $true $content = Get-Content $path -Raw $content | Should -Match '<html' $content | Should -Match 'NOT READY' $content | Should -Match 'Remediation Actions' $content | Should -Match 'Restart ARB' } It 'Generates HTML from mock fleet results' { $mockFleet = @( [PSCustomObject]@{ ClusterName = 'c1'; ResourceGroup = 'rg1'; Region = 'eastus'; ClusterConnected = $true; ARBHealthy = $true; CustomLocationOk = $true; ExtensionTotal = 5; ExtensionFailed = 0; FailedExtensions = ''; LogicalNetworks = 2; AksClusterCount = 1; Warnings = ''; ReadyForAksArc = $true; Status = 'Ready' } [PSCustomObject]@{ ClusterName = 'c2'; ResourceGroup = 'rg2'; Region = 'westus'; ClusterConnected = $false; ARBHealthy = $false; CustomLocationOk = $false; ExtensionTotal = 3; ExtensionFailed = 1; FailedExtensions = 'ext1'; LogicalNetworks = 1; AksClusterCount = 0; Warnings = 'Extensions: 1 failed'; ReadyForAksArc = $false; Status = 'NotReady' } ) $path = Join-Path $TestDrive 'fleet-report.html' New-AksArcReadinessReport -FleetResults $mockFleet -OutputPath $path Test-Path $path | Should -Be $true $content = Get-Content $path -Raw $content | Should -Match 'Fleet Cluster Status' $content | Should -Match 'c1' $content | Should -Match 'c2' } It 'Includes IP capacity section when DeploymentPlan provided' { $plan = New-AksArcDeploymentPlan -PlannedClusters 2 -ControlPlaneNodes 3 -WorkerNodes 5 -LoadBalancerIPs 3 $path = Join-Path $TestDrive 'plan-report.html' New-AksArcReadinessReport -DeploymentPlan $plan -Results @( [PSCustomObject]@{ Gate = 'Test'; Check = 'Test'; Status = 'Passed'; Message = 'OK'; Remediation = $null; Detail = $null } ) -OutputPath $path $content = Get-Content $path -Raw $content | Should -Match 'IP Capacity Analysis' $content | Should -Match '20' # TotalRequiredIPs for 2 clusters * (3+5) + 2 upgrade + 2 CP } It 'Returns HTML string with -PassThru' { $mockResults = @( [PSCustomObject]@{ Gate = 'Test'; Check = 'Test'; Status = 'Passed'; Message = 'OK'; Remediation = $null; Detail = $null } ) $path = Join-Path $TestDrive 'passthru.html' $html = New-AksArcReadinessReport -Results $mockResults -OutputPath $path -PassThru $html | Should -Not -BeNullOrEmpty $html | Should -Match '<html' } } } |