tests/Web.Module.Tests.ps1

#Requires -Modules @{ ModuleName = 'Pester'; ModuleVersion = '5.0.0' }
#Requires -Modules ImportExcel

<#
.SYNOPSIS
    Pester tests for Web inventory modules (AppServicePlan, AppServices).
#>


$WebPath = Join-Path (Split-Path -Parent $PSScriptRoot) 'Modules' 'Public' 'InventoryModules' 'Web'

$WebModules = @(
    @{ Name = 'AppServicePlan'; File = 'APPServicePlan.ps1'; Type = 'microsoft.web/serverfarms'; Worksheet = 'App Service Plan' }
    @{ Name = 'AppServices';    File = 'APPServices.ps1';    Type = 'microsoft.web/sites';       Worksheet = 'App Services' }
)

BeforeAll {
    $script:ModuleRoot = Split-Path -Parent $PSScriptRoot
    $script:WebPath    = Join-Path $script:ModuleRoot 'Modules' 'Public' 'InventoryModules' 'Web'
    $script:TempDir    = Join-Path $env:TEMP 'AZSC_WebTests'
    if (Test-Path $script:TempDir) { Remove-Item $script:TempDir -Recurse -Force }
    New-Item -ItemType Directory -Path $script:TempDir -Force | Out-Null

    $script:MockResources = @()

    $aspId = '/subscriptions/sub-00000001/resourceGroups/rg-web/providers/microsoft.web/serverfarms/asp-prod'

    # --- App Service Plan ---
    $script:MockResources += [PSCustomObject]@{
        id             = $aspId
        NAME           = 'asp-prod'
        TYPE           = 'microsoft.web/serverfarms'
        KIND           = 'app'
        LOCATION       = 'eastus'
        RESOURCEGROUP  = 'rg-web'
        subscriptionId = 'sub-00000001'
        tags           = [PSCustomObject]@{ env = 'prod' }
        SKU            = [PSCustomObject]@{ tier = 'Standard'; name = 'S1' }
        PROPERTIES     = [PSCustomObject]@{
            numberOfSites = 2
            computeMode = 'Dedicated'
            currentWorkerSize = 'Small'
            currentNumberOfWorkers = 2
            maximumNumberOfWorkers = 10
            reserved = 'false'
            kind = 'app'
            zoneRedundant = $false
        }
    }

    # --- AutoScale Setting (cross-resource lookup for AppServicePlan) ---
    $script:MockResources += [PSCustomObject]@{
        id             = '/subscriptions/sub-00000001/resourceGroups/rg-web/providers/microsoft.insights/autoscalesettings/autoscale-asp-prod'
        NAME           = 'autoscale-asp-prod'
        TYPE           = 'microsoft.insights/autoscalesettings'
        LOCATION       = 'eastus'
        RESOURCEGROUP  = 'rg-web'
        subscriptionId = 'sub-00000001'
        tags           = [PSCustomObject]@{}
        Properties     = [PSCustomObject]@{
            enabled = 'true'
            targetResourceUri = $aspId
        }
    }

    # --- App Service ---
    $script:MockResources += [PSCustomObject]@{
        id             = '/subscriptions/sub-00000001/resourceGroups/rg-web/providers/microsoft.web/sites/webapp-prod'
        NAME           = 'webapp-prod'
        TYPE           = 'microsoft.web/sites'
        KIND           = 'app'
        LOCATION       = 'eastus'
        RESOURCEGROUP  = 'rg-web'
        subscriptionId = 'sub-00000001'
        tags           = [PSCustomObject]@{ env = 'prod' }
        PROPERTIES     = [PSCustomObject]@{
            sku = 'Standard'
            virtualNetworkSubnetId = '/subscriptions/sub-00000001/resourceGroups/rg-net/providers/Microsoft.Network/virtualNetworks/vnet-web/subnets/webapp-subnet'
            siteConfig = [PSCustomObject]@{ ftpsState = 'Disabled'; linuxFxVersion = ''; windowsFxVersion = 'DOTNETCORE|6.0' }
            Properties = [PSCustomObject]@{ SiteConfig = [PSCustomObject]@{ acrUseManagedIdentityCreds = $false } }
            hostNameSslStates = @(
                [PSCustomObject]@{ Name = 'webapp-prod.azurewebsites.net'; hostType = 'Standard'; sslState = 'SniEnabled' }
            )
            zoneRedundant = $false
            maximumNumberOfZones = 3
            currentNumberOfZonesUtilized = 1
            enabled = $true
            state = 'Running'
            clientCertEnabled = $false
            clientCertMode = 'Optional'
            contentAvailabilityState = 'Normal'
            runtimeAvailabilityState = 'Normal'
            httpsOnly = $true
            possibleInboundIpAddresses = '20.1.2.3'
            repositorySiteName = 'webapp-prod'
            availabilityState = 'Normal'
            defaultHostName = 'webapp-prod.azurewebsites.net'
            containerSize = 0
            adminEnabled = $false
            ftpsHostName = 'ftps://waws-prod.ftp.azurewebsites.windows.net'
        }
    }
}

AfterAll {
    if (Test-Path $script:TempDir) { Remove-Item $script:TempDir -Recurse -Force }
}

Describe 'Web Module Files Exist' {
    It 'Web module folder exists' { $script:WebPath | Should -Exist }
    It '<Name> module file exists' -ForEach $WebModules { Join-Path $script:WebPath $File | Should -Exist }
}

Describe 'Web Module Processing Phase — <Name>' -ForEach $WebModules {
    BeforeAll {
        $script:ModFile = Join-Path $script:WebPath $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 'Web Module Reporting Phase — <Name>' -ForEach $WebModules {
    BeforeAll {
        $script:ModFile  = Join-Path $script:WebPath $File
        $script:ResType  = $Type
        $script:XlsxFile = Join-Path $script:TempDir ("Web_{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 processed data' }
    }
    It 'Excel file is created' {
        if ($script:ProcessedData) { $script:XlsxFile | Should -Exist }
        else { Set-ItResult -Skipped -Because 'No processed data' }
    }
}