Tests/AzurePSDrive.Tests.ps1

# Pester tests for AzurePSDrive PowerShell Provider
# 'Describe' sections are organized by the flow of the provider
# Ex: Drive:\Subscription\ResourceGroup\ResourceProvider\ResourceType
# Note: There is a one time initialization of all Compute/Network/Storage resouces in Azure which is used by the Test Suite

param (
    [string]$subscriptionName = 'AutomationTeam'
)

$script:Az_Profile = 'Az.Accounts'
$script:Az_Resources = 'Az.Resources'
$script:Az_Compute = 'Az.Compute'
$script:Az_Network = 'Az.Network'
$script:Az_Storage = 'Az.Storage'

function New-PartialGuidForName { ([guid]::NewGuid().ToString() -replace '-','')[0..9] -join '' }

#region Script variables
$resourceGroupName = "rgapsdt$(New-PartialGuidForName)"
$location = 'WestUS'
$storageAccountName = "saapsdt$(New-PartialGuidForName)"
$skuName = 'Standard_LRS'
$interfaceName = 'TestInterface'
$subnetName = 'TestSubnet1'
$vnetName = 'TestVNet1'
$vnetAddressPrefix = '10.0.0.0/16'
$vnetSubnetAddressPrefix = '10.0.0.0/24'
$vmName = 'TestVM'
$computerName = 'TestServer'
$adminUserName = 'localadmin'
$vmSize = 'Standard_A2'
$osDiskName = $VMName + "OSDisk"
#endregion

#region Utility
# Verify that dependent modules required by the test are available in current session
function Test-Dependency
{
    if ((-not (Get-Module -Name $script:Az_Profile)) `
    -or (-not (Get-Module -Name $script:Az_Resources)) `
    -or (-not (Get-Module -Name $script:Az_Compute)) `
    -or (-not (Get-Module -Name $script:Az_Network)) `
    -or (-not (Get-Module -Name $script:Az_Storage)) `
    -or (-not (Get-Module -Name SHiPS)) `
    -or (-not (Get-Module -Name AzurePSDrive)))
    {
        throw "Ensure $script:Az_Profile, $script:Az_Resources, $script:Az_Compute, $script:Az_Network, $script:Az_Storage, SHiPS, AzurePSDrive modules are installed"
    }
}

# Create AzurePSDrive PowerShell Drive
function New-AzureDrive
{   
    $driveName = 'Azure'
    Remove-PSDrive $driveName -ErrorAction SilentlyContinue    
    New-PSDrive -Name $driveName -PSProvider SHiPS -Root AzurePSDrive#Azure -Scope Global -ErrorAction Stop
}

