tests/Databases.Module.Tests.ps1

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

<#
.SYNOPSIS
    Pester tests for all Databases inventory modules.
.NOTES
    Author: AzureScout Contributors
    Version: 1.0.0
#>


$DatabasesPath = Join-Path (Split-Path -Parent $PSScriptRoot) 'Modules' 'Public' 'InventoryModules' 'Databases'

$DatabaseModules = @(
    @{ Name = 'CosmosDB';         File = 'CosmosDB.ps1';         Type = 'microsoft.documentdb/databaseaccounts';              Worksheet = 'Cosmos DB' }
    @{ Name = 'MariaDB';          File = 'MariaDB.ps1';          Type = 'microsoft.dbformariadb/servers';                      Worksheet = 'MariaDB' }
    @{ Name = 'MySQL';            File = 'MySQL.ps1';            Type = 'microsoft.dbformysql/servers';                        Worksheet = 'MySQL' }
    @{ Name = 'MySQLflexible';    File = 'MySQLflexible.ps1';    Type = 'Microsoft.DBforMySQL/flexibleServers';                Worksheet = 'MySQL Flexible' }
    @{ Name = 'POSTGRE';          File = 'POSTGRE.ps1';          Type = 'microsoft.dbforpostgresql/servers';                   Worksheet = 'PostgreSQL' }
    @{ Name = 'POSTGREFlexible';  File = 'POSTGREFlexible.ps1';  Type = 'Microsoft.DBforPostgreSQL/flexibleServers';           Worksheet = 'PostgreSQL Flexible' }
    @{ Name = 'RedisCache';       File = 'RedisCache.ps1';       Type = 'microsoft.cache/redis';                               Worksheet = 'Redis Cache' }
    @{ Name = 'SQLDB';            File = 'SQLDB.ps1';            Type = 'microsoft.sql/servers/databases';                     Worksheet = 'SQL DBs' }
    @{ Name = 'SQLMI';            File = 'SQLMI.ps1';            Type = 'microsoft.sql/managedInstances';                      Worksheet = 'SQL MI' }
    @{ Name = 'SQLMIDB';          File = 'SQLMIDB.ps1';          Type = 'microsoft.sql/managedinstances/databases';            Worksheet = 'SQL MI DBs' }
    @{ Name = 'SQLPOOL';          File = 'SQLPOOL.ps1';          Type = 'microsoft.sql/servers/elasticPools';                  Worksheet = 'SQL Pools' }
    @{ Name = 'SQLSERVER';        File = 'SQLSERVER.ps1';        Type = 'microsoft.sql/servers';                               Worksheet = 'SQL Servers' }
    @{ Name = 'SQLVM';            File = 'SQLVM.ps1';            Type = 'microsoft.sqlvirtualmachine/sqlvirtualmachines';      Worksheet = 'SQL VMs' }
)

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

    function New-MockDBResource {
        param([string]$Id, [string]$Name, [string]$Type, [string]$Kind = '',
              [string]$Location = 'eastus', [string]$RG = 'rg-databases',
              [string]$SubscriptionId = 'sub-00000001', [object]$Props,
              [object]$SKU = $null, [object]$Sku2 = $null, $Zones = @('1'),
              $PrivateEndpointConnections = $null)
        # Use the lowercase 'sku' form when Sku2 is provided (SQLMI, SQLPOOL etc. use $1.sku)
        $skuVal = if ($Sku2) { $Sku2 } else { $SKU }
        $res = [PSCustomObject]@{
            id             = $Id
            NAME           = $Name
            TYPE           = $Type
            KIND           = $Kind
            LOCATION       = $Location
            RESOURCEGROUP  = $RG
            subscriptionId = $SubscriptionId
            tags           = [PSCustomObject]@{ env = 'test' }
            PROPERTIES     = $Props
            SKU            = $skuVal
            ZONES          = $Zones
        }
        # Add lowercase aliases via Add-Member so modules that access $1.sku / $1.zones / $1.kind work
        $res | Add-Member -NotePropertyName 'kind' -NotePropertyValue $Kind -Force
        $res | Add-Member -NotePropertyName 'zones' -NotePropertyValue $Zones -Force
        if ($PrivateEndpointConnections) {
            $res | Add-Member -NotePropertyName 'privateEndpointConnections' -NotePropertyValue $PrivateEndpointConnections -Force
        }
        return $res
    }

    $script:MockResources = @()

    # --- CosmosDB ---
    $script:MockResources += New-MockDBResource `
        -Id '/subscriptions/sub-00000001/resourceGroups/rg-databases/providers/microsoft.documentdb/databaseaccounts/cosmos-prod' `
        -Name 'cosmos-prod' -Type 'microsoft.documentdb/databaseaccounts' `
        -Props ([PSCustomObject]@{
            virtualNetworkRules = @([PSCustomObject]@{ id = '/subscriptions/sub-00000001/resourceGroups/rg-net/providers/Microsoft.Network/virtualNetworks/vnet-cosmos/subnets/cosmos-subnet' })
            privateEndpointConnections = @([PSCustomObject]@{ Id = '/subscriptions/sub-00000001/resourceGroups/rg-databases/providers/Microsoft.Network/privateEndpoints/pe-cosmos' })
            failoverPolicies = @(
                [PSCustomObject]@{ locationName = 'East US'; failoverPriority = 0 }
                [PSCustomObject]@{ locationName = 'West US'; failoverPriority = 1 }
            )
            mongoEndpoint = $null
            documentEndpoint = 'https://cosmos-prod.documents.azure.com:443/'
            enableFreeTier = $false
            EnabledApiTypes = 'Sql'
            backupPolicy = [PSCustomObject]@{
                type = 'Periodic'
                periodicModeProperties = [PSCustomObject]@{ backupStorageRedundancy = 'Geo' }
            }
            databaseAccountOfferType = 'Standard'
            isVirtualNetworkFilterEnabled = $true
            capapcity = [PSCustomObject]@{ totalthroughputlimit = 4000 }
            capabilities = @([PSCustomObject]@{ Name = 'EnableServerless' })
            publicNetworkAccess = 'Enabled'
            consistencyPolicy = [PSCustomObject]@{ defaultConsistencyLevel = 'Session' }
            readLocations = @([PSCustomObject]@{ locationName = 'East US' })
            writeLocations = @([PSCustomObject]@{ locationName = 'East US' })
            cors = @()
        })

    # --- MariaDB ---
    $script:MockResources += New-MockDBResource `
        -Id '/subscriptions/sub-00000001/resourceGroups/rg-databases/providers/microsoft.dbformariadb/servers/mariadb-prod' `
        -Name 'mariadb-prod' -Type 'microsoft.dbformariadb/servers' `
        -SKU ([PSCustomObject]@{ name = 'GP_Gen5_2'; family = 'Gen5'; tier = 'GeneralPurpose'; capacity = 2 }) `
        -Props ([PSCustomObject]@{
            privateEndpointConnections = @([PSCustomObject]@{ Id = '/subscriptions/sub-00000001/resourceGroups/rg-databases/providers/Microsoft.Network/privateEndpoints/pe-mariadb' })
            version = '10.3'
            storageProfile = [PSCustomObject]@{ backupRetentionDays = 7; geoRedundantBackup = 'Disabled'; storageAutogrow = 'Enabled'; storageMB = 51200 }
            publicNetworkAccess = 'Enabled'
            administratorLogin = 'dbadmin'
            InfrastructureEncryption = 'Disabled'
            minimalTlsVersion = 'TLS1_2'
            userVisibleState = 'Ready'
            replicaCapacity = 5
            replicationRole = 'None'
            byokEnforcement = 'Disabled'
            sslEnforcement = 'Enabled'
        })

    # --- MySQL ---
    $script:MockResources += New-MockDBResource `
        -Id '/subscriptions/sub-00000001/resourceGroups/rg-databases/providers/microsoft.dbformysql/servers/mysql-prod' `
        -Name 'mysql-prod' -Type 'microsoft.dbformysql/servers' `
        -SKU ([PSCustomObject]@{ name = 'GP_Gen5_4'; family = 'Gen5'; tier = 'GeneralPurpose'; capacity = 4 }) `
        -Props ([PSCustomObject]@{
            privateEndpointConnections = @([PSCustomObject]@{ Id = '/subscriptions/sub-00000001/resourceGroups/rg-databases/providers/Microsoft.Network/privateEndpoints/pe-mysql' })
            version = '5.7'
            storageProfile = [PSCustomObject]@{ backupRetentionDays = 14; geoRedundantBackup = 'Enabled'; storageAutogrow = 'Enabled'; storageMB = 102400 }
            publicNetworkAccess = 'Disabled'
            administratorLogin = 'mysqladmin'
            InfrastructureEncryption = 'Enabled'
            minimalTlsVersion = 'TLS1_2'
            userVisibleState = 'Ready'
            replicaCapacity = 5
            replicationRole = 'None'
            byokEnforcement = 'Disabled'
            sslEnforcement = 'Enabled'
        })

    # --- MySQL Flexible ---
    $script:MockResources += New-MockDBResource `
        -Id '/subscriptions/sub-00000001/resourceGroups/rg-databases/providers/Microsoft.DBforMySQL/flexibleServers/mysql-flex-prod' `
        -Name 'mysql-flex-prod' -Type 'Microsoft.DBforMySQL/flexibleServers' `
        -Props ([PSCustomObject]@{
            sku = [PSCustomObject]@{ name = 'Standard_D2ds_v4' }
            version = '8.0'
            state = 'Ready'
            availabilityZone = '1'
            administratorLogin = 'flexadmin'
            storage = [PSCustomObject]@{ storageSizeGB = 128; iops = 360; autoGrow = 'Enabled'; storageSku = 'Premium_LRS' }
            maintenanceWindow = [PSCustomObject]@{ customWindow = 'Enabled' }
            replicationRole = 'None'
            replicaCapacity = 10
            network = [PSCustomObject]@{ publicNetworkAccess = 'Disabled' }
            backup = [PSCustomObject]@{ backupRetentionDays = 7; geoRedundantBackup = 'Disabled' }
            highAvailability = [PSCustomObject]@{ mode = 'ZoneRedundant'; state = 'Healthy' }
            fullyQualifiedDomainName = 'mysql-flex-prod.mysql.database.azure.com'
        })

    # --- PostgreSQL ---
    $script:MockResources += New-MockDBResource `
        -Id '/subscriptions/sub-00000001/resourceGroups/rg-databases/providers/microsoft.dbforpostgresql/servers/pg-prod' `
        -Name 'pg-prod' -Type 'microsoft.dbforpostgresql/servers' `
        -SKU ([PSCustomObject]@{ name = 'GP_Gen5_8'; family = 'Gen5'; tier = 'GeneralPurpose'; capacity = 8 }) `
        -Props ([PSCustomObject]@{
            privateEndpointConnections = @([PSCustomObject]@{ Id = '/subscriptions/sub-00000001/resourceGroups/rg-databases/providers/Microsoft.Network/privateEndpoints/pe-pg' })
            version = '11'
            storageProfile = [PSCustomObject]@{ backupRetentionDays = 14; geoRedundantBackup = 'Enabled'; storageAutogrow = 'Enabled'; storageMB = 204800 }
            publicNetworkAccess = 'Disabled'
            administratorLogin = 'pgadmin'
            InfrastructureEncryption = 'Enabled'
            minimalTlsVersion = 'TLS1_2'
            userVisibleState = 'Ready'
            replicaCapacity = 5
            replicationRole = 'None'
            byokEnforcement = 'Disabled'
            sslEnforcement = 'Enabled'
        })

    # --- PostgreSQL Flexible ---
    $script:MockResources += New-MockDBResource `
        -Id '/subscriptions/sub-00000001/resourceGroups/rg-databases/providers/Microsoft.DBforPostgreSQL/flexibleServers/pg-flex-prod' `
        -Name 'pg-flex-prod' -Type 'Microsoft.DBforPostgreSQL/flexibleServers' `
        -SKU ([PSCustomObject]@{ tier = 'GeneralPurpose'; name = 'Standard_D4s_v3' }) `
        -Props ([PSCustomObject]@{
            fullyqualifieddomainname = 'pg-flex-prod.postgres.database.azure.com'
            administratorLogin = 'pgflexadmin'
            version = '15'
            minorversion = '4'
            authconfig = [PSCustomObject]@{ activedirectoryauth = 'Enabled'; passwordauth = 'Enabled' }
            storage = [PSCustomObject]@{ storagesizegb = 256 }
            availabilityzone = '2'
            highavailability = [PSCustomObject]@{ state = 'Healthy' }
            dataencryption = [PSCustomObject]@{ type = 'SystemManaged' }
            backup = [PSCustomObject]@{ backupretentiondays = 7; geoRedundantBackup = 'Disabled' }
            replicationRole = 'Primary'
            replicaCapacity = 5
            network = [PSCustomObject]@{
                publicnetworkaccess = 'Disabled'
                delegatedsubnetresourceid = '/subscriptions/sub-00000001/resourceGroups/rg-net/providers/Microsoft.Network/virtualNetworks/vnet-pg/subnets/pg-subnet'
                privatednszonearmresourceid = '/subscriptions/sub-00000001/resourceGroups/rg-net/providers/Microsoft.Network/privateDnsZones/pg.postgres.database.azure.com'
            }
        })

    # --- Redis Cache (standard) ---
    $script:MockResources += New-MockDBResource `
        -Id '/subscriptions/sub-00000001/resourceGroups/rg-databases/providers/microsoft.cache/redis/redis-prod' `
        -Name 'redis-prod' -Type 'microsoft.cache/redis' `
        -Props ([PSCustomObject]@{
            privateEndpointConnections = @([PSCustomObject]@{
                properties = [PSCustomObject]@{
                    privateEndpoint = [PSCustomObject]@{
                        id = '/subscriptions/sub-00000001/resourceGroups/rg-databases/providers/Microsoft.Network/privateEndpoints/pe-redis'
                    }
                }
            })
            redisVersion = '6.0'
            publicNetworkAccess = 'Disabled'
            hostName = 'redis-prod.redis.cache.windows.net'
            port = 6379
            enableNonSslPort = $false
            minimumTlsVersion = '1.2'
            sslPort = 6380
            sku = [PSCustomObject]@{ name = 'Premium'; capacity = 1; family = 'P' }
            redisConfiguration = [PSCustomObject]@{
                'maxfragmentationmemory-reserved' = '642'
                'maxmemory-reserved' = '642'
                'maxmemory-delta' = '642'
                'maxclients' = '7500'
            }
        })

    # --- Redis Enterprise ---
    $script:MockResources += New-MockDBResource `
        -Id '/subscriptions/sub-00000001/resourceGroups/rg-databases/providers/microsoft.cache/redisenterprise/redis-ent-prod' `
        -Name 'redis-ent-prod' -Type 'microsoft.cache/redisenterprise' `
        -Props ([PSCustomObject]@{
            privateEndpointConnections = @()
            redisVersion = '6.0'
            publicNetworkAccess = 'Enabled'
            hostName = 'redis-ent-prod.redisenterprise.cache.azure.net'
            port = 10000
            enableNonSslPort = $false
            minimumTlsVersion = ''
            sslPort = 10000
            sku = [PSCustomObject]@{ name = 'Enterprise_E10'; capacity = 2; family = 'Enterprise' }
            redisConfiguration = [PSCustomObject]@{
                'maxfragmentationmemory-reserved' = $null
                'maxmemory-reserved' = $null
                'maxmemory-delta' = $null
                'maxclients' = $null
            }
        })

    # --- SQL Database (non-master) ---
    $script:MockResources += New-MockDBResource `
        -Id '/subscriptions/sub-00000001/resourceGroups/rg-databases/providers/microsoft.sql/servers/sql-prod/databases/appdb' `
        -Name 'appdb' -Type 'microsoft.sql/servers/databases' `
        -Props ([PSCustomObject]@{
            elasticPoolId = '/subscriptions/sub-00000001/resourceGroups/rg-databases/providers/Microsoft.Sql/servers/sql-prod/elasticPools/ep-prod'
            earliestrestoredate = '2025-06-01T00:00:00Z'
            defaultSecondaryLocation = 'westus'
            status = 'Online'
            availabilityzone = '1'
            minCapacity = 0.5
            currentSku = [PSCustomObject]@{ capacity = 2; tier = 'GeneralPurpose'; name = 'GP_S_Gen5' }
            zoneRedundant = $false
            catalogCollation = 'SQL_Latin1_General_CP1_CI_AS'
            readReplicaCount = 0
            maxSizeBytes = 34359738368
        })

    # --- SQL Managed Instance ---
    $script:MockResources += New-MockDBResource `
        -Id '/subscriptions/sub-00000001/resourceGroups/rg-databases/providers/microsoft.sql/managedInstances/sqlmi-prod' `
        -Name 'sqlmi-prod' -Type 'microsoft.sql/managedInstances' `
        -Sku2 ([PSCustomObject]@{ Name = 'GP_Gen5'; capacity = 8; tier = 'GeneralPurpose' }) `
        -PrivateEndpointConnections @([PSCustomObject]@{ id = '/subscriptions/sub-00000001/resourceGroups/rg-databases/providers/Microsoft.Network/privateEndpoints/pe-sqlmi' }) `
        -Props ([PSCustomObject]@{
            adminitrators = [PSCustomObject]@{ login = 'sqladmin'; azureADOnlyAuthentication = $false }
            fullyQualifiedDomainName = 'sqlmi-prod.abcdef123456.database.windows.net'
            publicDataEndpointEnabled = $false
            licenseType = 'LicenseIncluded'
            managedInstanceCreateMode = 'Default'
            zoneRedundant = $true
        })

    # --- SQL MI Database ---
    $script:MockResources += New-MockDBResource `
        -Id '/subscriptions/sub-00000001/resourceGroups/rg-databases/providers/microsoft.sql/managedinstances/sqlmi-prod/databases/midb-app' `
        -Name 'midb-app' -Type 'microsoft.sql/managedinstances/databases' `
        -Props ([PSCustomObject]@{
            privateEndpointConnections = @([PSCustomObject]@{ id = '/subscriptions/sub-00000001/resourceGroups/rg-databases/providers/Microsoft.Sql/managedInstances/sqlmi-prod/privateEndpoints/pe-midb' })
            collation = 'SQL_Latin1_General_CP1_CI_AS'
            creationDate = '2025-03-15T10:00:00Z'
            defaultSecondaryLocation = 'westus'
            status = 'Online'
        })

    # --- SQL Elastic Pool ---
    $script:MockResources += New-MockDBResource `
        -Id '/subscriptions/sub-00000001/resourceGroups/rg-databases/providers/microsoft.sql/servers/sql-prod/elasticPools/ep-prod' `
        -Name 'ep-prod' -Type 'microsoft.sql/servers/elasticPools' `
        -Sku2 ([PSCustomObject]@{ capacity = 200; name = 'GP_Gen5'; tier = 'GeneralPurpose' }) `
        -Props ([PSCustomObject]@{
            state = 'Ready'
            licenseType = 'LicenseIncluded'
            maxSizeBytes = 107374182400
            perDatabaseSettings = [PSCustomObject]@{ maxCapacity = 2; minCapacity = 0 }
            zoneRedundant = $false
        })

    # --- SQL Server ---
    $script:MockResources += New-MockDBResource `
        -Id '/subscriptions/sub-00000001/resourceGroups/rg-databases/providers/microsoft.sql/servers/sql-prod' `
        -Name 'sql-prod' -Type 'microsoft.sql/servers' -Kind 'v12.0' `
        -Props ([PSCustomObject]@{
            privateEndpointConnections = @([PSCustomObject]@{ id = '/subscriptions/sub-00000001/resourceGroups/rg-databases/providers/Microsoft.Sql/servers/sql-prod/privateEndpoints/pe-sqlserver' })
            administratorLogin = 'sqladmin'
            fullyQualifiedDomainName = 'sql-prod.database.windows.net'
            publicNetworkAccess = 'Disabled'
            minimalTlsVersion = '1.2'
            state = 'Ready'
            version = '12.0'
        })

    # --- SQL VM ---
    $script:MockResources += New-MockDBResource `
        -Id '/subscriptions/sub-00000001/resourceGroups/rg-databases/providers/microsoft.sqlvirtualmachine/sqlvirtualmachines/sqlvm-prod' `
        -Name 'sqlvm-prod' -Type 'microsoft.sqlvirtualmachine/sqlvirtualmachines' `
        -Props ([PSCustomObject]@{
            sqlServerLicenseType = 'PAYG'
            sqlImageOffer = 'SQL2019-WS2019'
            sqlManagement = 'Full'
            sqlImageSku = 'Enterprise'
        })
}

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

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

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