tests/Containers.Module.Tests.ps1

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

<#
.SYNOPSIS
    Pester tests for all Containers inventory modules.
.NOTES
    Author: AzureScout Contributors
    Version: 1.0.0
    Created: 2026-02-25
#>


$ContainersPath = Join-Path (Split-Path -Parent $PSScriptRoot) 'Modules' 'Public' 'InventoryModules' 'Containers'

$ContainerModules = @(
    @{ Name = 'AKS';                File = 'AKS.ps1';                Type = 'microsoft.containerservice/managedclusters';     Worksheet = 'AKS' }
    @{ Name = 'ARO';                File = 'ARO.ps1';                Type = 'microsoft.redhatopenshift/openshiftclusters';     Worksheet = 'ARO' }
    @{ Name = 'ContainerApp';       File = 'ContainerApp.ps1';       Type = 'microsoft.app/containerapps';                    Worksheet = 'Container Apps' }
    @{ Name = 'ContainerAppEnv';    File = 'ContainerAppEnv.ps1';    Type = 'microsoft.app/managedenvironments';              Worksheet = 'Container App Env' }
    @{ Name = 'ContainerGroups';    File = 'ContainerGroups.ps1';    Type = 'microsoft.containerinstance/containergroups';     Worksheet = 'Containers' }
    @{ Name = 'ContainerRegistries';File = 'ContainerRegistries.ps1';Type = 'microsoft.containerregistry/registries';          Worksheet = 'Registries' }
)

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

    function New-MockContainerResource {
        param([string]$Id, [string]$Name, [string]$Type, [string]$Kind = '',
              [string]$Location = 'eastus', [string]$RG = 'rg-containers',
              [string]$SubscriptionId = 'sub-00000001', [object]$Props, [object]$SKU = $null)
        [PSCustomObject]@{
            id             = $Id
            NAME           = $Name
            TYPE           = $Type
            KIND           = $Kind
            LOCATION       = $Location
            RESOURCEGROUP  = $RG
            subscriptionId = $SubscriptionId
            tags           = [PSCustomObject]@{}
            PROPERTIES     = $Props
            SKU            = $SKU
            ZONES          = @('1')
        }
    }

    $script:MockResources = @()

    # --- AKS ---
    $script:MockResources += New-MockContainerResource -Id '/subscriptions/sub-00000001/resourceGroups/rg-containers/providers/microsoft.containerservice/managedclusters/aks-prod' `
        -Name 'aks-prod' -Type 'microsoft.containerservice/managedclusters' `
        -SKU ([PSCustomObject]@{ name = 'Base'; tier = 'Standard' }) `
        -Props ([PSCustomObject]@{
            kubernetesVersion = '1.28.5'; powerstate = [PSCustomObject]@{ code = 'Running' }
            enableRBAC = $true; aadProfile = [PSCustomObject]@{ admingroupobjectids = @('group1') }
            disablelocalaccounts = $true
            networkprofile = [PSCustomObject]@{ networkplugin = 'azure'; networkpluginmode = 'overlay'; podCidr = '10.244.0.0/16'; networkPolicy = 'calico'; outboundType = 'loadBalancer' }
            noderesourcegroup = 'MC_rg-containers_aks-prod_eastus'
            identityprofile = [PSCustomObject]@{ kubeletidentity = [PSCustomObject]@{ resourceid = '/subscriptions/sub-00000001/resourceGroups/MC_rg/providers/Microsoft.ManagedIdentity/userAssignedIdentities/aks-kubelet' } }
            addonProfiles = [PSCustomObject]@{
                omsagent = [PSCustomObject]@{ config = [PSCustomObject]@{ logAnalyticsWorkspaceResourceID = '/subscriptions/sub-00000001/resourceGroups/rg-mon/providers/microsoft.operationalinsights/workspaces/la-prod' } }
                ingressApplicationGateway = [PSCustomObject]@{ config = [PSCustomObject]@{ applicationGatewayName = 'appgw-aks' } }
            }
            apiServerAccessProfile = [PSCustomObject]@{ enablePrivateCluster = $false }
            privatefqdn = $null; publicNetworkAccess = 'Enabled'
            autoUpgradeProfile = [PSCustomObject]@{ upgradeChannel = 'stable'; nodeosupgradechannel = 'NodeImage' }
            fqdn = 'aks-prod-dns.hcp.eastus.azmk8s.io'
            agentPoolProfiles = @([PSCustomObject]@{
                name = 'nodepool1'; powerstate = [PSCustomObject]@{ code = 'Running' }
                orchestratorVersion = '1.28.5'; mode = 'System'; osType = 'Linux'
                ossku = 'AzureLinux'; nodeimageversion = 'AzureLinux-202401.01.0'
                vmSize = 'Standard_D4s_v3'; osDiskSizeGB = 128; count = 3
                availabilityZones = @('1','2','3'); enableAutoScaling = $true
                minCount = 3; maxCount = 10; maxPods = 110
                vnetSubnetID = '/subscriptions/sub-00000001/resourceGroups/rg-net/providers/Microsoft.Network/virtualNetworks/vnet-aks/subnets/aks-subnet'
                enableNodePublicIP = $false; nodetaints = @(); nodelabels = [PSCustomObject]@{}
            })
        })

    # --- ARO ---
    $script:MockResources += New-MockContainerResource -Id '/subscriptions/sub-00000001/resourceGroups/rg-containers/providers/microsoft.redhatopenshift/openshiftclusters/aro-prod' `
        -Name 'aro-prod' -Type 'microsoft.redhatopenshift/openshiftclusters' `
        -Props ([PSCustomObject]@{
            clusterProfile = [PSCustomObject]@{ version = '4.14.16'; domain = 'aro-prod' }
            networkProfile = [PSCustomObject]@{ outboundType = 'Loadbalancer'; podCidr = '10.128.0.0/14'; serviceCidr = '172.30.0.0/16' }
            ingressProfiles = @([PSCustomObject]@{ name = 'default'; visibility = 'Public'; ip = '20.1.2.3' })
            apiserverProfile = [PSCustomObject]@{ visibility = 'Public'; url = 'https://api.aro-prod.eastus.aroapp.io:6443'; ip = '20.1.2.4' }
            consoleProfile = [PSCustomObject]@{ url = 'https://console-openshift-console.apps.aro-prod.eastus.aroapp.io' }
            masterProfile = [PSCustomObject]@{
                vmSize = 'Standard_D8s_v3'
                subnetId = '/subscriptions/sub-00000001/resourceGroups/rg-net/providers/Microsoft.Network/virtualNetworks/vnet-aro/subnets/master-subnet'
            }
            workerProfiles = @([PSCustomObject]@{
                vmSize = 'Standard_D4s_v3'; diskSizeGB = 128; count = 3
                subnetId = '/subscriptions/sub-00000001/resourceGroups/rg-net/providers/Microsoft.Network/virtualNetworks/vnet-aro/subnets/worker-subnet'
            })
        })

    # --- Container App ---
    $envId = '/subscriptions/sub-00000001/resourceGroups/rg-containers/providers/microsoft.app/managedenvironments/cae-prod'
    $script:MockResources += New-MockContainerResource -Id '/subscriptions/sub-00000001/resourceGroups/rg-containers/providers/microsoft.app/containerapps/ca-api' `
        -Name 'ca-api' -Type 'microsoft.app/containerapps' `
        -Props ([PSCustomObject]@{
            runningStatus = 'Running'
            environmentId = $envId
            workloadProfileName = 'Consumption'
            configuration = [PSCustomObject]@{
                ingress = [PSCustomObject]@{ targetPort = 8080; external = $true; allowInsecure = $false; transport = 'auto' }
                dapr = $null; secrets = @([PSCustomObject]@{name='secret1'})
            }
            template = @([PSCustomObject]@{
                containers = @([PSCustomObject]@{
                    name = 'api'; image = 'mcr.microsoft.com/azuredocs/containerapps-helloworld:latest'
                    resources = [PSCustomObject]@{ cpu = 0.5; memory = '1Gi'; ephemeralStorage = '2Gi' }
                })
            })
        })

    # --- Container App Environment ---
    $script:MockResources += New-MockContainerResource -Id $envId `
        -Name 'cae-prod' -Type 'microsoft.app/managedenvironments' `
        -Props ([PSCustomObject]@{
            publicNetworkAccess = 'Enabled'; zoneRedundant = $true; staticIp = '10.0.0.100'
            kedaconfiguration = [PSCustomObject]@{ Version = '2.12.1' }
            daprconfiguration = [PSCustomObject]@{ Version = '1.12.0' }
            workloadProfiles = @([PSCustomObject]@{ name = 'Consumption'; workloadProfileType = 'Consumption'; minimumCount = 0; maximumCount = 30 })
        })

    # --- Container Groups ---
    $script:MockResources += New-MockContainerResource -Id '/subscriptions/sub-00000001/resourceGroups/rg-containers/providers/microsoft.containerinstance/containergroups/cg-worker' `
        -Name 'cg-worker' -Type 'microsoft.containerinstance/containergroups' `
        -Props ([PSCustomObject]@{
            osType = 'Linux'
            containers = @([PSCustomObject]@{
                name = 'worker'
                properties = [PSCustomObject]@{
                    instanceView = [PSCustomObject]@{ currentState = [PSCustomObject]@{ state = 'Running'; startTime = '2025-06-01T10:00:00Z' }; restartCount = 0 }
                    image = 'myregistry.azurecr.io/worker:v1'
                    command = @('/bin/sh', '-c', 'echo hello')
                    resources = [PSCustomObject]@{ requests = [PSCustomObject]@{ cpu = 1.0; memoryInGB = 1.5 } }
                    ports = @([PSCustomObject]@{ protocol = 'TCP'; port = 80 })
                }
            })
            ipAddress = [PSCustomObject]@{ ip = '10.0.0.50' }
        })

    # --- Container Registries ---
    $script:MockResources += New-MockContainerResource -Id '/subscriptions/sub-00000001/resourceGroups/rg-containers/providers/microsoft.containerregistry/registries/acrprod' `
        -Name 'acrprod' -Type 'microsoft.containerregistry/registries' `
        -SKU ([PSCustomObject]@{ name = 'Premium' }) `
        -Props ([PSCustomObject]@{
            creationDate = '2025-01-15T08:00:00Z'
            anonymouspullenabled = $false
            encryption = [PSCustomObject]@{ status = 'Disabled' }
            publicnetworkaccess = 'Enabled'
            zoneredundancy = 'Enabled'
            privateendpointconnections = @([PSCustomObject]@{ id = 'pe1' })
            policies = [PSCustomObject]@{
                softdeletepolicy = [PSCustomObject]@{ status = 'enabled' }
                trustpolicy = [PSCustomObject]@{ status = 'disabled' }
            }
        })
}

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

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

Describe 'Containers Module Processing Phase — <Name>' -ForEach $ContainerModules {
    BeforeAll {
        $script:ModFile = Join-Path $script:ContainersPath $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 'Containers Module Reporting Phase — <Name>' -ForEach $ContainerModules {
    BeforeAll {
        $script:ModFile  = Join-Path $script:ContainersPath $File
        $script:ResType  = $Type
        $script:XlsxFile = Join-Path $script:TempDir ("Ctr_{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'" }
    }
}