# Initialize ResourceGroup and other Azure deployments
# One time setup in Azure
function Initialize-AzureTestResource
{    
    Write-Verbose "Creating the resources used by the tests..."
    # ResourceGroup
    $rg = & $script:Az_Resources\Get-AzResourceGroup -Name $resourceGroupName -ErrorAction SilentlyContinue
    if ($rg -eq $null)
    {
        $rg = & $script:Az_Resources\New-AzResourceGroup -Name $resourceGroupName -Location $location -Force -Verbose
    }

    #Storage
    $storage = & $script:Az_Storage\Get-AzStorageAccount -Name $storageAccountName -ResourceGroupName $resourceGroupName -ErrorAction SilentlyContinue
    if ($storage -eq $null)
    {
        $storage = & $script:Az_Storage\New-AzStorageAccount -Name $storageAccountName -ResourceGroupName $resourceGroupName -Location $location -SkuName $skuName -Verbose
    }

    #Network
    $interface = & $script:Az_Network\Get-AzNetworkInterface -Name $interfaceName -ResourceGroupName $resourceGroupName -ErrorAction SilentlyContinue
    if ($interface -eq $null)
    {
        $pubIp = & $script:Az_Network\New-AzPublicIpAddress -Name $interfaceName -ResourceGroupName $resourceGroupName -Location $location -AllocationMethod Dynamic -Force -Verbose
        $subnetConfig = & $script:Az_Network\New-AzVirtualNetworkSubnetConfig -Name $subnetName -AddressPrefix $vnetSubnetAddressPrefix -Verbose
        $vnet = & $script:Az_Network\New-AzVirtualNetwork -Name $vnetName -ResourceGroupName $resourceGroupName -Location $location -AddressPrefix $vnetAddressPrefix -Subnet $subnetConfig -Force -Verbose
        $interface = & $script:Az_Network\New-AzNetworkInterface -Name $interfaceName -ResourceGroupName $resourceGroupName -Location $location -SubnetId $vnet.Subnets[0].Id -PublicIpAddressId $pubIp.Id -Force -Verbose
    }
        
    #Compute - VM
    $virtualMachine = & $script:Az_Compute\Get-AzVM -Name $vmName -ResourceGroupName $resourceGroupName -ErrorAction SilentlyContinue
    if ($virtualMachine -eq $null)
    {
        $password = "TestAsdf1234!!!" | ConvertTo-SecureString -asPlainText -Force
        $credential = $credential = New-Object System.Management.Automation.PSCredential($adminUserName,$password)
        $virtualMachine = & $script:Az_Compute\New-AzVMConfig -VMName $vmName -VMSize $vmSize
        $virtualMachine = & $script:Az_Compute\Set-AzVMOperatingSystem -VM $virtualMachine -Windows -ComputerName $computerName -Credential $credential -ProvisionVMAgent -EnableAutoUpdate
        $virtualMachine = & $script:Az_Compute\Set-AzVMSourceImage -VM $virtualMachine -PublisherName MicrosoftWindowsServer -Offer WindowsServer -Skus 2012-R2-Datacenter -Version "latest"
        $virtualMachine = & $script:Az_Compute\Add-AzVMNetworkInterface -VM $virtualMachine -Id $interface.Id
        $osDiskUri = $storage.PrimaryEndpoints.Blob.ToString() + "vhds/" + $osDiskName + ".vhd"
        $virtualMachine = & $script:Az_Compute\Set-AzVMOSDisk -VM $virtualMachine -Name $osDiskName -VhdUri $osDiskUri -CreateOption FromImage    

        #Create the VM in Azure
        & $script:Az_Compute\New-AzVM -ResourceGroupName $resourceGroupName -Location $location -VM $virtualMachine
    }
    Write-Verbose "Resources created."
}
#endregion

# Remove ResourceGroup and other Azure deployments
# One time teardown in Azure
function Remove-AzureTestResource
{
    Write-Verbose "Deleting the resources used by the tests..."
    # ResourceGroup
    $rg = & $script:Az_Resources\Get-AzResourceGroup -Name $resourceGroupName -ErrorAction SilentlyContinue
    if ($rg -ne $null)
    {
        $rg | & $script:Az_Resources\Remove-AzResourceGroup -Force
    }
    Write-Verbose "Resources deleted."
}
#endregion

#region Test Suite Initialization
cd $PSScriptRoot
Test-Dependency
Initialize-AzureTestResource
New-AzureDrive
#endregion

#region Get-Subscription Tests
Describe Get-Subscription {
    BeforeAll {
        cd Azure:
    }

    It "Retrieving a valid Azure Subscription using the provider" {
        $sub = dir

        # Only one subscription corresponding to the service principal and tenant must be returned
        $sub.Count | Should Be 1
        $sub.Name | Should Be $subscriptionName
        $sub.SubscriptionName | Should Be $subscriptionName

        # Indicates that this is a DirectoryType object
        $sub.SSItemMode | Should Be '+'
        $sub.PSDrive | Should Be 'Azure'
        $sub.SubscriptionId | Should not BeNullOrEmpty
        $sub.TenantId | Should not BeNullOrEmpty
        $sub.State | Should Be 'Enabled'
    }


    It "Retrieving an invalid subscription" {        

        try
        {
            dir invalidSubscription -ErrorAction Stop
        }
        catch
        {            
            $_.Exception.GetType().Name | Should Be 'ItemNotFoundException'
        }
    }

    It "Using Filter parameter with Force in Subscription" {
        $subNameWild = "$($subscriptionName[0])*$($subscriptionName.Substring($subscriptionName.Length - 1))"
        $sub = dir -Force -Filter $subNameWild

        # Only one subscription corresponding to specified Filter must be returned
        $sub.Count | Should Be 1
        $sub.Name | Should Be $subscriptionName
        
    }

    It "Using non-existant Filter with Force in Subscription" {
        $sub = dir -Force -Filter Invalid*

        # None must be returned since supplied filter is non-existant
        $sub | Should BeNullOrEmpty
        
    }
    
    AfterAll {
        Set-Location $PSScriptRoot
    }
}
#endregion

