tests/Private/Get-AzureInstanceData.Tests.ps1

BeforeAll {
    if (-not (Get-Command Get-AzNetworkInterface -ErrorAction SilentlyContinue)) {
        $script:stubCreatedGetAzNetworkInterface = $true
        function global:Get-AzNetworkInterface { param([string]$ResourceId) }
    }

    if (-not (Get-Command Get-AzPublicIpAddress -ErrorAction SilentlyContinue)) {
        $script:stubCreatedGetAzPublicIpAddress = $true
        function global:Get-AzPublicIpAddress { param([string]$ResourceId) }
    }

    Import-Module (Resolve-Path (Join-Path $PSScriptRoot '..\..\PSCumulus.psd1')).Path -Force
}

AfterAll {
    if ($script:stubCreatedGetAzNetworkInterface) {
        Remove-Item -Path Function:global:Get-AzNetworkInterface -ErrorAction SilentlyContinue
    }

    if ($script:stubCreatedGetAzPublicIpAddress) {
        Remove-Item -Path Function:global:Get-AzPublicIpAddress -ErrorAction SilentlyContinue
    }
}

Describe 'Get-AzureInstanceData' {

    Context 'when Az.Compute is not installed' {
        It 'throws when Get-AzVM is unavailable' {
            InModuleScope PSCumulus {
                Mock Assert-CommandAvailable {
                    throw [System.Management.Automation.CommandNotFoundException]::new(
                        "Required command 'Get-AzVM' was not found."
                    )
                }

                { Get-AzureInstanceData -ResourceGroup 'prod-rg' } | Should -Throw
            }
        }
    }

    Context 'when VMs are returned' {
        BeforeAll {
            $script:mockVm = [pscustomobject]@{
                Name              = 'web-server-01'
                Location          = 'eastus'
                ResourceGroupName = 'prod-rg'
                VmId              = 'vm-guid-1234'
                NetworkProfile    = [pscustomobject]@{ NetworkInterfaces = @() }
                HardwareProfile   = [pscustomobject]@{ VmSize = 'Standard_D2s_v3' }
                StorageProfile    = [pscustomobject]@{
                    OsDisk = [pscustomobject]@{ OsType = 'Linux' }
                }
                Statuses          = @(
                    [pscustomobject]@{ Code = 'ProvisioningState/succeeded'; DisplayStatus = 'Provisioning succeeded' }
                    [pscustomobject]@{ Code = 'PowerState/running'; DisplayStatus = 'VM running' }
                )
            }
        }

        It 'returns a CloudRecord for each VM' {
            InModuleScope PSCumulus -Parameters @{ MockVm = $script:mockVm } {
                param($MockVm)
                Mock Assert-CommandAvailable {}
                Mock Get-AzVM { @($MockVm) }

                $results = @(Get-AzureInstanceData -ResourceGroup 'prod-rg')
                $results.Count | Should -Be 1
            }
        }

        It 'maps Name correctly' {
            InModuleScope PSCumulus -Parameters @{ MockVm = $script:mockVm } {
                param($MockVm)
                Mock Assert-CommandAvailable {}
                Mock Get-AzVM { @($MockVm) }

                $result = Get-AzureInstanceData -ResourceGroup 'prod-rg'
                $result.Name | Should -Be 'web-server-01'
            }
        }

        It 'sets Provider to Azure' {
            InModuleScope PSCumulus -Parameters @{ MockVm = $script:mockVm } {
                param($MockVm)
                Mock Assert-CommandAvailable {}
                Mock Get-AzVM { @($MockVm) }

                $result = Get-AzureInstanceData -ResourceGroup 'prod-rg'
                $result.Provider | Should -Be 'Azure'
            }
        }

        It 'maps Location to Region' {
            InModuleScope PSCumulus -Parameters @{ MockVm = $script:mockVm } {
                param($MockVm)
                Mock Assert-CommandAvailable {}
                Mock Get-AzVM { @($MockVm) }

                $result = Get-AzureInstanceData -ResourceGroup 'prod-rg'
                $result.Region | Should -Be 'eastus'
            }
        }

        It 'strips the VM prefix from the power state' {
            InModuleScope PSCumulus -Parameters @{ MockVm = $script:mockVm } {
                param($MockVm)
                Mock Assert-CommandAvailable {}
                Mock Get-AzVM { @($MockVm) }

                $result = Get-AzureInstanceData -ResourceGroup 'prod-rg'
                $result.Status | Should -Be 'Running'
            }
        }

        It 'falls back to Ready when no power state is returned' {
            InModuleScope PSCumulus {
                $vmWithoutPowerState = [pscustomobject]@{
                    Name              = 'offline-vm'
                    Location          = 'eastus'
                    ResourceGroupName = 'prod-rg'
                    VmId              = 'vm-guid-offline'
                    NetworkProfile    = [pscustomobject]@{ NetworkInterfaces = @() }
                    HardwareProfile   = [pscustomobject]@{ VmSize = 'Standard_D2s_v3' }
                    StorageProfile    = [pscustomobject]@{
                        OsDisk = [pscustomobject]@{ OsType = 'Linux' }
                    }
                    Statuses          = @(
                        [pscustomobject]@{ Code = 'ProvisioningState/succeeded'; DisplayStatus = 'Provisioning succeeded' }
                    )
                }

                Mock Assert-CommandAvailable {}
                Mock Get-AzVM { @($vmWithoutPowerState) }

                $result = Get-AzureInstanceData -ResourceGroup 'prod-rg'
                $result.Status | Should -Be 'Ready'
                $result.Metadata.PowerState | Should -BeNullOrEmpty
            }
        }

        It 'maps VmSize to Size' {
            InModuleScope PSCumulus -Parameters @{ MockVm = $script:mockVm } {
                param($MockVm)
                Mock Assert-CommandAvailable {}
                Mock Get-AzVM { @($MockVm) }

                $result = Get-AzureInstanceData -ResourceGroup 'prod-rg'
                $result.Size | Should -Be 'Standard_D2s_v3'
            }
        }

        It 'includes ResourceGroup in Metadata' {
            InModuleScope PSCumulus -Parameters @{ MockVm = $script:mockVm } {
                param($MockVm)
                Mock Assert-CommandAvailable {}
                Mock Get-AzVM { @($MockVm) }

                $result = Get-AzureInstanceData -ResourceGroup 'prod-rg'
                $result.Metadata.ResourceGroup | Should -Be 'prod-rg'
            }
        }

        It 'includes VmId in Metadata' {
            InModuleScope PSCumulus -Parameters @{ MockVm = $script:mockVm } {
                param($MockVm)
                Mock Assert-CommandAvailable {}
                Mock Get-AzVM { @($MockVm) }

                $result = Get-AzureInstanceData -ResourceGroup 'prod-rg'
                $result.Metadata.VmId | Should -Be 'vm-guid-1234'
            }
        }

        It 'returns PSCumulus.CloudRecord type' {
            InModuleScope PSCumulus -Parameters @{ MockVm = $script:mockVm } {
                param($MockVm)
                Mock Assert-CommandAvailable {}
                Mock Get-AzVM { @($MockVm) }

                $result = Get-AzureInstanceData -ResourceGroup 'prod-rg'
                $result.PSObject.TypeNames | Should -Contain 'PSCumulus.CloudRecord'
            }
        }

        It 'returns nothing when the resource group has no VMs' {
            InModuleScope PSCumulus {
                Mock Assert-CommandAvailable {}
                Mock Get-AzVM { @() }

                $results = @(Get-AzureInstanceData -ResourceGroup 'empty-rg')
                $results.Count | Should -Be 0
            }
        }

        It 'returns all VMs when ResourceGroup is empty' {
            InModuleScope PSCumulus -Parameters @{ MockVm = $script:mockVm } {
                param($MockVm)
                Mock Assert-CommandAvailable {}
                Mock Get-AzVM { @($MockVm) }

                $results = @(Get-AzureInstanceData)
                $results.Count | Should -Be 1
                Should -Invoke Get-AzVM -Times 1 -ParameterFilter { -not $ResourceGroupName }
            }
        }

        It 'includes VM tags in the Tags property' {
            InModuleScope PSCumulus {
                $vmWithTags = [pscustomobject]@{
                    Name              = 'tagged-vm'
                    Location          = 'eastus'
                    ResourceGroupName = 'prod-rg'
                    VmId              = 'vm-guid-tagged'
                    NetworkProfile    = [pscustomobject]@{ NetworkInterfaces = @() }
                    HardwareProfile   = [pscustomobject]@{ VmSize = 'Standard_D2s_v3' }
                    StorageProfile    = [pscustomobject]@{
                        OsDisk = [pscustomobject]@{ OsType = 'Linux' }
                    }
                    Tags              = @{ environment = 'prod'; team = 'platform' }
                    Statuses          = @(
                        [pscustomobject]@{ Code = 'PowerState/running'; DisplayStatus = 'VM running' }
                    )
                }
                Mock Assert-CommandAvailable {}
                Mock Get-AzVM { @($vmWithTags) }

                $result = Get-AzureInstanceData -ResourceGroup 'prod-rg'
                $result.Tags['environment'] | Should -Be 'prod'
                $result.Tags['team'] | Should -Be 'platform'
            }
        }

        It 'includes private and public IPs when network data is available' {
            InModuleScope PSCumulus {
                $vmWithNetwork = [pscustomobject]@{
                    Name              = 'networked-vm'
                    Location          = 'eastus'
                    ResourceGroupName = 'prod-rg'
                    VmId              = 'vm-guid-network'
                    NetworkProfile    = [pscustomobject]@{
                        NetworkInterfaces = @([pscustomobject]@{ Id = '/subscriptions/sub/resourceGroups/prod-rg/providers/Microsoft.Network/networkInterfaces/nic-1' })
                    }
                    HardwareProfile   = [pscustomobject]@{ VmSize = 'Standard_D2s_v3' }
                    StorageProfile    = [pscustomobject]@{
                        OsDisk = [pscustomobject]@{ OsType = 'Linux' }
                    }
                    Statuses          = @(
                        [pscustomobject]@{ Code = 'PowerState/running'; DisplayStatus = 'VM running' }
                    )
                }

                $networkInterface = [pscustomobject]@{
                    IpConfigurations = @([pscustomobject]@{
                        Primary          = $true
                        PrivateIpAddress = '10.0.1.4'
                        PublicIpAddress  = [pscustomobject]@{ Id = '/subscriptions/sub/resourceGroups/prod-rg/providers/Microsoft.Network/publicIPAddresses/pip-1' }
                    })
                }

                Mock Assert-CommandAvailable {}
                Mock Get-AzVM { @($vmWithNetwork) }
                Mock Get-AzNetworkInterface { $networkInterface }
                Mock Get-AzPublicIpAddress { [pscustomobject]@{ IpAddress = '52.160.1.10' } }

                $result = Get-AzureInstanceData -ResourceGroup 'prod-rg'
                $result.PrivateIpAddress | Should -Be '10.0.1.4'
                $result.PublicIpAddress | Should -Be '52.160.1.10'
            }
        }

        It 'leaves IPs null when network data is unavailable' {
            InModuleScope PSCumulus {
                $vmWithoutNetwork = [pscustomobject]@{
                    Name              = 'bare-vm'
                    Location          = 'eastus'
                    ResourceGroupName = 'prod-rg'
                    VmId              = 'vm-guid-bare'
                    HardwareProfile   = [pscustomobject]@{ VmSize = 'Standard_D2s_v3' }
                    StorageProfile    = [pscustomobject]@{
                        OsDisk = [pscustomobject]@{ OsType = 'Linux' }
                    }
                    Statuses          = @(
                        [pscustomobject]@{ Code = 'PowerState/running'; DisplayStatus = 'VM running' }
                    )
                }

                Mock Assert-CommandAvailable {}
                Mock Get-AzVM { @($vmWithoutNetwork) }

                $result = Get-AzureInstanceData -ResourceGroup 'prod-rg'
                $result.PrivateIpAddress | Should -BeNullOrEmpty
                $result.PublicIpAddress | Should -BeNullOrEmpty
            }
        }

        It 'filters by Name when provided' {
            InModuleScope PSCumulus -Parameters @{ MockVm = $script:mockVm } {
                param($MockVm)
                $otherVm = [pscustomobject]@{
                    Name              = 'other-vm'
                    Location          = 'eastus'
                    ResourceGroupName = 'prod-rg'
                    VmId              = 'vm-guid-other'
                    HardwareProfile   = [pscustomobject]@{ VmSize = 'Standard_D2s_v3' }
                    StorageProfile    = [pscustomobject]@{
                        OsDisk = [pscustomobject]@{ OsType = 'Linux' }
                    }
                    Statuses          = @(
                        [pscustomobject]@{ Code = 'PowerState/running'; DisplayStatus = 'VM running' }
                    )
                }
                Mock Assert-CommandAvailable {}
                Mock Get-AzVM { @($MockVm, $otherVm) }

                $results = @(Get-AzureInstanceData -ResourceGroup 'prod-rg' -Name 'web-server-01')
                $results.Count | Should -Be 1
                $results[0].Name | Should -Be 'web-server-01'
            }
        }
    }
}