tests/Collect.VmQuotaSku.Tests.ps1

#Requires -Version 7.0
#Requires -Modules Pester

<#
    Pester tests for src/collect/Get-ScoutVmQuotas.ps1 and Get-ScoutVmSkuDetails.ps1
    (AB#5639/5646, Epic AB#5638).
 
    Get-AzContext / Set-AzContext / Get-AzVMUsage / Get-AzComputeResourceSku are mocked
    throughout -- no live Azure connection is made.
#>


BeforeAll {
    $root = Split-Path $PSScriptRoot -Parent
    . "$root/src/collect/Get-ScoutVmQuotas.ps1"
    . "$root/src/collect/Get-ScoutVmSkuDetails.ps1"

    function Get-AzContext { [pscustomobject]@{ Subscription = [pscustomobject]@{ Id = 'original-sub' } } }
    function Set-AzContext { param([Parameter(ValueFromRemainingArguments)] $Rest) }

    # Mixed array: Resource Graph rows carrying subscriptionId/type, alongside a REST-API row
    # (e.g. from Get-ScoutApiResources) that carries neither -- the exact AB#5633 shape.
    $script:mixedResources = @(
        [pscustomobject]@{ type = 'microsoft.compute/virtualmachines'; subscriptionId = 'sub-1'; location = 'eastus' }
        [pscustomobject]@{ type = 'microsoft.compute/virtualmachinescalesets'; subscriptionId = 'sub-1'; location = 'westus' }
        [pscustomobject]@{ type = 'microsoft.storage/storageaccounts'; subscriptionId = 'sub-1'; location = 'eastus' }
        [pscustomobject]@{ Subscription = 'sub-1'; ResourceHealth = @() }   # REST-API row, no type/subscriptionId
    )
    $script:subs = @([pscustomobject]@{ id = 'sub-1'; name = 'sub-one' })
}

Describe 'Get-ScoutVmQuotas' {
    It 'does not throw on a mixed array missing type/subscriptionId on some rows (AB#5633)' {
        function Get-AzVMUsage { param([string] $Location) return @() }
        { Get-ScoutVmQuotas -Subscriptions $script:subs -Resources $script:mixedResources } | Should -Not -Throw
    }

    It 'queries only the locations that actually have a VM/VMSS' {
        $script:queriedLocations = @()
        function Get-AzVMUsage {
            param([string] $Location)
            $script:queriedLocations += $Location
            return @()
        }
        Get-ScoutVmQuotas -Subscriptions $script:subs -Resources $script:mixedResources | Out-Null
        ($script:queriedLocations | Sort-Object) | Should -Be @('eastus', 'westus')
    }

    It 'preserves the AZSC/VM/Quotas type tag and filters to CurrentValue >= 1' {
        function Get-AzVMUsage {
            param([string] $Location)
            return @(
                [pscustomobject]@{ Name = [pscustomobject]@{ Value = 'cores' }; CurrentValue = 4; Limit = 100 }
                [pscustomobject]@{ Name = [pscustomobject]@{ Value = 'unused' }; CurrentValue = 0; Limit = 100 }
            )
        }
        $result = Get-ScoutVmQuotas -Subscriptions $script:subs -Resources $script:mixedResources
        $result.type | Should -Be 'AZSC/VM/Quotas'
        $entry = $result.properties | Where-Object { $_.Location -eq 'eastus' }
        $entry.Data.Count | Should -Be 1
        $entry.Data[0].Name.Value | Should -Be 'cores'
    }

    It 'restores the original Az context even when a quota lookup throws' {
        $script:restoredTo = $null
        function Set-AzContext {
            param([string] $Subscription, [string] $SubscriptionId, [Parameter(ValueFromRemainingArguments)] $Rest)
            if ($SubscriptionId) { $script:restoredTo = $SubscriptionId }
        }
        function Get-AzVMUsage { param([string] $Location) throw 'transient quota API error' }
        { Get-ScoutVmQuotas -Subscriptions $script:subs -Resources $script:mixedResources -WarningAction SilentlyContinue } | Should -Not -Throw
        $script:restoredTo | Should -Be 'original-sub'
    }
}

Describe 'Get-ScoutVmSkuDetails' {
    It 'does not throw on a mixed array missing type on some rows' {
        function Get-AzComputeResourceSku { param([string] $Location) return @() }
        { Get-ScoutVmSkuDetails -Resources $script:mixedResources } | Should -Not -Throw
    }

    It 'queries only the distinct VM/VMSS locations and preserves the AZSC/VM/SKU type tag' {
        $script:queriedLocations = @()
        function Get-AzComputeResourceSku {
            param([string] $Location)
            $script:queriedLocations += $Location
            return @([pscustomobject]@{ Name = 'Standard_D2s_v5' })
        }
        $result = Get-ScoutVmSkuDetails -Resources $script:mixedResources
        $result.type | Should -Be 'AZSC/VM/SKU'
        ($script:queriedLocations | Sort-Object) | Should -Be @('eastus', 'westus')
        ($result.properties | Where-Object Location -eq 'eastus').SKUs[0].Name | Should -Be 'Standard_D2s_v5'
    }
}