#region Get-ResourceGroup Tests
Describe Get-ResourceGroup {
    BeforeAll {
        cd "Azure:\$subscriptionName\ResourceGroups"
    }

    It "Retrieving a ResourceGroup in the subscription using Force switch and wild card" {        
        $rgNameWild = "$($resourceGroupName[0])*$($resourceGroupName.Substring(2, $resourceGroupName.Length - 4))*$($resourceGroupName.Substring($resourceGroupName.Length - 1))"
        $rg = dir $rgNameWild -Force       
        $rg.Count | Should Be 1
        
        # Indicates that this is a DirectoryType object
        $rg.SubscriptionId | Should not BeNullOrEmpty
        $rg.ResourceGroupName | Should Be $resourceGroupName
        $rg.Name | Should Be $resourceGroupName
        $rg.Location | Should Be 'westus'
        $rg.ProvisioningState | Should Be 'Succeeded'
    }

    It "Retrieving a ResourceGroup in the subscription using wildcard in name" {
        $rgNameWild = "$($resourceGroupName[0])*$($resourceGroupName.Substring(2, $resourceGroupName.Length - 4))*$($resourceGroupName.Substring($resourceGroupName.Length - 1))"
        $rg = dir $rgNameWild
        $rg.Count | Should Be 1
        
        # Indicates that this is a DirectoryType object
        $rg.SSItemMode | Should Be '+'
        $rg.PSDrive | Should Be 'Azure'
        $rg.SubscriptionId | Should not BeNullOrEmpty
        $rg.ResourceGroupName | Should Be $resourceGroupName
        $rg.Name | Should Be $resourceGroupName
        $rg.Location | Should Be 'westus'
        $rg.ProvisioningState | Should Be 'Succeeded'
    }


    It "Retrieving an invalid ResourceGroup" {        

        try
        {
            dir InvalidRG -ErrorAction Stop
        }
        catch
        {            
            $_.Exception.GetType().Name | Should Be 'ItemNotFoundException'
        }
    }

    It "Using server supported ODataQuery Filter parameter in ResourceGroup" {
        # BUG: Using -Force in when retrieving OData filtered ResourceGroups results in an error
        # Using -Filter results in using ODataQuery based server-side filterring
        $rgNameWild = "$($resourceGroupName.Substring(0, $resourceGroupName.Length - 5))*"
        $rg = dir -Filter $rgNameWild

        # Only one ResourceGroup corresponding to specified Filter must be returned
        $rg.Count | Should Be 1
        $rg.Name | Should Be $resourceGroupName
        
    }

    It "Using non-existant Filter in ResourceGroup" {
        $rgNameWild = "$($resourceGroupName[0])$($resourceGroupName[2])$($resourceGroupName[3])$($resourceGroupName[5])*"
        $rg = dir -Filter $rgNameWild

        # None must be returned since supplied filter is non-existant
        $rg | Should BeNullOrEmpty
        
    }
    
    AfterAll {
        Set-Location $PSScriptRoot
    }
}
#endregion

