tests/Private.Extraction.Tests.ps1
|
#Requires -Modules @{ ModuleName = 'Pester'; ModuleVersion = '5.0.0' } <# .SYNOPSIS Pester tests for Private/Extraction modules. .DESCRIPTION Tests extraction pipeline functions: API resource retrieval, cost inventory, management groups, subscriptions, inventory loop, Entra extraction, Graph extraction, VM quotas, and VM SKU details. No live Azure authentication is required. .NOTES Author: AzureScout Contributors Version: 1.0.0 #> BeforeAll { $script:ModuleRoot = Split-Path -Parent $PSScriptRoot $script:ExtractionPath = Join-Path $script:ModuleRoot 'Modules' 'Private' 'Extraction' $script:ResourceDetails = Join-Path $script:ExtractionPath 'ResourceDetails' } # ===================================================================== # FILE EXISTENCE # ===================================================================== Describe 'Private/Extraction Module Files Exist' { $extractionFiles = @( 'Get-AZTIAPIResources.ps1', 'Get-AZTICostInventory.ps1', 'Get-AZTIManagementGroups.ps1', 'Get-AZTISubscriptions.ps1', 'Invoke-AZTIInventoryLoop.ps1', 'Start-AZTIEntraExtraction.ps1', 'Start-AZTIGraphExtraction.ps1' ) It '<_> exists' -ForEach $extractionFiles { Join-Path $script:ExtractionPath $_ | Should -Exist } It 'ResourceDetails/Get-AZTIVMQuotas.ps1 exists' { Join-Path $script:ResourceDetails 'Get-AZTIVMQuotas.ps1' | Should -Exist } It 'ResourceDetails/Get-AZTIVMSkuDetails.ps1 exists' { Join-Path $script:ResourceDetails 'Get-AZTIVMSkuDetails.ps1' | Should -Exist } } # ===================================================================== # SYNTAX VALIDATION # ===================================================================== Describe 'Private/Extraction Script Syntax Validation' { $allFiles = @( 'Get-AZTIAPIResources.ps1', 'Get-AZTICostInventory.ps1', 'Get-AZTIManagementGroups.ps1', 'Get-AZTISubscriptions.ps1', 'Invoke-AZTIInventoryLoop.ps1', 'Start-AZTIEntraExtraction.ps1', 'Start-AZTIGraphExtraction.ps1' ) It '<_> parses without errors' -ForEach $allFiles { $filePath = Join-Path $script:ExtractionPath $_ $errors = $null [System.Management.Automation.Language.Parser]::ParseFile($filePath, [ref]$null, [ref]$errors) $errors | Should -BeNullOrEmpty } $rdFiles = @('Get-AZTIVMQuotas.ps1', 'Get-AZTIVMSkuDetails.ps1') It 'ResourceDetails/<_> parses without errors' -ForEach $rdFiles { $filePath = Join-Path $script:ResourceDetails $_ $errors = $null [System.Management.Automation.Language.Parser]::ParseFile($filePath, [ref]$null, [ref]$errors) $errors | Should -BeNullOrEmpty } } # ===================================================================== # FUNCTION DEFINITIONS # ===================================================================== Describe 'Private/Extraction Function Definitions' { It 'Get-AZTIAPIResources.ps1 defines Get-AZSCAPIResources' { $content = Get-Content (Join-Path $script:ExtractionPath 'Get-AZTIAPIResources.ps1') -Raw $content | Should -Match 'function\s+Get-AZSCAPIResources' } It 'Get-AZTICostInventory.ps1 defines Get-AZSCCostInventory' { $content = Get-Content (Join-Path $script:ExtractionPath 'Get-AZTICostInventory.ps1') -Raw $content | Should -Match 'function\s+Get-AZSCCostInventory' } It 'Get-AZTIManagementGroups.ps1 defines Get-AZSCManagementGroups' { $content = Get-Content (Join-Path $script:ExtractionPath 'Get-AZTIManagementGroups.ps1') -Raw $content | Should -Match 'function\s+Get-AZSCManagementGroups' } It 'Get-AZTISubscriptions.ps1 defines Get-AZSCSubscriptions' { $content = Get-Content (Join-Path $script:ExtractionPath 'Get-AZTISubscriptions.ps1') -Raw $content | Should -Match 'function\s+Get-AZSCSubscriptions' } It 'Invoke-AZTIInventoryLoop.ps1 defines Invoke-AZSCInventoryLoop' { $content = Get-Content (Join-Path $script:ExtractionPath 'Invoke-AZTIInventoryLoop.ps1') -Raw $content | Should -Match 'function\s+Invoke-AZSCInventoryLoop' } It 'Start-AZTIEntraExtraction.ps1 defines Start-AZSCEntraExtraction' { $content = Get-Content (Join-Path $script:ExtractionPath 'Start-AZTIEntraExtraction.ps1') -Raw $content | Should -Match 'function\s+Start-AZSCEntraExtraction' } It 'Start-AZTIGraphExtraction.ps1 defines Start-AZSCGraphExtraction' { $content = Get-Content (Join-Path $script:ExtractionPath 'Start-AZTIGraphExtraction.ps1') -Raw $content | Should -Match 'function\s+Start-AZSCGraphExtraction' } It 'Get-AZTIVMQuotas.ps1 defines Get-AZSCVMQuotas' { $content = Get-Content (Join-Path $script:ResourceDetails 'Get-AZTIVMQuotas.ps1') -Raw $content | Should -Match 'function\s+Get-AZSCVMQuotas' } It 'Get-AZTIVMSkuDetails.ps1 defines Get-AZSCVMSkuDetails' { $content = Get-Content (Join-Path $script:ResourceDetails 'Get-AZTIVMSkuDetails.ps1') -Raw $content | Should -Match 'function\s+Get-AZSCVMSkuDetails' } } # ===================================================================== # STRICTMODE CRASH-HARDENING REGRESSION TESTS (AB#5041 sweep) # ===================================================================== # Invoke-AZSCInventoryLoop used to `return $LocalResults` directly. When # $LocalResults stayed an empty @() (e.g. every Search-AzGraph page came back # empty), PowerShell's empty-array-on-return unrolls to $null — and the very # next `.Count` access on that $null by a caller throws under # `Set-StrictMode -Version Latest`. The fix is `return ,$LocalResults`. Describe 'Invoke-AZSCInventoryLoop empty-result StrictMode hardening' { BeforeAll { . (Join-Path $script:ExtractionPath 'Invoke-AZTIInventoryLoop.ps1') Mock Search-AzGraph { return @() } } It 'never returns $null for an empty result set (would collapse via bare "return $var")' { Set-StrictMode -Version Latest $result = Invoke-AZSCInventoryLoop -GraphQuery 'resources' -FSubscri @('11111111-1111-1111-1111-111111111111') -LoopName 'Test' # Intentionally NOT using -BeNullOrEmpty: an empty array is a valid, non-null result # here and that distinction is exactly what this regression test protects. ($null -eq $result) | Should -BeFalse -Because 'a real (possibly-empty) array must come back, never $null' } It 'lets the caller call .Count on the result without throwing under StrictMode' { Set-StrictMode -Version Latest $result = Invoke-AZSCInventoryLoop -GraphQuery 'resources' -FSubscri @('11111111-1111-1111-1111-111111111111') -LoopName 'Test' { $result.Count } | Should -Not -Throw $result.Count | Should -Be 0 } It 'accepts a single scalar subscription id without throwing on the internal .count checks' { Set-StrictMode -Version Latest { Invoke-AZSCInventoryLoop -GraphQuery 'resources' -FSubscri '11111111-1111-1111-1111-111111111111' -LoopName 'Test' } | Should -Not -Throw } } |