tests/Get-RDPSecurityDrift.Tests.ps1

BeforeAll {
    $modulePath = (Resolve-Path "$PSScriptRoot\..\modules\System.psm1").Path
    Import-Module $modulePath -Force
}

AfterAll {
    Remove-Module System -Force -ErrorAction SilentlyContinue
}

Describe "Get-RDPSecurityDrift" {

    Context "Parameter Validation" -Fixture {
        It "accepts default parameters" {
            InModuleScope System {
                { Get-RDPSecurityDrift -ErrorAction SilentlyContinue } | Should -Not -Throw
            }
        }

        It "accepts MinRDPEncryptionLevel parameter" {
            InModuleScope System {
                { Get-RDPSecurityDrift -MinRDPEncryptionLevel 2 -ErrorAction SilentlyContinue } | Should -Not -Throw
            }
        }

        It "accepts RequireNLA parameter" {
            InModuleScope System {
                { Get-RDPSecurityDrift -RequireNLA $false -ErrorAction SilentlyContinue } | Should -Not -Throw
            }
        }

        It "accepts RequireCertificate parameter" {
            InModuleScope System {
                { Get-RDPSecurityDrift -RequireCertificate $true -ErrorAction SilentlyContinue } | Should -Not -Throw
            }
        }

        It "accepts MaxIdleTimeMinutes parameter" {
            InModuleScope System {
                { Get-RDPSecurityDrift -MaxIdleTimeMinutes 20 -ErrorAction SilentlyContinue } | Should -Not -Throw
            }
        }

        It "rejects invalid encryption level (0)" {
            InModuleScope System {
                { Get-RDPSecurityDrift -MinRDPEncryptionLevel 0 -ErrorAction Stop } | Should -Throw
            }
        }

        It "rejects invalid encryption level (4)" {
            InModuleScope System {
                { Get-RDPSecurityDrift -MinRDPEncryptionLevel 4 -ErrorAction Stop } | Should -Throw
            }
        }

        It "rejects invalid idle timeout (-1)" {
            InModuleScope System {
                { Get-RDPSecurityDrift -MaxIdleTimeMinutes -1 -ErrorAction Stop } | Should -Throw
            }
        }

        It "accepts idle timeout value 0 (no timeout)" {
            InModuleScope System {
                { Get-RDPSecurityDrift -MaxIdleTimeMinutes 0 -ErrorAction SilentlyContinue } | Should -Not -Throw
            }
        }
    }

    Context "Function Execution" {
        It "returns results without error" {
            InModuleScope System {
                { Get-RDPSecurityDrift -ErrorAction SilentlyContinue } | Should -Not -Throw
            }
        }

        It "returns a collection or null" {
            InModuleScope System {
                $result = Get-RDPSecurityDrift -ErrorAction SilentlyContinue
                ($result -is [System.Collections.IEnumerable] -or $null -eq $result) | Should -Be $true
            }
        }
    }

    Context "Return Value Structure" {
        It "includes Category property in drift findings" {
            InModuleScope System {
                Mock Get-ItemProperty {
                    if ($Name -eq 'fDenyTSConnections') {
                        [PSCustomObject]@{ fDenyTSConnections = 1 }
                    }
                    else {
                        [PSCustomObject]@{
                            MinEncryptionLevel = 1
                            SecurityLayer = 1
                            PortNumber = 5555
                            SSLCertificateSHA1Hash = ''
                            MaxIdleTime = 0
                        }
                    }
                }

                $result = Get-RDPSecurityDrift -MinRDPEncryptionLevel 3 -ErrorAction SilentlyContinue
                $result[0].Category | Should -Not -BeNullOrEmpty
            }
        }

        It "includes Setting property in drift findings" {
            InModuleScope System {
                Mock Get-ItemProperty {
                    if ($Name -eq 'fDenyTSConnections') {
                        [PSCustomObject]@{ fDenyTSConnections = 1 }
                    }
                    else {
                        [PSCustomObject]@{
                            MinEncryptionLevel = 3
                            SecurityLayer = 2
                            PortNumber = 3389
                            SSLCertificateSHA1Hash = ''
                            MaxIdleTime = 0
                        }
                    }
                }

                $result = Get-RDPSecurityDrift -ErrorAction SilentlyContinue
                ($result.Setting -contains 'RDP Service Enabled') | Should -Be $true
            }
        }

        It "includes Expected property in drift findings" {
            InModuleScope System {
                Mock Get-ItemProperty {
                    if ($Name -eq 'fDenyTSConnections') {
                        [PSCustomObject]@{ fDenyTSConnections = 0 }
                    }
                    else {
                        [PSCustomObject]@{
                            MinEncryptionLevel = 1
                            SecurityLayer = 2
                            PortNumber = 3389
                            SSLCertificateSHA1Hash = ''
                            MaxIdleTime = 0
                        }
                    }
                }

                $result = Get-RDPSecurityDrift -MinRDPEncryptionLevel 3 -ErrorAction SilentlyContinue
                $encResult = $result | Where-Object { $_.Setting -eq 'Encryption Level' }
                $encResult.Expected | Should -Not -BeNullOrEmpty
            }
        }

        It "includes Status property" {
            InModuleScope System {
                Mock Get-ItemProperty {
                    if ($Name -eq 'fDenyTSConnections') {
                        [PSCustomObject]@{ fDenyTSConnections = 0 }
                    }
                    else {
                        [PSCustomObject]@{
                            MinEncryptionLevel = 1
                            SecurityLayer = 2
                            PortNumber = 3389
                            SSLCertificateSHA1Hash = ''
                            MaxIdleTime = 0
                        }
                    }
                }

                $result = Get-RDPSecurityDrift -MinRDPEncryptionLevel 3 -ErrorAction SilentlyContinue
                $result[0].Status | Should -Be 'DRIFT'
            }
        }

        It "includes Severity property" {
            InModuleScope System {
                Mock Get-ItemProperty {
                    if ($Name -eq 'fDenyTSConnections') {
                        [PSCustomObject]@{ fDenyTSConnections = 0 }
                    }
                    else {
                        [PSCustomObject]@{
                            MinEncryptionLevel = 3
                            SecurityLayer = 2
                            PortNumber = 5555
                            SSLCertificateSHA1Hash = ''
                            MaxIdleTime = 0
                        }
                    }
                }

                $result = Get-RDPSecurityDrift -ErrorAction SilentlyContinue
                $portResult = $result | Where-Object { $_.Setting -eq 'RDP Port' }
                $portResult.Severity | Should -Match 'HIGH|MEDIUM|LOW'
            }
        }
    }

    Context "Drift Detection" {
        It "detects encryption drift" {
            InModuleScope System {
                Mock Get-ItemProperty {
                    if ($Name -eq 'fDenyTSConnections') {
                        [PSCustomObject]@{ fDenyTSConnections = 0 }
                    }
                    else {
                        [PSCustomObject]@{
                            MinEncryptionLevel = 1
                            SecurityLayer = 2
                            PortNumber = 3389
                            SSLCertificateSHA1Hash = ''
                            MaxIdleTime = 0
                        }
                    }
                }

                $result = Get-RDPSecurityDrift -MinRDPEncryptionLevel 3 -ErrorAction SilentlyContinue
                $encDrift = $result | Where-Object { $_.Setting -eq 'Encryption Level' }
                $encDrift | Should -Not -BeNullOrEmpty
            }
        }

        It "detects NLA drift" {
            InModuleScope System {
                Mock Get-ItemProperty {
                    if ($Name -eq 'fDenyTSConnections') {
                        [PSCustomObject]@{ fDenyTSConnections = 0 }
                    }
                    else {
                        [PSCustomObject]@{
                            MinEncryptionLevel = 3
                            SecurityLayer = 1
                            PortNumber = 3389
                            SSLCertificateSHA1Hash = ''
                            MaxIdleTime = 0
                        }
                    }
                }

                $result = Get-RDPSecurityDrift -RequireNLA $true -ErrorAction SilentlyContinue
                $nlaDrift = $result | Where-Object { $_.Setting -eq 'Network Level Authentication' }
                $nlaDrift | Should -Not -BeNullOrEmpty
            }
        }

        It "detects port drift" {
            InModuleScope System {
                Mock Get-ItemProperty {
                    if ($Name -eq 'fDenyTSConnections') {
                        [PSCustomObject]@{ fDenyTSConnections = 0 }
                    }
                    else {
                        [PSCustomObject]@{
                            MinEncryptionLevel = 3
                            SecurityLayer = 2
                            PortNumber = 5555
                            SSLCertificateSHA1Hash = ''
                            MaxIdleTime = 0
                        }
                    }
                }

                $result = Get-RDPSecurityDrift -ErrorAction SilentlyContinue
                $portDrift = $result | Where-Object { $_.Setting -eq 'RDP Port' }
                $portDrift | Should -Not -BeNullOrEmpty
            }
        }

        It "detects certificate drift when required" {
            InModuleScope System {
                Mock Get-ItemProperty {
                    if ($Name -eq 'fDenyTSConnections') {
                        [PSCustomObject]@{ fDenyTSConnections = 0 }
                    }
                    else {
                        [PSCustomObject]@{
                            MinEncryptionLevel = 3
                            SecurityLayer = 2
                            PortNumber = 3389
                            SSLCertificateSHA1Hash = ''
                            MaxIdleTime = 0
                        }
                    }
                }

                $result = Get-RDPSecurityDrift -RequireCertificate $true -ErrorAction SilentlyContinue
                $certDrift = $result | Where-Object { $_.Setting -eq 'SSL Certificate' }
                $certDrift | Should -Not -BeNullOrEmpty
            }
        }

        It "returns empty when no drifts" {
            InModuleScope System {
                Mock Get-ItemProperty {
                    if ($Name -eq 'fDenyTSConnections') {
                        [PSCustomObject]@{ fDenyTSConnections = 0 }
                    }
                    else {
                        [PSCustomObject]@{
                            MinEncryptionLevel = 3
                            SecurityLayer = 2
                            PortNumber = 3389
                            SSLCertificateSHA1Hash = 'ABC123'
                            MaxIdleTime = 0
                        }
                    }
                }

                $result = Get-RDPSecurityDrift -RequireCertificate $true -MaxIdleTimeMinutes 0 -ErrorAction SilentlyContinue
                ($result -eq $null -or @($result).Count -eq 0) | Should -Be $true
            }
        }
    }

    Context "Error Handling" {
        It "throws terminating error on registry access failure" {
            InModuleScope System {
                Mock Get-ItemProperty { throw "Access denied" }

                { Get-RDPSecurityDrift -ErrorAction Stop } | Should -Throw
            }
        }
    }

    Context "Documentation" {
        It "has complete help documentation" {
            $help = Get-Help Get-RDPSecurityDrift
            $help.Synopsis | Should -Not -BeNullOrEmpty
        }

        It "documents MinRDPEncryptionLevel parameter" {
            $help = Get-Help Get-RDPSecurityDrift -Full
            $help.Parameters.Parameter.Name | Should -Contain 'MinRDPEncryptionLevel'
        }

        It "documents RequireNLA parameter" {
            $help = Get-Help Get-RDPSecurityDrift -Full
            $help.Parameters.Parameter.Name | Should -Contain 'RequireNLA'
        }

        It "documents RequireCertificate parameter" {
            $help = Get-Help Get-RDPSecurityDrift -Full
            $help.Parameters.Parameter.Name | Should -Contain 'RequireCertificate'
        }

        It "documents MaxIdleTimeMinutes parameter" {
            $help = Get-Help Get-RDPSecurityDrift -Full
            $help.Parameters.Parameter.Name | Should -Contain 'MaxIdleTimeMinutes'
        }

        It "includes usage examples" {
            $help = Get-Help Get-RDPSecurityDrift -Full
            $help.Examples | Should -Not -BeNullOrEmpty
        }
    }

}