<# Failing Test - Commenting to investigate
#region ResourceGroup with PSDefaultParameterValues Tests
Describe ResourceGroupPSDefaultParameterValues {
    BeforeAll {
        cd "Azure:\$subscriptionName\ResourceGroups"
    }

    It "Retrieving VM in a specific resourceGroup with PSDefaultParameterValues" {
        $vmInAllResourceGroup = & $script:Az_Compute\Get-AzVM
        
        # Navigate to a specific ResourceGroup
        cd "$resourceGroupName"
        $vmInSpecificResourceGroup = & $script:Az_Compute\Get-AzVM

        # Since PSDefaultParameterValues are passed to Get-AzVM when in a particular ResourceGroup
        # results are filtered to the specific ResourceGroup
        ($vmInSpecificResourceGroup.Count -lt $vmInAllResourceGroup.Count) | Should Be $true
    }
    
    AfterAll {
        Set-Location $PSScriptRoot
    }
}
#endregion
#>


#region Get-ResourceProvider Tests
Describe Get-ResourceProvider {
    BeforeAll {
        cd "Azure:\$subscriptionName\ResourceGroups\$resourceGroupName"
    }

    It "Retrieving ResourceProviders in the ResourceGroup" {
        $providers = dir

        # AzurePSDrive provider does post-processing on the retrieved providers from Azure to eliminate duplicates
        # Hence count must be 3 - show only unique providers that have deployments
        $providers.Count | Should Be 3

        # Only following providers must be returned, since we initialized only these in 'Initialize-AzureTestResource'
        $expected = @('Microsoft.Compute', 'Microsoft.Network', 'Microsoft.Storage')
        $actual = @()
        foreach ($provider in $providers)
        {
            $actual += $provider.Name
        }
        $diff = Compare-Object -ReferenceObject $expected -DifferenceObject $actual -PassThru
        $diff | Should BeNullOrEmpty       
        
    }


    It "Retrieving an invalid Azure ResourceProvider" {        

        try
        {
            dir InvalidProvider -ErrorAction Stop
        }
        catch
        {            
            $_.Exception.GetType().Name | Should Be 'ItemNotFoundException'
        }
    }

    It "Using Filter parameter in ResourceProvider with Force switch" {        
        $provider = dir -Filter *Compute* -Force

        # Only one ResourceGroup corresponding to specified Filter must be returned
        $provider.Count | Should Be 1
        $provider.Name | Should Be 'Microsoft.Compute'
        
    }

    It "Using non-existant Filter in ResourceProvider with Force switch" {
        $provider = dir -Filter DoesNotExist*

        # None must be returned since supplied filter is non-existant
        $provider | Should BeNullOrEmpty
        
    }
    
    AfterAll {
        Set-Location $PSScriptRoot
    }
}
#endregion

#region Get-ResourceType Tests
Describe Get-ResourceType {
    BeforeAll {        
    }

    It "Retrieving ResourceTypes in the ResourceType with and without Force switch" {

        # Verify Compute Type
        cd "Azure:\$subscriptionName\ResourceGroups\$resourceGroupName\Microsoft.Compute"
        $resourceTypes = dir               
        $resourceTypes.Count | Should Be 2

        # Only following resourceTypes must be returned, since we initialized only these in 'Initialize-AzureTestResource'
        $expected = @('virtualMachines', 'virtualMachines-extensions')
        $actual = @()
        foreach ($resourceType in $resourceTypes)
        {
            $actual += $resourceType.Name
        }
        $diff = Compare-Object -ReferenceObject $expected -DifferenceObject $actual -PassThru
        $diff | Should BeNullOrEmpty        
        
        # Verify Network Type
        cd "Azure:\$subscriptionName\ResourceGroups\$resourceGroupName\Microsoft.Network"
        $resourceTypes = dir -Force              
        
        # In certain Subscriptions, depending on the settings, networksecuritygroups is also returned as a ResourceGroup
        $resourceTypes.Count | Should BeGreaterThan 2
        $resourceTypes.Count | Should BeLessThan 5

        # Only following resourceTypes must be returned, since we initialized only these in 'Initialize-AzureTestResource'
        $expected = @('networkInterfaces', 'publicIPAddresses', 'virtualNetworks')
        $actual = @()
        foreach ($resourceType in $resourceTypes)
        {
            $actual += $resourceType.Name
        }
        $diff = Compare-Object -ReferenceObject $expected -DifferenceObject $actual -PassThru
        
        # In certain Subscriptions, depending on the settings, networksecuritygroups is also returned as a ResourceGroup
        # So the diff can be null/empty OR can contain 'networkSecurityGroups'
        (($null -eq $diff ) -or (1 -eq $diff.Count)) | Should Be $true
        
    }


    It "Retrieving an invalid Azure ResourceType" {        

        try
        {
            dir InvalidResourceType -ErrorAction Stop
        }
        catch
        {            
            $_.Exception.GetType().Name | Should Be 'ItemNotFoundException'
        }
    }

    It "Using Filter parameter in ResourceType with Force switch" {
            
        # Verify Storage Type
        cd "Azure:\$subscriptionName\ResourceGroups\$resourceGroupName\Microsoft.Storage"
        $resourceType = dir -Filter *Storage* -Force

        # Only one ResourceType corresponding to specified Filter must be returned
        $resourceType.Count | Should Be 1
        $resourceType.Name | Should Be 'storageAccounts'
        $resourceType.resourceType | Should Be 'Microsoft.Storage/storageAccounts'
        $resourceType.resourceGroupName | Should Be $resourceGroupName
        $resourceType.providerNamespace | Should Be 'Microsoft.Storage'
        
    }

    It "Using non-existant Filter in ResourceType with Force switch" {
        $resourceType = dir -Filter DoesNotExist* -Force

        # None must be returned since supplied filter is non-existant
        $resourceType | Should BeNullOrEmpty
        
    }
    
    AfterAll {
        Set-Location $PSScriptRoot
    }
}
#endregion

