Tests/Unit/MSFT_CertReq.Tests.ps1

[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '')]
param ()

$script:DSCModuleName      = 'CertificateDsc'
$script:DSCResourceName    = 'MSFT_CertReq'

#region HEADER
# Integration Test Template Version: 1.1.0
[System.String] $script:moduleRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot)
if ( (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests'))) -or `
     (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) )
{
    & git @('clone','https://github.com/PowerShell/DscResource.Tests.git',(Join-Path -Path $script:moduleRoot -ChildPath '\DSCResource.Tests\'))
}

Import-Module (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force
$TestEnvironment = Initialize-TestEnvironment `
    -DSCModuleName $script:DSCModuleName `
    -DSCResourceName $script:DSCResourceName `
    -TestType Unit
#endregion

# Begin Testing
try
{
    InModuleScope $script:DSCResourceName {
        $dscResourceName = $script:DSCResourceName
        $validThumbprint = (
            [System.AppDomain]::CurrentDomain.GetAssemblies().GetTypes() | Where-Object {
                $_.BaseType.BaseType -eq [System.Security.Cryptography.HashAlgorithm] -and
                ($_.Name -cmatch 'Managed$' -or $_.Name -cmatch 'Provider$')
            } | Select-Object -First 1 | ForEach-Object {
                (New-Object $_).ComputeHash([String]::Empty) | ForEach-Object {
                    '{0:x2}' -f $_
                }
            }
        ) -join ''
        $caServerFQDN          = 'rootca.contoso.com'
        $caRootName            = 'contoso-CA'
        $validSubject          = 'Test Subject'
        $validIssuer           = "CN=$caRootName, DC=contoso, DC=com"
        $keyLength             = '2048'
        $exportable            = $true
        $providerName          = '"Microsoft RSA SChannel Cryptographic Provider"'
        $oid                   = '1.3.6.1.5.5.7.3.1'
        $keyUsage              = '0xa0'
        $certificateTemplate   = 'WebServer'
        $subjectAltUrl         = 'contoso.com'
        $subjectAltName        = "dns=$subjectAltUrl"
        $friendlyName          = "Test Certificate"

        $validCert = New-Object -TypeName PSObject -Property @{
            Thumbprint   = $validThumbprint
            Subject      = "CN=$validSubject"
            Issuer       = $validIssuer
            NotBefore    = (Get-Date).AddDays(-30) # Issued on
            NotAfter     = (Get-Date).AddDays(31) # Expires after
            FriendlyName = $friendlyName
        }
        Add-Member -InputObject $validCert -MemberType ScriptMethod -Name Verify -Value {
            return $true
        }

        $expiringCert = New-Object -TypeName PSObject -Property @{
            Thumbprint   = $validThumbprint
            Subject      = "CN=$validSubject"
            Issuer       = $validIssuer
            NotBefore    = (Get-Date).AddDays(-30) # Issued on
            NotAfter     = (Get-Date).AddDays(30) # Expires after
            FriendlyName = $friendlyName
        }
        Add-Member -InputObject $expiringCert -MemberType ScriptMethod -Name Verify -Value {
            return $true
        }

        $expiredCert = New-Object -TypeName PSObject -Property @{
            Thumbprint   = $validThumbprint
            Subject      = "CN=$validSubject"
            Issuer       = $validIssuer
            NotBefore    = (Get-Date).AddDays(-30) # Issued on
            NotAfter     = (Get-Date).AddDays(-1) # Expires after
            FriendlyName = $friendlyName
        }
        Add-Member -InputObject $expiredCert -MemberType ScriptMethod -Name Verify -Value {
            return $true
        }

        $sanOid = New-Object -TypeName System.Security.Cryptography.Oid -Property @{FriendlyName = 'Subject Alternative Name'}
        $sanExt = @{
            oid      = $(,$sanOid)
            Critical = $false
        }
        Add-Member -InputObject $sanExt -MemberType ScriptMethod -Name Format -Force -Value {
            return "DNS Name=$subjectAltUrl"
        }
        $validSANCert = New-Object -TypeName PSObject -Property @{
            Thumbprint   = $validThumbprint
            Subject      = "CN=$validSubject"
            Issuer       = $validIssuer
            NotBefore    = (Get-Date).AddDays(-30) # Issued on
            NotAfter     = (Get-Date).AddDays(31) # Expires after
            Extensions   = $sanExt
            FriendlyName = $friendlyName
        }
        Add-Member -InputObject $validSANCert -MemberType ScriptMethod -Name Verify -Value {
            return $true
        }

        $incorrectSanExt = @{
            oid      = $(,$sanOid)
            Critical = $false
        }
        Add-Member -InputObject $incorrectSanExt -MemberType ScriptMethod -Name Format -Force -Value {
            return "DNS Name=incorrect.com"
        }
        $incorrectSANCert = New-Object -TypeName PSObject -Property @{
            Thumbprint   = $validThumbprint
            Subject      = "CN=$validSubject"
            Issuer       = $validIssuer
            NotBefore    = (Get-Date).AddDays(-30) # Issued on
            NotAfter     = (Get-Date).AddDays(31) # Expires after
            Extensions   = $incorrectSanExt
            FriendlyName = $friendlyName
        }
        Add-Member -InputObject $incorrectSANCert -MemberType ScriptMethod -Name Verify -Value {
            return $true
        }

        $emptySANCert    = New-Object -TypeName PSObject -Property @{
            Thumbprint   = $validThumbprint
            Subject      = "CN=$validSubject"
            Issuer       = $validIssuer
            NotBefore    = (Get-Date).AddDays(-30) # Issued on
            NotAfter     = (Get-Date).AddDays(31) # Expires after
            Extensions   = @()
            FriendlyName = $friendlyName
        }
        Add-Member -InputObject $emptySANCert -MemberType ScriptMethod -Name Verify -Value {
            return $true
        }

        $incorrectFriendlyName = New-Object -TypeName PSObject -Property @{
            Thumbprint   = $validThumbprint
            Subject      = "CN=$validSubject"
            Issuer       = $validIssuer
            NotBefore    = (Get-Date).AddDays(-30) # Issued on
            NotAfter     = (Get-Date).AddDays(31) # Expires after
            FriendlyName = 'This name will not match'
        }
        Add-Member -InputObject $incorrectFriendlyName -MemberType ScriptMethod -Name Verify -Value {
            return $true
        }

        $caType         = 'Enterprise'
        $cepURL         = 'DummyURL'
        $cesURL         = 'DummyURL'

        $testUsername   = 'DummyUsername'
        $testPassword   = 'DummyPassword'
        $testCredential = New-Object System.Management.Automation.PSCredential $testUsername, (ConvertTo-SecureString $testPassword -AsPlainText -Force)

        $paramsStandard = @{
            Subject               = $validSubject
            CAServerFQDN          = $caServerFQDN
            CARootName            = $caRootName
            KeyLength             = $keyLength
            Exportable            = $exportable
            ProviderName          = $providerName
            OID                   = $oid
            KeyUsage              = $keyUsage
            CertificateTemplate   = $certificateTemplate
            Credential            = $testCredential
            AutoRenew             = $False
            FriendlyName          = $friendlyName
        }

        $paramsAutoDiscovery = @{
            Subject               = $validSubject
            KeyLength             = $keyLength
            Exportable            = $exportable
            ProviderName          = $providerName
            OID                   = $oid
            KeyUsage              = $keyUsage
            CertificateTemplate   = $certificateTemplate
            Credential            = $testCredential
            AutoRenew             = $False
            FriendlyName          = $friendlyName
        }

        $paramsAutoRenew = @{
            Subject               = $validSubject
            CAServerFQDN          = $caServerFQDN
            CARootName            = $caRootName
            KeyLength             = $keyLength
            Exportable            = $exportable
            ProviderName          = $providerName
            OID                   = $oid
            KeyUsage              = $keyUsage
            CertificateTemplate   = $certificateTemplate
            Credential            = $testCredential
            AutoRenew             = $True
            FriendlyName          = $friendlyName
        }

        $paramsNoCred = @{
            Subject               = $validSubject
            CAServerFQDN          = $caServerFQDN
            CARootName            = $caRootName
            KeyLength             = $keyLength
            Exportable            = $exportable
            ProviderName          = $providerName
            OID                   = $oid
            KeyUsage              = $keyUsage
            CertificateTemplate   = $certificateTemplate
            Credential            = $null
            AutoRenew             = $False
            FriendlyName          = $friendlyName
        }

        $paramsAutoRenewNoCred = @{
            Subject               = $validSubject
            CAServerFQDN          = $caServerFQDN
            CARootName            = $caRootName
            KeyLength             = $keyLength
            Exportable            = $exportable
            ProviderName          = $providerName
            OID                   = $oid
            KeyUsage              = $keyUsage
            CertificateTemplate   = $certificateTemplate
            Credential            = $null
            AutoRenew             = $True
            FriendlyName          = $friendlyName
        }

        $paramsKeyLength4096AutoRenewNoCred = @{
            Subject               = $validSubject
            CAServerFQDN          = $caServerFQDN
            CARootName            = $caRootName
            KeyLength             = '4096'
            Exportable            = $exportable
            ProviderName          = $providerName
            OID                   = $oid
            KeyUsage              = $keyUsage
            CertificateTemplate   = $certificateTemplate
            Credential            = $null
            AutoRenew             = $True
            FriendlyName          = $friendlyName
        }

        $paramsSubjectAltName = @{
            Subject               = $validSubject
            CAServerFQDN          = $caServerFQDN
            CARootName            = $caRootName
            KeyLength             = $keyLength
            Exportable            = $exportable
            ProviderName          = $providerName
            OID                   = $oid
            KeyUsage              = $keyUsage
            CertificateTemplate   = $certificateTemplate
            Credential            = $testCredential
            SubjectAltName        = $subjectAltName
            AutoRenew             = $False
            FriendlyName          = $friendlyName
        }

        $paramsSubjectAltNameNoCred = @{
            Subject               = $validSubject
            CAServerFQDN          = $caServerFQDN
            CARootName            = $caRootName
            KeyLength             = $keyLength
            Exportable            = $exportable
            ProviderName          = $providerName
            OID                   = $oid
            KeyUsage              = $keyUsage
            CertificateTemplate   = $certificateTemplate
            Credential            = $null
            SubjectAltName        = $subjectAltName
            AutoRenew             = $False
            FriendlyName          = $friendlyName
        }

        $paramsStandaloneWebEnrollment = @{
            Subject               = $validSubject
            CAServerFQDN          = $caServerFQDN
            CARootName            = $caRootName
            KeyLength             = $keyLength
            Exportable            = $exportable
            ProviderName          = $providerName
            OID                   = $oid
            KeyUsage              = $keyUsage
            CertificateTemplate   = $certificateTemplate
            Credential            = $testCredential
            AutoRenew             = $False
            CAType                = 'Standalone'
            CepURL                = $cepURL
            CesURL                = $cesURL
            FriendlyName          = $friendlyName
        }

        $paramsEnterpriseWebEnrollment = @{
            Subject               = $validSubject
            CAServerFQDN          = $caServerFQDN
            CARootName            = $caRootName
            KeyLength             = $keyLength
            Exportable            = $exportable
            ProviderName          = $providerName
            OID                   = $oid
            KeyUsage              = $keyUsage
            CertificateTemplate   = $certificateTemplate
            Credential            = $testCredential
            AutoRenew             = $False
            CAType                = $caType
            CepURL                = $cepURL
            CesURL                = $cesURL
            FriendlyName          = $friendlyName
        }

        $certInf = @"
[NewRequest]
Subject = "CN=$validSubject"
KeySpec = 1
KeyLength = $keyLength
Exportable = $($exportable.ToString().ToUpper())
MachineKeySet = TRUE
SMIME = FALSE
PrivateKeyArchive = FALSE
UserProtected = FALSE
UseExistingKeySet = FALSE
ProviderName = $providerName
ProviderType = 12
RequestType = CMC
KeyUsage = $keyUsage
FriendlyName = "$friendlyName"
[RequestAttributes]
CertificateTemplate = "$certificateTemplate"
[EnhancedKeyUsageExtension]
OID = $oid
"@


        $certInfNoTemplate = $certInf.Replace(@"
[RequestAttributes]
CertificateTemplate = "$certificateTemplate"
[EnhancedKeyUsageExtension]
"@
, '[EnhancedKeyUsageExtension]')

        $certInfRenew = $certInf
        $certInfRenew += @"
 
RenewalCert = $validThumbprint
"@

        $certInfKeyRenew = $certInfRenew -Replace 'KeyLength = ([0-z]*)', 'KeyLength = 4096'
        $certInfSubjectAltName = $certInf
        $certInfSubjectAltName += @"
 
[Extensions]
2.5.29.17 = "{text}$subjectAltName"
"@


        Describe "$dscResourceName\Get-TargetResource" {
            Mock Get-ChildItem -ParameterFilter { $Path -eq 'Cert:\LocalMachine\My' } `
                -Mockwith { $validCert }

            Mock Get-CertificateTemplateName -MockWith { $certificateTemplate }

            Mock Get-CertificateSan -MockWith { $subjectAltName }

            Mock -CommandName Find-CertificateAuthority -MockWith {
                    return New-Object -TypeName psobject -Property @{
                        CAServerFQDN = 'rootca.contoso.com'
                        CARootName = 'contoso-CA'
                    }
                }

            Context 'Called without auto discovery' {
                $result = Get-TargetResource @paramsStandard -Verbose

                It 'Should return a hashtable' {
                    $result | Should -BeOfType System.Collections.Hashtable
                }

                It 'Should contain the input values' {
                    $result.Subject              | Should -BeExactly $validSubject
                    $result.CAServerFQDN         | Should -BeNullOrEmpty
                    $result.CARootName           | Should -BeExactly $caRootName
                    $result.KeyLength            | Should -BeNullOrEmpty
                    $result.Exportable           | Should -BeNullOrEmpty
                    $result.ProviderName         | Should -BeNullOrEmpty
                    $result.OID                  | Should -BeNullOrEmpty
                    $result.KeyUsage             | Should -BeNullOrEmpty
                    $result.CertificateTemplate  | Should -BeExactly $certificateTemplate
                    $result.SubjectAltName       | Should -BeNullOrEmpty
                    $result.FriendlyName         | Should -BeExactly $friendlyName
                }
            }

            Context 'Called with auto discovery' {
                $result = Get-TargetResource @paramsAutoDiscovery -Verbose

                It 'Should return a hashtable' {
                    $result | Should -BeOfType System.Collections.Hashtable
                }

                It 'Should contain the input values and the CA should be auto-discovered' {
                    $result.Subject              | Should -BeExactly $validSubject
                    $result.CAServerFQDN         | Should -BeExactly $caServerFQDN
                    $result.CARootName           | Should -BeExactly $caRootName
                    $result.KeyLength            | Should -BeNullOrEmpty
                    $result.Exportable           | Should -BeNullOrEmpty
                    $result.ProviderName         | Should -BeNullOrEmpty
                    $result.OID                  | Should -BeNullOrEmpty
                    $result.KeyUsage             | Should -BeNullOrEmpty
                    $result.CertificateTemplate  | Should -BeExactly $certificateTemplate
                    $result.SubjectAltName       | Should -BeNullOrEmpty
                    $result.FriendlyName         | Should -BeExactly $friendlyName
                }

                It 'Should call the mocked function Find-CertificateAuthority once' {
                    Assert-MockCalled -CommandName Find-CertificateAuthority -Exactly -Times 1
                }
            }
        }
        #endregion

        #region Set-TargetResource
        Describe "$dscResourceName\Set-TargetResource" {
            Mock -CommandName Join-Path -MockWith { 'CertReq-Test' } `
                -ParameterFilter { $Path -eq $env:Temp }

            Mock -CommandName Test-Path -MockWith { $true } `
                -ParameterFilter { $Path -eq 'CertReq-Test.req' }

            Mock -CommandName Test-Path -MockWith { $true } `
                -ParameterFilter { $Path -eq 'CertReq-Test.cer' }

            Mock -CommandName CertReq.exe

            Mock -CommandName Set-Content `
                -ParameterFilter {
                    $Path -eq 'CertReq-Test.inf' -and `
                    $Value -eq $certInf
                }

            Context 'autorenew is false, credentials not passed' {
                Mock -CommandName Get-ChildItem -Mockwith { } `
                    -ParameterFilter { $Path -eq 'Cert:\LocalMachine\My' }

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

                It 'Should call expected mocks' {
                    Assert-MockCalled -CommandName Join-Path -Exactly 1

                    Assert-MockCalled -CommandName Test-Path -Exactly 1 `
                        -ParameterFilter { $Path -eq 'CertReq-Test.req' }

                    Assert-MockCalled -CommandName Test-Path  -Exactly 1 `
                        -ParameterFilter { $Path -eq 'CertReq-Test.cer' }

                    Assert-MockCalled -CommandName Set-Content -Exactly 1 `
                        -ParameterFilter {
                            $Path -eq 'CertReq-Test.inf' -and `
                            $Value -eq $certInf
                        }

                    Assert-MockCalled -CommandName CertReq.exe -Exactly 3
                }
            }

            Context 'autorenew is true, credentials not passed and certificate does not exist' {
                Mock -CommandName Get-ChildItem -Mockwith { } `
                    -ParameterFilter { $Path -eq 'Cert:\LocalMachine\My' }

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

                It 'Should call expected mocks' {
                    Assert-MockCalled -CommandName Join-Path -Exactly 1

                    Assert-MockCalled -CommandName Test-Path -Exactly 1 `
                        -ParameterFilter { $Path -eq 'CertReq-Test.req' }

                    Assert-MockCalled -CommandName Test-Path  -Exactly 1 `
                        -ParameterFilter { $Path -eq 'CertReq-Test.cer' }

                    Assert-MockCalled -CommandName Get-ChildItem -Exactly 1 `
                        -ParameterFilter { $Path -eq 'Cert:\LocalMachine\My' }

                    Assert-MockCalled -CommandName Set-Content -Exactly 1 `
                        -ParameterFilter {
                            $Path -eq 'CertReq-Test.inf' -and `
                            $Value -eq $certInf
                        }

                    Assert-MockCalled -CommandName CertReq.exe -Exactly 3
                }
            }

            Context 'autorenew is true, credentials not passed and valid certificate exists' {
                Mock -CommandName Get-ChildItem -Mockwith { $validCert } `
                    -ParameterFilter { $Path -eq 'Cert:\LocalMachine\My' }

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

                It 'Should call expected mocks' {
                    Assert-MockCalled -CommandName Join-Path -Exactly 1

                    Assert-MockCalled -CommandName Test-Path -Exactly 1 `
                        -ParameterFilter { $Path -eq 'CertReq-Test.req' }

                    Assert-MockCalled -CommandName Test-Path  -Exactly 1 `
                        -ParameterFilter { $Path -eq 'CertReq-Test.cer' }

                    Assert-MockCalled -CommandName Get-ChildItem -Exactly 1 `
                        -ParameterFilter { $Path -eq 'Cert:\LocalMachine\My' }

                    Assert-MockCalled -CommandName Set-Content -Exactly 1 `
                        -ParameterFilter {
                            $Path -eq 'CertReq-Test.inf' -and `
                            $Value -eq $certInf
                        }

                    Assert-MockCalled -CommandName CertReq.exe -Exactly 3
                }
            }

            Mock -CommandName Set-Content `
                -ParameterFilter {
                    $Path -eq 'CertReq-Test.inf' -and `
                    $Value -eq $certInfRenew
                }
            Context 'autorenew is true, credentials not passed and expiring certificate exists' {
                Mock -CommandName Get-ChildItem -Mockwith { $expiringCert } `
                    -ParameterFilter { $Path -eq 'Cert:\LocalMachine\My' }

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

                It 'Should call expected mocks' {
                    Assert-MockCalled -CommandName Join-Path -Exactly 1

                    Assert-MockCalled -CommandName Test-Path -Exactly 1 `
                        -ParameterFilter { $Path -eq 'CertReq-Test.req' }

                    Assert-MockCalled -CommandName Test-Path  -Exactly 1 `
                        -ParameterFilter { $Path -eq 'CertReq-Test.cer' }

                    Assert-MockCalled -CommandName Get-ChildItem -Exactly 1 `
                        -ParameterFilter { $Path -eq 'Cert:\LocalMachine\My' }

                    Assert-MockCalled -CommandName Set-Content -Exactly 1 `
                        -ParameterFilter {
                            $Path -eq 'CertReq-Test.inf' -and `
                            $Value -eq $certInfRenew
                        }

                    Assert-MockCalled -CommandName CertReq.exe -Exactly 3
                }
            }

            Context 'autorenew is true, credentials not passed and expired certificate exists' {
                Mock -CommandName Get-ChildItem -Mockwith { $expiredCert } `
                    -ParameterFilter { $Path -eq 'Cert:\LocalMachine\My' }

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

                It 'Should call expected mocks' {
                    Assert-MockCalled -CommandName Join-Path -Exactly 1

                    Assert-MockCalled -CommandName Test-Path -Exactly 1 `
                        -ParameterFilter { $Path -eq 'CertReq-Test.req' }

                    Assert-MockCalled -CommandName Test-Path  -Exactly 1 `
                        -ParameterFilter { $Path -eq 'CertReq-Test.cer' }

                    Assert-MockCalled -CommandName Get-ChildItem -Exactly 1 `
                        -ParameterFilter { $Path -eq 'Cert:\LocalMachine\My' }

                    Assert-MockCalled -CommandName Set-Content -Exactly 1 `
                        -ParameterFilter {
                            $Path -eq 'CertReq-Test.inf' -and `
                            $Value -eq $certInfRenew
                        }

                    Assert-MockCalled -CommandName CertReq.exe -Exactly 3
                }
            }

            Mock -CommandName Set-Content `
                -ParameterFilter {
                    $Path -eq 'CertReq-Test.inf' -and `
                    $Value -eq $certInfKeyRenew
                }

            Context 'autorenew is true, credentials not passed, keylength passed and expired certificate exists' {
                Mock -CommandName Get-ChildItem -Mockwith { $expiredCert } `
                    -ParameterFilter { $Path -eq 'Cert:\LocalMachine\My' }

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

                It 'Should call expected mocks' {
                    Assert-MockCalled -CommandName Join-Path -Exactly 1

                    Assert-MockCalled -CommandName Test-Path -Exactly 1 `
                        -ParameterFilter { $Path -eq 'CertReq-Test.req' }
                    Assert-MockCalled -CommandName Test-Path  -Exactly 1 `
                        -ParameterFilter { $Path -eq 'CertReq-Test.cer' }

                    Assert-MockCalled -CommandName Get-ChildItem -Exactly 1 `
                        -ParameterFilter { $Path -eq 'Cert:\LocalMachine\My' }

                    Assert-MockCalled -CommandName Set-Content -Exactly 1 `
                        -ParameterFilter { $Path -eq 'CertReq-Test.inf' }

                    Assert-MockCalled -CommandName CertReq.exe -Exactly 3

                    Assert-MockCalled -CommandName Set-Content -Exactly 1 `
                        -ParameterFilter {
                            $Path -eq 'CertReq-Test.inf' -and `
                            $Value -eq $certInfKeyRenew
                        }
                }
            }

            Mock -CommandName Test-Path -MockWith { $false } `
                -ParameterFilter { $Path -eq 'CertReq-Test.req' }

            Mock -CommandName Set-Content `
                -ParameterFilter {
                    $Path -eq 'CertReq-Test.inf' -and `
                    $Value -eq $certInf
                }

            Context 'autorenew is false, credentials not passed, certificate request creation failed' {
                Mock -CommandName Get-ChildItem -Mockwith { } `
                    -ParameterFilter { $Path -eq 'Cert:\LocalMachine\My' }

                $errorRecord = Get-InvalidOperationRecord `
                    -Message ($LocalizedData.CertificateReqNotFoundError -f 'CertReq-Test.req')

                It 'Should throw CertificateReqNotFoundError exception' {
                    { Set-TargetResource @paramsNoCred -Verbose } | Should -Throw $errorRecord
                }

                It 'Should call expected mocks' {
                    Assert-MockCalled -CommandName Join-Path -Exactly 1

                    Assert-MockCalled -CommandName Test-Path -Exactly 1 `
                        -ParameterFilter { $Path -eq 'CertReq-Test.req' }

                    Assert-MockCalled -CommandName Test-Path -Exactly 0 `
                        -ParameterFilter { $Path -eq 'CertReq-Test.cer' }

                    Assert-MockCalled -CommandName Set-Content -Exactly 1 `
                        -ParameterFilter {
                            $Path -eq 'CertReq-Test.inf' -and `
                            $Value -eq $certInf
                        }

                    Assert-MockCalled -CommandName CertReq.exe -Exactly 1
                }
            }

            Mock -CommandName Test-Path -MockWith { $true } `
                -ParameterFilter { $Path -eq 'CertReq-Test.req' }

            Mock -CommandName Test-Path -MockWith { $false } `
                -ParameterFilter { $Path -eq 'CertReq-Test.cer' }

            Context 'Autorenew is false, credentials not passed, certificate creation failed' {
                Mock -CommandName Get-ChildItem -Mockwith { } `
                    -ParameterFilter { $Path -eq 'Cert:\LocalMachine\My' }

                $errorRecord = Get-InvalidOperationRecord `
                    -Message ($LocalizedData.CertificateCerNotFoundError -f 'CertReq-Test.cer')

                It 'Should throw CertificateCerNotFoundError exception' {
                    { Set-TargetResource @paramsNoCred -Verbose } | Should -Throw $errorRecord
                }

                It 'Should call expected mocks' {
                    Assert-MockCalled -CommandName Join-Path -Exactly 1

                    Assert-MockCalled -CommandName Test-Path -Exactly 1 `
                        -ParameterFilter { $Path -eq 'CertReq-Test.req' }

                    Assert-MockCalled -CommandName Test-Path -Exactly 1 `
                        -ParameterFilter { $Path -eq 'CertReq-Test.cer' }

                    Assert-MockCalled -CommandName Set-Content -Exactly 1 `
                        -ParameterFilter {
                            $Path -eq 'CertReq-Test.inf' -and `
                            $Value -eq $certInf
                        }

                    Assert-MockCalled -CommandName CertReq.exe -Exactly 2
                }
            }

            Mock -CommandName Test-Path -MockWith { $true } `
                -ParameterFilter { $Path -eq 'CertReq-Test.req' }

            Mock -CommandName Test-Path -MockWith { $true } `
                -ParameterFilter { $Path -eq 'CertReq-Test.cer' }

            Mock -CommandName Test-Path -MockWith { $true } `
                -ParameterFilter { $Path -eq 'CertReq-Test.out' }

            Context 'autorenew is false, credentials passed' {
                Mock -CommandName Get-ChildItem -Mockwith { } `
                    -ParameterFilter { $Path -eq 'Cert:\LocalMachine\My' }

                Mock -CommandName Get-Content -Mockwith { 'Output' } `
                    -ParameterFilter { $Path -eq 'CertReq-Test.out' }

                Mock -CommandName Remove-Item `
                    -ParameterFilter { $Path -eq 'CertReq-Test.out' }

                Mock -CommandName Import-Module

                function Start-Win32Process {
                    param (
                        [Parameter()]
                        $Path,

                        [Parameter()]
                        $Arguments,

                        [Parameter()]
                        $Credential
                    )
                }

                function Wait-Win32ProcessStop {
                    param (
                        [Parameter()]
                        $Path,

                        [Parameter()]
                        $Arguments,

                        [Parameter()]
                        $Credential
                    )
                }

                Mock -CommandName Start-Win32Process -ModuleName MSFT_CertReq

                Mock -CommandName Wait-Win32ProcessStop -ModuleName MSFT_CertReq

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

                It 'Should call expected mocks' {
                    Assert-MockCalled -CommandName Join-Path -Exactly 1

                    Assert-MockCalled -CommandName Test-Path -Exactly 1 `
                        -ParameterFilter { $Path -eq 'CertReq-Test.req' }

                    Assert-MockCalled -CommandName Test-Path  -Exactly 1 `
                        -ParameterFilter { $Path -eq 'CertReq-Test.cer' }

                    Assert-MockCalled -CommandName Set-Content -Exactly 1 `
                        -ParameterFilter {
                            $Path -eq 'CertReq-Test.inf' -and `
                            $Value -eq $certInf
                        }

                    Assert-MockCalled -CommandName CertReq.exe -Exactly 2

                    Assert-MockCalled -CommandName Start-Win32Process -ModuleName MSFT_CertReq -Exactly 1

                    Assert-MockCalled -CommandName Wait-Win32ProcessStop -ModuleName MSFT_CertReq -Exactly 1

                    Assert-MockCalled -CommandName Test-Path  -Exactly 1 `
                        -ParameterFilter { $Path -eq 'CertReq-Test.out' }

                    Assert-MockCalled -CommandName Get-Content -Exactly 1 `
                        -ParameterFilter { $Path -eq 'CertReq-Test.out' }

                    Assert-MockCalled -CommandName Remove-Item -Exactly 1 `
                        -ParameterFilter { $Path -eq 'CertReq-Test.out' }
                }
            }

            Mock -CommandName Set-Content `
                -ParameterFilter {
                    $Path -eq 'CertReq-Test.inf' -and `
                    $Value -eq $certInfSubjectAltName
                }

            Context 'autorenew is false, subject alt name passed, credentials not passed' {
                Mock -CommandName Get-ChildItem -Mockwith { } `
                    -ParameterFilter { $Path -eq 'Cert:\LocalMachine\My' }

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

                It 'Should call expected mocks' {
                    Assert-MockCalled -CommandName Join-Path -Exactly 1

                    Assert-MockCalled -CommandName Test-Path -Exactly 1 `
                        -ParameterFilter { $Path -eq 'CertReq-Test.req' }

                    Assert-MockCalled -CommandName Test-Path  -Exactly 1 `
                        -ParameterFilter { $Path -eq 'CertReq-Test.cer' }

                    Assert-MockCalled -CommandName Set-Content -Exactly 1 `
                        -ParameterFilter {
                            $Path -eq 'CertReq-Test.inf' -and `
                            $Value -eq $certInfSubjectAltName
                        }

                    Assert-MockCalled -CommandName CertReq.exe -Exactly 3
                }
            }

            Context 'standalone CA, URL for CEP and CES passed, credentials passed, inf not containing template' {
                Mock -CommandName Set-Content -ParameterFilter {
                    $Path -eq 'CertReq-Test.inf' -and `
                    $Value -eq $certInfNoTemplate
                }

                Mock -CommandName Get-ChildItem -Mockwith { } `
                    -ParameterFilter { $Path -eq 'Cert:\LocalMachine\My' }

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

                It 'Should call expected mocks' {
                    Assert-MockCalled -CommandName Join-Path -Exactly 1

                    Assert-MockCalled -CommandName Test-Path -Exactly 1 `
                        -ParameterFilter { $Path -eq 'CertReq-Test.req' }

                    Assert-MockCalled -CommandName Test-Path  -Exactly 1 `
                        -ParameterFilter { $Path -eq 'CertReq-Test.cer' }

                    Assert-MockCalled -CommandName Set-Content -Exactly 1 `
                        -ParameterFilter {
                            $Path -eq 'CertReq-Test.inf' -and `
                            $Value -eq $certInfNoTemplate
                        }

                    Assert-MockCalled -CommandName CertReq.exe -Exactly 3
                }
            }

            Context 'enterprise CA, URL for CEP and CES passed, credentials passed' {
                Mock -CommandName Set-Content -ParameterFilter {
                    $Path -eq 'CertReq-Test.inf' -and `
                    $Value -eq $certInf
                }

                Mock -CommandName Get-ChildItem -Mockwith { } `
                    -ParameterFilter { $Path -eq 'Cert:\LocalMachine\My' }

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

                It 'Should call expected mocks' {
                    Assert-MockCalled -CommandName Join-Path -Exactly 1

                    Assert-MockCalled -CommandName Test-Path -Exactly 1 `
                        -ParameterFilter { $Path -eq 'CertReq-Test.req' }

                    Assert-MockCalled -CommandName Test-Path  -Exactly 1 `
                        -ParameterFilter { $Path -eq 'CertReq-Test.cer' }

                    Assert-MockCalled -CommandName Set-Content -Exactly 1 `
                        -ParameterFilter {
                            $Path -eq 'CertReq-Test.inf' -and `
                            $Value -eq $certInf
                        }

                    Assert-MockCalled -CommandName CertReq.exe -Exactly 3
                }
            }

            Context 'Auto-discovered CA, autorenew is false, credentials passed' {
                Mock -CommandName Get-ChildItem -Mockwith { } `
                    -ParameterFilter { $Path -eq 'Cert:\LocalMachine\My' }

                Mock -CommandName Get-Content -Mockwith { 'Output' } `
                    -ParameterFilter { $Path -eq 'CertReq-Test.out' }

                Mock -CommandName Remove-Item `
                    -ParameterFilter { $Path -eq 'CertReq-Test.out' }

                Mock -CommandName Import-Module

                Mock -CommandName Start-Win32Process

                Mock -CommandName Wait-Win32ProcessStop

                Mock -CommandName Find-CertificateAuthority -MockWith {
                    return New-Object -TypeName psobject -Property @{
                        CARootName = "ContosoCA"
                        CAServerFQDN = "ContosoVm.contoso.com"
                    }
                }

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

                It 'Should call expected mocks' {
                    Assert-MockCalled -CommandName Join-Path -Exactly 1

                    Assert-MockCalled -CommandName Test-Path -Exactly 1 `
                        -ParameterFilter { $Path -eq 'CertReq-Test.req' }

                    Assert-MockCalled -CommandName Test-Path  -Exactly 1 `
                        -ParameterFilter { $Path -eq 'CertReq-Test.cer' }

                    Assert-MockCalled -CommandName Set-Content -Exactly 1 `
                        -ParameterFilter {
                            $Path -eq 'CertReq-Test.inf' -and `
                            $Value -eq $certInf
                        }

                    Assert-MockCalled -CommandName CertReq.exe -Exactly 2

                    Assert-MockCalled -CommandName Start-Win32Process -ModuleName MSFT_CertReq -Exactly 1

                    Assert-MockCalled -CommandName Wait-Win32ProcessStop -ModuleName MSFT_CertReq -Exactly 1

                    Assert-MockCalled -CommandName Test-Path  -Exactly 1 `
                        -ParameterFilter { $Path -eq 'CertReq-Test.out' }

                    Assert-MockCalled -CommandName Get-Content -Exactly 1 `
                        -ParameterFilter { $Path -eq 'CertReq-Test.out' }

                    Assert-MockCalled -CommandName Remove-Item -Exactly 1 `
                        -ParameterFilter { $Path -eq 'CertReq-Test.out' }

                    Assert-MockCalled -CommandName Find-CertificateAuthority -Exactly -Times 1
                }
            }
        }
        #endregion

        Describe "$dscResourceName\Test-TargetResource" {
            Mock -CommandName Find-CertificateAuthority -MockWith {
                    return New-Object -TypeName psobject -Property @{
                        CARootName = "ContosoCA"
                        CAServerFQDN = "ContosoVm.contoso.com"
                    }
                }

            It 'Should return a bool' {
                Test-TargetResource @paramsStandard -Verbose | Should -BeOfType Boolean
            }

            Context 'A valid certificate does not exist' {
                It 'Should return false' {
                    Mock Get-ChildItem -ParameterFilter { $Path -eq 'Cert:\LocalMachine\My' } `
                        -Mockwith { }

                    Test-TargetResource @paramsStandard -Verbose | Should -Be $false
                }
            }

            Context 'A valid certificate already exists and is not about to expire' {
                It 'Should return true' {
                    Mock Get-ChildItem -ParameterFilter { $Path -eq 'Cert:\LocalMachine\My' } `
                        -Mockwith { $validCert }

                    Mock Get-CertificateTemplateName -MockWith { $certificateTemplate }

                    Mock Get-CertificateSan -MockWith { $subjectAltName }

                    Test-TargetResource @paramsStandard -Verbose | Should -Be $true
                }
            }

            Context 'A valid certificate already exists and is about to expire and autorenew set' {
                It 'Should return true' {
                    Mock Get-ChildItem -ParameterFilter { $Path -eq 'Cert:\LocalMachine\My' } `
                        -Mockwith { $expiringCert }

                    Mock Get-CertificateTemplateName -MockWith { $certificateTemplate }

                    Mock Get-CertificateSan -MockWith { $subjectAltName }

                    Test-TargetResource @paramsAutoRenew -Verbose | Should -Be $true
                }
            }

            Context 'A valid certificate already exists and DNS SANs match' {
                It 'Should return true' {
                    Mock Get-ChildItem -ParameterFilter { $Path -eq 'Cert:\LocalMachine\My' } `
                        -Mockwith { $validSANCert }

                    Mock Get-CertificateTemplateName -MockWith { $certificateTemplate }

                    Test-TargetResource @paramsSubjectAltName -Verbose | Should -Be $true
                }
            }

            Context 'A certificate exists but contains incorrect DNS SANs' {
                It 'Should return false' {
                    Mock Get-ChildItem -ParameterFilter { $Path -eq 'Cert:\LocalMachine\My' } `
                        -Mockwith { $incorrectSANCert }

                    Mock Get-CertificateTemplateName -MockWith { $certificateTemplate }

                    Test-TargetResource @paramsSubjectAltName -Verbose | Should -Be $false
                }
            }

            Context 'A certificate exists but does not contain specified DNS SANs' {
                It 'Should return false' {
                    Mock Get-ChildItem -ParameterFilter { $Path -eq 'Cert:\LocalMachine\My' } `
                        -Mockwith { $emptySANCert }

                    Mock Get-CertificateTemplateName -MockWith { $certificateTemplate }

                    Test-TargetResource @paramsSubjectAltName -Verbose | Should -Be $false
                }
            }

            Context 'A certificate exists but does not match the Friendly Name' {
                It 'Should return false' {
                    Mock Get-ChildItem -ParameterFilter { $Path -eq 'Cert:\LocalMachine\My' } `
                        -Mockwith { $incorrectFriendlyName }

                    Mock Get-CertificateTemplateName -MockWith { $certificateTemplate }

                    Test-TargetResource @paramsStandard -Verbose | Should -Be $false
                }
            }

            Context 'When Auto auto-discover of the CA is enabled' {
                It 'Should return false' {
                    Test-TargetResource @paramsAutoDiscovery -Verbose | Should -Be $false
                }

                It 'Should execute the auto-discovery function' {
                    Assert-MockCalled -CommandName Find-CertificateAuthority -Exactly -Times 1
                }
            }
        }
    }
}
finally
{
    #region FOOTER
    Restore-TestEnvironment -TestEnvironment $TestEnvironment
    #endregion
}