Tests/Unit/MSFT_AdfsFarm.tests.ps1

$Global:DSCModuleName = 'AdfsDsc'
$Global:PSModuleName = 'ADFS'
$Global:DscResourceFriendlyName = 'AdfsFarm'
$Global:DSCResourceName = "MSFT_$Global:DscResourceFriendlyName"

$moduleRoot = Split-Path -Parent (Split-Path -Parent (Split-Path -Parent $Script:MyInvocation.MyCommand.Path))
if ( (-not (Test-Path -Path (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests'))) -or `
    (-not (Test-Path -Path (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) )
{
    & git @('clone', 'https://github.com/PowerShell/DscResource.Tests.git',
        (Join-Path -Path $moduleRoot -ChildPath '\DSCResource.Tests\'))
}

Import-Module (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force

$TestEnvironment = Initialize-TestEnvironment `
    -DSCModuleName $Global:DSCModuleName `
    -DSCResourceName $Global:DSCResourceName `
    -TestType Unit

try
{
    InModuleScope $Global:DSCResourceName {
        # Import Stub Module
        Import-Module (Join-Path -Path $PSScriptRoot -ChildPath "Stubs\$($Global:PSModuleName)Stub.psm1") -Force

        # Define Resource Commands
        $ResourceCommand = @{
            Get       = 'Get-AdfsConfigurationStatus'
            Install   = 'Install-AdfsFarm'
        }

        $mockUserName = 'CONTOSO\SvcAccount'
        $mockPassword = 'DummyPassword'

        $mockCredential = New-Object -TypeName 'System.Management.Automation.PSCredential' -ArgumentList @(
            $mockUserName,
            (ConvertTo-SecureString -String $mockPassword -AsPlainText -Force)
        )

        $mockMSFTCredential = New-CimCredentialInstance -UserName $mockUserName

        $sqlConnectionString = 'TBC'

        $mockResource = @{
            FederationServiceName         = 'sts.contoso.com'
            FederationServiceDisplayName  = 'Contoso ADFS Service'
            CertificateThumbprint         = '6F7E9F5543505B943FEEA49E651EDDD8D9D45011'
            SQLConnectionString           = $SQLConnectionString
            Ensure                        = 'Present'
        }

        $mockGsaResource = $mockResource.Clone()
        $mockGsaResource += @{
            GroupServiceAccountIdentifier = 'CONTOSO\AdfsGmsa'
            ServiceAccountCredential      = $null
        }

        $mockSaResource = $mockResource.Clone()
        $mockSaResource += @{
            GroupServiceAccountIdentifier = $null
            ServiceAccountCredential      = $mockMSFTCredential
        }

        $mockAbsentResource = @{
            FederationServiceName         = $mockResource.FederationServiceName
            CertificateThumbprint         = $mockResource.CertificateThumbprint
            FederationServiceDisplayName  = $null
            GroupServiceAccountIdentifier = $null
            ServiceAccountCredential      = $null
            SQLConnectionString           = $null
            Ensure                        = 'Absent'
        }

        $mockGetTargetResourceResult = @{
            FederationServiceName         = $mockGsaResource.FederationServiceName
            FederationServiceDisplayName  = $mockGsaResource.FederationServiceDisplayName
            CertificateThumbprint         = $mockGsaResource.CertificateThumbprint
            ServiceAccountCredential      = $mockGsaResource.ServiceAccountCredential
            GroupServiceAccountIdentifier = $mockGsaResource.GroupServiceAccountIdentifier
            SQLConnectionString           = $mockGsaResource.SQLConnectionString
        }

        $mockGetTargetResourcePresentResult = $mockGetTargetResourceResult.Clone()
        $mockGetTargetResourcePresentResult.Ensure = 'Present'

        $mockGetTargetResourceAbsentResult = $mockGetTargetResourceResult.Clone()
        $mockGetTargetResourceAbsentResult.Ensure = 'Absent'

        Describe "$Global:DSCResourceName\Get-TargetResource" -Tag 'Get' {
            BeforeAll {
                $getTargetResourceParameters = @{
                    FederationServiceName = $mockResource.FederationServiceName
                    CertificateThumbprint = $mockResource.CertificateThumbprint
                    Credential            = $mockCredential
                }

                $mockGetAdfsSslCertificateResult = @(
                    @{
                        Hostname        = 'sts.contoso.com'
                        PortNumber      = 443
                        CertificateHash = $mockResource.CertificateThumbprint
                    }
                )

                $mockGetAdfsPropertiesResult = @{
                    HostName    = $mockResource.FederationServiceName
                    DisplayName = $mockResource.FederationServiceDisplayName
                }

                $mockGetCimInstanceServiceGsaRunningResult = @{
                    State     = 'Running'
                    StartName = $mockGsaResource.GroupServiceAccountIdentifier
                }

                $mockGetCimInstanceServiceSaRunningResult = @{
                    State     = 'Running'
                    StartName = $mockSaResource.ServiceAccountCredential.UserName
                }

                $mockGetCimInstanceSecurityTokenServiceResult = @{
                    ConfigurationDatabaseConnectionString = $sqlConnectionString
                }

                $mockExceptionErrorMessage = 'UnknownException'
                $mockException = New-Object -TypeName 'System.Exception' -ArgumentList $mockExceptionErrorMessage
                $mockErrorRecord = New-Object -TypeName 'System.Management.Automation.ErrorRecord' `
                    -ArgumentList @($mockException, $null, 'InvalidOperation', $null)

                Mock -CommandName Assert-Module
                Mock -CommandName Assert-DomainMember
                Mock -CommandName "Assert-$($Global:PSModuleName)Service"

                Mock -CommandName Get-CimInstance `
                    -ParameterFilter { $ClassName -eq 'Win32_Service' } `
                    -MockWith { $mockGetCimInstanceServiceGsaRunningResult }
                Mock -CommandName Get-CimInstance `
                    -ParameterFilter { `
                        $Namespace -eq 'root/ADFS' -and `
                        $ClassName -eq 'SecurityTokenService' } `
                    -MockWith { $mockGetCimInstanceSecurityTokenServiceResult }
                Mock -CommandName Get-AdfsSslCertificate -MockWith { $mockGetAdfsSslCertificateResult }
                Mock -CommandName Get-AdfsProperties -MockWith { $mockGetAdfsPropertiesResult }
                Mock -CommandName Assert-GroupServiceAccount -MockWith { $true }
            }

            Context "When the $($Global:DscResourceFriendlyName) Resource is Configured" {
                BeforeAll {
                    Mock -CommandName $ResourceCommand.Get -MockWith { 'Configured' }

                    $result = Get-TargetResource @getTargetResourceParameters
                }

                foreach ($property in $mockGsaResource.Keys)
                {
                    It "Should return the correct $property property" {
                        $result.$property | Should -Be $mockGsaResource.$property
                    }
                }

                It 'Should call the expected mocks' {
                    Assert-MockCalled -CommandName Assert-Module `
                        -ParameterFilter { $ModuleName -eq $Global:PSModuleName } `
                        -Exactly -Times 1
                    Assert-MockCalled -CommandName Assert-DomainMember -Exactly -Times 1
                    Assert-MockCalled -CommandName "Assert-$($Global:PSModuleName)Service" -Exactly -Times 1
                    Assert-MockCalled -CommandName $ResourceCommand.Get -Exactly -Times 1
                    Assert-MockCalled -CommandName Get-CimInstance `
                        -ParameterFilter {
                        $ClassName -eq 'Win32_Service' -and `
                            $Filter -eq "Name='$script:AdfsServiceName'" } `
                        -Exactly -Times 1
                    Assert-MockCalled -CommandName Get-CimInstance `
                        -ParameterFilter {
                        $Namespace -eq 'root/ADFS' -and `
                            $ClassName -eq 'SecurityTokenService' } `
                        -Exactly -Times 1
                    Assert-MockCalled -CommandName Get-CimInstance `
                        -ParameterFilter { $Filter -eq "Name='$script:AdfsServiceName'" } `
                        -Exactly -Times 1
                    Assert-MockCalled -CommandName Get-AdfsSslCertificate -Exactly -Times 1
                    Assert-MockCalled -CommandName Get-AdfsProperties -Exactly -Times 1
                    Assert-MockCalled -CommandName Assert-GroupServiceAccount -Exactly -Times 1
                }

                Context 'When Get-AdfsSslCertificate throws an exception' {
                    BeforeAll {
                        Mock Get-AdfsSslCertificate -MockWith { throw $mockExceptionErrorMessage }
                    }

                    It 'Should throw the correct exception' {
                        { Get-TargetResource @getTargetResourceParameters } | Should -Throw (
                            $script:localizedData.GettingAdfsSslCertificateError -f
                            $mockResource.FederationServiceName)
                    }
                }

                Context 'When Get-AdfsSslCertificate returns an empty result' {
                    BeforeAll {
                        Mock Get-AdfsSslCertificate
                    }

                    It 'Should throw the correct exception' {
                        { Get-TargetResource @getTargetResourceParameters } | Should -Throw (
                            $script:localizedData.GettingAdfsSslCertificateError -f
                            $mockResource.FederationServiceName)
                    }
                }

                Context 'When Get-CimInstance -ClassName Win32_Service returns an empty result' {
                    BeforeAll {
                        Mock -CommandName Get-CimInstance `
                            -ParameterFilter { $ClassName -eq 'Win32_Service' }
                    }

                    It 'Should throw the correct exception' {
                        { Get-TargetResource @getTargetResourceParameters } | Should -Throw (
                            $script:localizedData.GettingAdfsServiceError -f
                            $mockResource.FederationServiceName)
                    }
                }

                Context 'When the Service Account is not a group managed service account' {
                    BeforeAll {
                        Mock -CommandName Get-CimInstance `
                            -ParameterFilter { $ClassName -eq 'Win32_Service' } `
                            -MockWith { $mockGetCimInstanceServiceSaRunningResult }
                        Mock -CommandName Assert-GroupServiceAccount -MockWith { $false }

                        $result = Get-TargetResource @getTargetResourceParameters
                    }

                    foreach ($property in $mockSaResource.Keys)
                    {
                        if ($property -eq 'ServiceAccountCredential')
                        {
                            It "Should return the correct $property property" {
                                $result.ServiceAccountCredential.UserName | Should -Be $mockSaResource.ServiceAccountCredential.UserName
                            }
                        }
                        else
                        {
                            It "Should return the correct $property property" {
                                $result.$property | Should -Be $mockSaResource.$property
                            }
                        }
                    }
                }

                Context 'When Get-CimInstance -ClassName SecurityTokenService throws an exception' {
                    BeforeAll {
                        Mock -CommandName Get-CimInstance `
                            -ParameterFilter { `
                                $Namespace -eq 'root/ADFS' -and `
                                $ClassName -eq 'SecurityTokenService' } `
                            -MockWith { throw $mockExceptionErrorMessage }
                    }

                    It 'Should throw the correct exception' {
                        { Get-TargetResource @getTargetResourceParameters } | Should -Throw (
                            $script:localizedData.GettingAdfsSecurityTokenServiceError -f
                            $mockResource.FederationServiceName)
                    }
                }

                Context 'When Get-AdfsProperties throws an exception' {
                    BeforeAll {
                        Mock Get-AdfsProperties -MockWith { throw $mockExceptionErrorMessage }
                    }

                    It 'Should throw the correct exception' {
                        { Get-TargetResource @getTargetResourceParameters } | Should -Throw (
                            $script:localizedData.GettingAdfsPropertiesError -f
                            $mockResource.FederationServiceName)
                    }
                }
            }

            Context "When the $($Global:DscResourceFriendlyName) Resource is Absent" {
                BeforeAll {
                    Mock -CommandName $ResourceCommand.Get -MockWith { 'NotConfigured' }

                    $result = Get-TargetResource @getTargetResourceParameters
                }

                foreach ($property in $mockGsaResource.Keys)
                {
                    It "Should return the correct $property property" {
                        $result.$property | Should -Be $mockAbsentResource.$property
                    }
                }

                It 'Should call the expected mocks' {
                    Assert-MockCalled -CommandName Assert-Module `
                        -ParameterFilter { $ModuleName -eq $Global:PSModuleName } `
                        -Exactly -Times 1
                    Assert-MockCalled -CommandName Assert-DomainMember -Exactly -Times 1
                    Assert-MockCalled -CommandName "Assert-$($Global:PSModuleName)Service" -Exactly -Times 0
                    Assert-MockCalled -CommandName Get-CimInstance `
                        -ParameterFilter { $Filter -eq "Name='$script:AdfsServiceName'" } `
                        -Exactly -Times 0
                    Assert-MockCalled -CommandName Get-AdfsSslCertificate -Exactly -Times 0
                    Assert-MockCalled -CommandName Get-AdfsProperties -Exactly -Times 0
                    Assert-MockCalled -CommandName Assert-GroupServiceAccount -Exactly -Times 0
                }
            }
        }

        Describe "$Global:DSCResourceName\Set-TargetResource" -Tag 'Set' {
            BeforeAll {
                $setTargetResourceParameters = @{
                    FederationServiceName         = $mockGsaResource.FederationServiceName
                    CertificateThumbprint         = $mockGsaResource.CertificateThumbprint
                    Credential                    = $mockCredential
                    GroupServiceAccountIdentifier = $mockGsaResource.GroupServiceAccountIdentifier
                }

                $mockInstallResourceSuccessResult = @{
                    Message = 'The configuration completed successfully.'
                    Context = 'DeploymentSucceeded'
                    Status  = 'Success'
                }

                $mockInstallResourceErrorResult = @{
                    Message = 'The configuration did not complete successfully.'
                    Context = 'DeploymentTask'
                    Status  = 'Error'
                }

                $mockNewCertificateThumbprint = '6F7E9F5543505B943FEEA49E651EDDD8D9D45014'
                $mockNewFederationServiceDisplayName = 'Fabrikam ADFS Service'

                Mock -CommandName $ResourceCommand.Install -MockWith { $mockInstallResourceSuccessResult }
            }

            Context 'When both credential parameters have been specified' {
                BeforeAll {
                    $setTargetResourceBothCredentialParameters = $setTargetResourceParameters.Clone()
                    $setTargetResourceBothCredentialParameters.Add('ServiceAccountCredential', $mockCredential)
                }

                It 'Should throw the correct error' {
                    { Set-TargetResource @setTargetResourceBothCredentialParameters } |
                        Should -Throw ($script:localizedData.ResourceDuplicateCredentialError -f
                            $mockResource.FederationServiceName)
                }
            }

            Context 'When neither credential parameters have been specified' {
                BeforeAll {
                    $setTargetResourceBothCredentialParameters = $setTargetResourceParameters.Clone()
                    $setTargetResourceBothCredentialParameters.Remove('GroupServiceAccountIdentifier')
                }

                It 'Should throw the correct error' {
                    { Set-TargetResource @setTargetResourceBothCredentialParameters } |
                        Should -Throw ($script:localizedData.ResourceMissingCredentialError -f
                            $mockResource.FederationServiceName)
                }
            }

            Context "When the $($Global:DscResourceFriendlyName) Resource is not installed" {
                BeforeAll {
                    $mockGetTargetResourceAbsentResult = @{
                        Ensure = 'Absent'
                    }

                    Mock -CommandName Get-TargetResource -MockWith { $mockGetTargetResourceAbsentResult }
                }

                It 'Should not throw' {
                    { Set-TargetResource @setTargetResourceParameters } | Should -Not -Throw
                }

                It 'Should call the expected mocks' {
                    Assert-MockCalled -CommandName $ResourceCommand.Install `
                        -ParameterFilter { $FederationServiceName -eq $setTargetResourceParameters.FederationServiceName } `
                        -Exactly -Times 1
                }

                Context "When $($ResourceCommand.Install) throws System.IO.FileNotFoundException" {
                    BeforeAll {
                        Mock $ResourceCommand.Install -MockWith { throw New-Object System.IO.FileNotFoundException }
                    }

                    It 'Should not throw' {
                        { Set-TargetResource @setTargetResourceParameters } | Should -Not -Throw
                    }

                    It 'Should call the expected mocks' {
                        Assert-MockCalled -CommandName $ResourceCommand.Install `
                            -ParameterFilter { $FederationServiceName -eq $setTargetResourceParameters.FederationServiceName } `
                            -Exactly -Times 1
                    }
                }

                Context "When $($ResourceCommand.Install) throws an exception" {
                    BeforeAll {
                        Mock $ResourceCommand.Install -MockWith { throw $mockExceptionErrorMessage }
                    }

                    It 'Should throw the correct error' {
                        { Set-TargetResource @setTargetResourceParameters } | Should -Throw (
                            $script:localizedData.InstallationError -f $setTargetResourceParameters.FederationServiceName)
                    }
                }

                Context "When $($ResourceCommand.Install) returns a result with a status of 'Error'" {
                    BeforeAll {
                        Mock $ResourceCommand.Install -MockWith { $mockInstallResourceErrorResult }
                    }

                    It 'Should throw the correct error' {
                        { Set-TargetResource @setTargetResourceParameters } | Should -Throw (
                            $mockInstallResourceErrorResult.Message)
                    }
                }
            }

            Context "When the $($Global:DscResourceFriendlyName) Resource is installed" {
                BeforeAll {
                    Mock -CommandName Get-TargetResource -MockWith { $mockGetTargetResourcePresentResult }
                }

                It 'Should not throw' {
                    { Set-TargetResource @setTargetResourceParameters } | Should -Not -Throw
                }
            }
        }

        Describe "$Global:DSCResourceName\Test-TargetResource" -Tag 'Test' {
            BeforeAll {
                $testTargetResourceParameters = @{
                    FederationServiceName = $mockResource.FederationServiceName
                    CertificateThumbprint = $mockResource.CertificateThumbprint
                    Credential            = $mockCredential
                }
            }

            Context "When the $($Global:DscResourceFriendlyName) Resource is installed" {
                BeforeAll {
                    Mock Get-TargetResource -MockWith { $mockGetTargetResourcePresentResult }
                }

                It 'Should return $true' {
                    Test-TargetResource @testTargetResourceParameters | Should -BeTrue
                }

                It 'Should call the expected mocks' {
                    Assert-MockCalled -CommandName Get-TargetResource `
                        -ParameterFilter { `
                            $FederationServiceName -eq $testTargetResourceParameters.FederationServiceName } `
                        -Exactly -Times 1

                }
            }

            Context "When the $($Global:DscResourceFriendlyName) Resource is not installed" {
                BeforeAll {
                    Mock Get-TargetResource -MockWith { $mockGetTargetResourceAbsentResult }
                }

                It 'Should return $false' {
                    Test-TargetResource @testTargetResourceParameters | Should -BeFalse
                }

                It 'Should call the expected mocks' {
                    Assert-MockCalled -CommandName Get-TargetResource `
                        -ParameterFilter { `
                            $FederationServiceName -eq $testTargetResourceParameters.FederationServiceName } `
                        -Exactly -Times 1
                }
            }
        }
    }
}
finally
{
    Restore-TestEnvironment -TestEnvironment $TestEnvironment
}