#region Get-SpecificRMResourceType Tests
Describe Get-SpecificRMResourceType {
    BeforeAll {        

    }

    It "Retrieving VM type using the Provider" {

        cd "Azure:\$subscriptionName\ResourceGroups\$resourceGroupName\Microsoft.Compute\virtualMachines"

        $vm = dir

        # Validate all properties - Ensure they match the ones used during resource creation
        $vm.ResourceName | Should Be $VMName
        $vm.Name | Should Be $VMName
        $vm.ResourceType | Should Be 'Microsoft.Compute/virtualMachines'
        $vm.Location | Should Be $location
        $vm.Properties.hardwareProfile.vmSize | Should Be $vmSize
        $vm.Properties.osProfile.computerName | Should Be $computerName
        $vm.Properties.osProfile.adminUsername | Should Be $adminUserName
        $vm.Properties.networkProfile.networkInterfaces.Count | Should Be 1
        $vm.Properties.provisioningState | Should Be 'Succeeded'
        
    }

    It "Retrieving Network type using the Provider" {

        cd "Azure:\$subscriptionName\ResourceGroups\$resourceGroupName\Microsoft.Network\networkInterfaces"

        $network = dir

        # Validate all properties - Ensure they match the ones used during resource creation
        $network.ResourceName | Should Be $interfaceName
        $network.Name | Should Be $interfaceName
        $network.ResourceType | Should Be 'Microsoft.Network/networkInterfaces'
        $network.Location | Should Be $location
          
        $subnet = $network.Properties.ipConfigurations[0].properties.subnet.id
        $subnet.Contains($subnetName) | Should Be $true
        $subnet.Contains($vnetName) | Should Be $true        
        
        $publicIPAddress = $network.Properties.ipConfigurations[0].properties.publicIPAddress.id
        $publicIPAddress.Contains($interfaceName) | Should Be $true

        $network.Properties.provisioningState | Should Be 'Succeeded'
        $network.Properties.primary | Should Be 'True'
        
    }

      It "Retrieving Storage type using the Provider" {

        cd "Azure:\$subscriptionName\ResourceGroups\$resourceGroupName\Microsoft.Storage\storageAccounts"
        $storage = dir

        # Validate all properties - Ensure they match the ones used during resource creation
        $storage.ResourceName | Should Be $storageAccountName
        $storage.Name | Should Be $storageAccountName
        $storage.ResourceType | Should Be 'Microsoft.Storage/storageAccounts'
        $storage.Location | Should Be $location
          
        $blob = $storage.Properties.primaryEndpoints.blob
        $blob.Contains($storageAccountName) | Should Be $true

        $file = $storage.Properties.primaryEndpoints.file
        $file.Contains($storageAccountName) | Should Be $true

        $queue = $storage.Properties.primaryEndpoints.queue
        $queue.Contains($storageAccountName) | Should Be $true
                
        $storage.Properties.provisioningState | Should Be 'Succeeded'        
        
    }
    
    AfterAll {
        Set-Location $PSScriptRoot
    }
}
#endregion

#region Get-AllResources with Recurse functionality Tests
Describe Get-AllResourcesWithRecurse {
    BeforeAll {     
       cd "Azure:\$subscriptionName\ResourceGroups\$resourceGroupName"
    }

    It "Retrieving all resources with Recurse switch from ResourceGroup top level" {

        $allResources = dir -Recurse -Force
        # There are resources deployed in Azure as part of 'Initialize-AzureTestResource'
        # This includes Storage, Network, Compute resources
        # Depending on the subscription this number varies, but min is 5 since we are deploying atleast one of each
        $allResources.Count | Should BeGreaterThan 5
        
    }    
    
    AfterAll {
        Set-Location $PSScriptRoot
    }
}
#endregion

#region AllResources, VMs, StorageAccounts, and Webapps tests
Describe "Get AllResource, VMs, StorageAccounts and Webapps" {
    BeforeAll {     
        & $script:Az_Profile\Disable-AzDataCollection
        cd "Azure:\$subscriptionName\"
    }

    It "Testing childitems under a subscription" {
        cd "Azure:\$subscriptionName\"
        $a = dir

        $a | ?{ $_.name -eq "AllResources" } | should not BeNullOrEmpty  
        $a | ?{ $_.name -eq "ResourceGroups" } | should not BeNullOrEmpty  
        $a | ?{ $_.name -eq "VirtualMachines" } | should not BeNullOrEmpty  
        $a | ?{ $_.name -eq "StorageAccounts" } | should not BeNullOrEmpty  
        $a | ?{ $_.name -eq "WebApps" } | should not BeNullOrEmpty  
        
    }    
    
    It "Retrieving all resources" {
        cd "Azure:\$subscriptionName\AllResources"
        $a = dir

        # Ensure it is not 0
        $a.Count | Should Not Be 0
    }
    
    It "Retrieving all VirtualMachines" {
        cd "Azure:\$subscriptionName\VirtualMachines"
        
        $a = dir

        # Ensure it is not 0
        $a.Count | Should Not Be 0
    }    
    
    It "Retrieving all StorageAccounts" {
        cd "Azure:\$subscriptionName\StorageAccounts"
        
        # shipsAztest is azurepsdriveteststorage, AzurePSDrive.Test is resourcegroup
        $a = dir

        # Ensure it is not 0
        $a.Count | Should Not Be 0
        
        cd .\$storageAccountName\
        
        $b=dir
        $b | ?{ $_.name -eq "Blobs" } | should not BeNullOrEmpty  
        $b | ?{ $_.name -eq "Files" } | should not BeNullOrEmpty  
        $b | ?{ $_.name -eq "Tables" } | should not BeNullOrEmpty  
        $b | ?{ $_.name -eq "Queues" } | should not BeNullOrEmpty  

        cd .\Blobs\
        $c=dir
        $c | ?{ $_.name -eq "vhds" } | should not BeNullOrEmpty  

        cd vhds
        $d=dir
        $d | Should Not BeNullOrEmpty

        cd "Azure:\$subscriptionName\StorageAccounts\$storageAccountName\Files"
        $e=dir
        $e | Should BeNullOrEmpty

        cd "Azure:\$subscriptionName\StorageAccounts\$storageAccountName\Tables"
        $g=dir
        $g | Should BeNullOrEmpty

        cd "Azure:\$subscriptionName\StorageAccounts\$storageAccountName\Queues"
        $h=dir
        $h | Should BeNullOrEmpty
    } 
     
    AfterAll {
        Set-Location $PSScriptRoot
    }
}
#endregion

Remove-AzureTestResource