tests/Get-NetworkSecurityDrift.Tests.ps1

BeforeAll {
    # Load Core module first (ADR-008: Module Import Strategy)
    $coreModulePath = (Resolve-Path "$PSScriptRoot\..\modules\Core.psm1").Path
    Import-Module $coreModulePath -Force

    # Then load System module (which depends on Core)
    $modulePath = (Resolve-Path "$PSScriptRoot\..\modules\System.psm1").Path
    Import-Module $modulePath -Force
}

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

Describe "Get-NetworkSecurityDrift" {
    Context "Function Exists and Help" {
        It "function exists and can be called" {
            { Get-NetworkSecurityDrift -ErrorAction SilentlyContinue } | Should -Not -Throw
        }

        It "has complete help documentation" {
            $help = Get-Help Get-NetworkSecurityDrift
            $help.Synopsis | Should -Not -BeNullOrEmpty
            $help.Description | Should -Not -BeNullOrEmpty
        }

        It "includes parameter descriptions" {
            $help = Get-Help Get-NetworkSecurityDrift
            $help.Parameters.Parameter.Name | Should -Contain 'Profile'
            $help.Parameters.Parameter.Name | Should -Contain 'ComputerName'
            $help.Parameters.Parameter.Name | Should -Contain 'Detailed'
        }

        It "includes example usage" {
            $help = Get-Help Get-NetworkSecurityDrift
            $help.Examples.Example.Count | Should -BeGreaterThan 0
        }
    }

    Context "Parameter Validation" {
        BeforeEach {
            Mock Get-WindowsOptionalFeature { [PSCustomObject]@{ FeatureName = 'SMB1Protocol'; State = 'Disabled' } }
            Mock Get-ItemProperty { [PSCustomObject]@{ } }
            Mock Write-Log { }
        }

        It "works without parameters for local computer" {
            { Get-NetworkSecurityDrift -ErrorAction SilentlyContinue } | Should -Not -Throw
        }

        It "accepts ComputerName parameter" {
            { Get-NetworkSecurityDrift -ComputerName 'localhost' -ErrorAction SilentlyContinue } | Should -Not -Throw
        }

        It "accepts Profile parameter with valid value" {
            { Get-NetworkSecurityDrift -Profile Recommended -ErrorAction SilentlyContinue } | Should -Not -Throw
            { Get-NetworkSecurityDrift -Profile Basis -ErrorAction SilentlyContinue } | Should -Not -Throw
            { Get-NetworkSecurityDrift -Profile Strict -ErrorAction SilentlyContinue } | Should -Not -Throw
        }

        It "rejects invalid Profile parameter" {
            { Get-NetworkSecurityDrift -Profile 'InvalidProfile' -ErrorAction Stop } | Should -Throw
        }

        It "accepts Detailed switch" {
            { Get-NetworkSecurityDrift -Detailed -ErrorAction SilentlyContinue } | Should -Not -Throw
        }

        It "accepts NTLMv2Level parameter with valid range" {
            { Get-NetworkSecurityDrift -NTLMv2Level 5 -ErrorAction SilentlyContinue } | Should -Not -Throw
            { Get-NetworkSecurityDrift -NTLMv2Level 0 -ErrorAction SilentlyContinue } | Should -Not -Throw
        }
    }

    Context "Output Structure" {
        BeforeEach {
            Mock Get-WindowsOptionalFeature { [PSCustomObject]@{ FeatureName = 'SMB1Protocol'; State = 'Disabled' } }
            Mock Get-ItemProperty { [PSCustomObject]@{ LmCompatibilityLevel = 5 } }
            Mock Write-Log { }
        }

        It "returns array of PSCustomObjects" {
            $result = Get-NetworkSecurityDrift -ErrorAction SilentlyContinue
            $result | Should -BeOfType [PSCustomObject]
        }

        It "includes Category property" {
            $result = Get-NetworkSecurityDrift -ErrorAction SilentlyContinue
            $result[0].Category | Should -Be 'Network Security'
        }

        It "includes Setting property" {
            $result = Get-NetworkSecurityDrift -ErrorAction SilentlyContinue
            $result[0].Setting | Should -Not -BeNullOrEmpty
        }

        It "includes Expected property" {
            $result = Get-NetworkSecurityDrift -ErrorAction SilentlyContinue
            $result[0].Expected | Should -Not -BeNullOrEmpty
        }

        It "includes Actual property" {
            $result = Get-NetworkSecurityDrift -ErrorAction SilentlyContinue
            $result[0].Actual | Should -Not -BeNullOrEmpty
        }

        It "includes Status property with valid values" {
            $result = Get-NetworkSecurityDrift -ErrorAction SilentlyContinue
            $result[0].Status | Should -Match '^(DRIFT|COMPLIANT|EXEMPT)$'
        }

        It "includes Severity property with valid values" {
            $result = Get-NetworkSecurityDrift -ErrorAction SilentlyContinue
            $result[0].Severity | Should -Match '^(CRITICAL|HIGH|MEDIUM|LOW|INFO)$'
        }

        It "includes ComputerName property" {
            $result = Get-NetworkSecurityDrift -ErrorAction SilentlyContinue
            $result[0].ComputerName | Should -Not -BeNullOrEmpty
        }
    }

    Context "Basis Profile" {
        BeforeEach {
            Mock Get-WindowsOptionalFeature { [PSCustomObject]@{ FeatureName = 'SMB1Protocol'; State = 'Disabled' } }
            Mock Get-ItemProperty { [PSCustomObject]@{ LmCompatibilityLevel = 5 } }
            Mock Write-Log { }
        }

        It "includes SMB1 Protocol check" {
            $result = Get-NetworkSecurityDrift -Profile Basis -ErrorAction SilentlyContinue
            $result | Where-Object { $_.Setting -eq 'SMB1 Protocol' } | Should -Not -BeNullOrEmpty
        }

        It "includes NTLMv2 check" {
            $result = Get-NetworkSecurityDrift -Profile Basis -ErrorAction SilentlyContinue
            $result | Where-Object { $_.Setting -eq 'NTLM Compatibility Level' } | Should -Not -BeNullOrEmpty
        }

        It "does not include SMB Signing check" {
            $result = Get-NetworkSecurityDrift -Profile Basis -ErrorAction SilentlyContinue
            $result | Where-Object { $_.Setting -eq 'SMB Signing Enforcement' } | Should -BeNullOrEmpty
        }

        It "does not include LDAP Signing check" {
            $result = Get-NetworkSecurityDrift -Profile Basis -ErrorAction SilentlyContinue
            $result | Where-Object { $_.Setting -eq 'LDAP Signing' } | Should -BeNullOrEmpty
        }

        It "returns expected values for Basis profile" {
            $result = Get-NetworkSecurityDrift -Profile Basis -ErrorAction SilentlyContinue
            $smb1 = $result | Where-Object { $_.Setting -eq 'SMB1 Protocol' }
            $smb1.Expected | Should -Be 'Disabled'
        }
    }

    Context "Recommended Profile" {
        BeforeEach {
            Mock Get-WindowsOptionalFeature { [PSCustomObject]@{ FeatureName = 'SMB1Protocol'; State = 'Disabled' } }
            Mock Get-ItemProperty {
                param($Path, $Name)
                switch ($Name) {
                    'LmCompatibilityLevel' {
                        [PSCustomObject]@{ LmCompatibilityLevel = 5 }
                    }
                    'RequireSecuritySignature' {
                        [PSCustomObject]@{ RequireSecuritySignature = 1 }
                    }
                    'LDAPClientIntegrity' {
                        [PSCustomObject]@{ LDAPClientIntegrity = 1 }
                    }
                    'EnableMulticast' {
                        [PSCustomObject]@{ EnableMulticast = 0 }
                    }
                    default {
                        [PSCustomObject]@{ }
                    }
                }
            }
            Mock Write-Log { }
        }

        It "includes all Basis checks" {
            $result = Get-NetworkSecurityDrift -Profile Recommended -ErrorAction SilentlyContinue
            $result | Where-Object { $_.Setting -eq 'SMB1 Protocol' } | Should -Not -BeNullOrEmpty
            $result | Where-Object { $_.Setting -eq 'NTLM Compatibility Level' } | Should -Not -BeNullOrEmpty
        }

        It "includes SMB Signing check" {
            $result = Get-NetworkSecurityDrift -Profile Recommended -ErrorAction SilentlyContinue
            $result | Where-Object { $_.Setting -eq 'SMB Signing Enforcement' } | Should -Not -BeNullOrEmpty
        }

        It "includes LDAP Signing check" {
            $result = Get-NetworkSecurityDrift -Profile Recommended -ErrorAction SilentlyContinue
            $result | Where-Object { $_.Setting -eq 'LDAP Signing' } | Should -Not -BeNullOrEmpty
        }

        It "includes LLMNR check" {
            $result = Get-NetworkSecurityDrift -Profile Recommended -ErrorAction SilentlyContinue
            $result | Where-Object { $_.Setting -Match 'LLMNR' } | Should -Not -BeNullOrEmpty
        }

        It "does not include Kerberos check by default" {
            $result = Get-NetworkSecurityDrift -Profile Recommended -ErrorAction SilentlyContinue
            $result | Where-Object { $_.Setting -Match 'Kerberos' } | Should -BeNullOrEmpty
        }

        It "enforces NTLMv2 level 5" {
            $result = Get-NetworkSecurityDrift -Profile Recommended -ErrorAction SilentlyContinue
            $ntlm = $result | Where-Object { $_.Setting -eq 'NTLM Compatibility Level' }
            $ntlm.Expected | Should -Match '5'
        }
    }

    Context "Strict Profile" {
        BeforeEach {
            Mock Get-WindowsOptionalFeature { [PSCustomObject]@{ FeatureName = 'SMB1Protocol'; State = 'Disabled' } }
            Mock Get-ItemProperty {
                param($Path, $Name)
                switch ($Name) {
                    'LmCompatibilityLevel' {
                        [PSCustomObject]@{ LmCompatibilityLevel = 5 }
                    }
                    'RequireSecuritySignature' {
                        [PSCustomObject]@{ RequireSecuritySignature = 1 }
                    }
                    'LDAPClientIntegrity' {
                        [PSCustomObject]@{ LDAPClientIntegrity = 1 }
                    }
                    'EnableMulticast' {
                        [PSCustomObject]@{ EnableMulticast = 0 }
                    }
                    'SMBEncryptionRequired' {
                        [PSCustomObject]@{ SMBEncryptionRequired = 1 }
                    }
                    'SupportedEncryptionTypes' {
                        [PSCustomObject]@{ SupportedEncryptionTypes = 0xFFFFFFFF }
                    }
                    default {
                        [PSCustomObject]@{ }
                    }
                }
            }
            Mock Get-NetFirewallProfile { [PSCustomObject]@{ Name = 'Domain'; PolicyStore = 'PersistentStore' } }
            Mock Write-Log { }
        }

        It "includes all Recommended checks" {
            $result = Get-NetworkSecurityDrift -Profile Strict -ErrorAction SilentlyContinue
            $result | Where-Object { $_.Setting -eq 'SMB Signing Enforcement' } | Should -Not -BeNullOrEmpty
            $result | Where-Object { $_.Setting -eq 'LDAP Signing' } | Should -Not -BeNullOrEmpty
        }

        It "includes SMB Encryption check" {
            $result = Get-NetworkSecurityDrift -Profile Strict -ErrorAction SilentlyContinue
            $result | Where-Object { $_.Setting -eq 'SMB Encryption' } | Should -Not -BeNullOrEmpty
        }

        It "includes Kerberos check" {
            $result = Get-NetworkSecurityDrift -Profile Strict -ErrorAction SilentlyContinue
            $result | Where-Object { $_.Setting -Match 'Kerberos' } | Should -Not -BeNullOrEmpty
        }

        It "includes TLS check with -Detailed" {
            $result = Get-NetworkSecurityDrift -Profile Strict -Detailed -ErrorAction SilentlyContinue
            $result | Where-Object { $_.Setting -Match 'TLS' } | Should -Not -BeNullOrEmpty
        }

        It "includes IPsec check with -Detailed" {
            $result = Get-NetworkSecurityDrift -Profile Strict -Detailed -ErrorAction SilentlyContinue
            $result | Where-Object { $_.Setting -Match 'IPsec' } | Should -Not -BeNullOrEmpty
        }
    }

    Context "Detailed Output" {
        BeforeEach {
            Mock Get-WindowsOptionalFeature { [PSCustomObject]@{ FeatureName = 'SMB1Protocol'; State = 'Enabled' } }
            Mock Get-ItemProperty { [PSCustomObject]@{ } }
            Mock Write-Log { }
        }

        It "-Detailed flag is accepted" {
            { Get-NetworkSecurityDrift -Detailed -ErrorAction SilentlyContinue } | Should -Not -Throw
        }

        It "detects drift items when present" {
            $result = Get-NetworkSecurityDrift -Profile Recommended -ErrorAction SilentlyContinue
            $result | Where-Object { $_.Status -eq 'DRIFT' } | Should -Not -BeNullOrEmpty
        }
    }

    Context "ReportDriftOnly Flag" {
        BeforeEach {
            Mock Get-WindowsOptionalFeature { [PSCustomObject]@{ FeatureName = 'SMB1Protocol'; State = 'Enabled' } }
            Mock Get-ItemProperty { [PSCustomObject]@{ LmCompatibilityLevel = 5 } }
            Mock Write-Log { }
        }

        It "returns only DRIFT status items when ReportDriftOnly specified" {
            $result = Get-NetworkSecurityDrift -ReportDriftOnly -ErrorAction SilentlyContinue
            if ($result.Count -gt 0) {
                $result | Where-Object { $_.Status -ne 'DRIFT' } | Should -BeNullOrEmpty
            }
        }

        It "filters out COMPLIANT items correctly" {
            $result = Get-NetworkSecurityDrift -ReportDriftOnly -ErrorAction SilentlyContinue
            if ($result.Count -gt 0) {
                $result | ForEach-Object { $_.Status | Should -Be 'DRIFT' }
            }
        }
    }

    Context "WhatIf Support" {
        BeforeEach {
            Mock Get-WindowsOptionalFeature { [PSCustomObject]@{ FeatureName = 'SMB1Protocol'; State = 'Disabled' } }
            Mock Get-ItemProperty { [PSCustomObject]@{ } }
            Mock Write-Log { }
        }

        It "supports -WhatIf parameter" {
            { Get-NetworkSecurityDrift -WhatIf -ErrorAction SilentlyContinue } | Should -Not -Throw
        }

        It "executes without error with -WhatIf" {
            $result = Get-NetworkSecurityDrift -WhatIf -ErrorAction SilentlyContinue
            $result | Should -Not -BeNull
        }
    }

    Context "Default Parameters" {
        BeforeEach {
            Mock Get-WindowsOptionalFeature { [PSCustomObject]@{ FeatureName = 'SMB1Protocol'; State = 'Disabled' } }
            Mock Get-ItemProperty { [PSCustomObject]@{ } }
            Mock Write-Log { }
        }

        It "uses 'localhost' as default ComputerName" {
            $result = Get-NetworkSecurityDrift -ErrorAction SilentlyContinue
            $result[0].ComputerName | Should -Be 'localhost'
        }

        It "uses 'Recommended' as default Profile" {
            Mock Get-ItemProperty {
                param($Path, $Name)
                if ($Name -eq 'RequireSecuritySignature') {
                    [PSCustomObject]@{ RequireSecuritySignature = $null }
                }
                else {
                    [PSCustomObject]@{ }
                }
            }
            $result = Get-NetworkSecurityDrift -ErrorAction SilentlyContinue
            $smbSigning = $result | Where-Object { $_.Setting -eq 'SMB Signing Enforcement' }
            $smbSigning | Should -Not -BeNullOrEmpty
        }
    }

    Context "Drift Detection Accuracy" {
        It "detects SMB1 Protocol drift" {
            Mock Get-WindowsOptionalFeature { [PSCustomObject]@{ FeatureName = 'SMB1Protocol'; State = 'Enabled' } }
            Mock Get-ItemProperty { [PSCustomObject]@{ } }
            Mock Write-Log { }

            $result = Get-NetworkSecurityDrift -Profile Basis -ErrorAction SilentlyContinue
            $smb1 = $result | Where-Object { $_.Setting -eq 'SMB1 Protocol' }
            $smb1.Status | Should -Be 'DRIFT'
            $smb1.Severity | Should -Be 'CRITICAL'
        }

        It "detects NTLMv2 drift when level too low" {
            Mock Get-WindowsOptionalFeature { [PSCustomObject]@{ FeatureName = 'SMB1Protocol'; State = 'Disabled' } }
            Mock Get-ItemProperty { [PSCustomObject]@{ LmCompatibilityLevel = 3 } }
            Mock Write-Log { }

            $result = Get-NetworkSecurityDrift -Profile Recommended -ErrorAction SilentlyContinue
            $ntlm = $result | Where-Object { $_.Setting -eq 'NTLM Compatibility Level' }
            $ntlm.Status | Should -Be 'DRIFT'
        }

        It "marks compliant SMB1 as COMPLIANT" {
            Mock Get-WindowsOptionalFeature { [PSCustomObject]@{ FeatureName = 'SMB1Protocol'; State = 'Disabled' } }
            Mock Get-ItemProperty { [PSCustomObject]@{ } }
            Mock Write-Log { }

            $result = Get-NetworkSecurityDrift -Profile Basis -ErrorAction SilentlyContinue
            $smb1 = $result | Where-Object { $_.Setting -eq 'SMB1 Protocol' }
            $smb1.Status | Should -Be 'COMPLIANT'
        }
    }

    Context "Severity Classification" {
        BeforeEach {
            Mock Get-WindowsOptionalFeature { [PSCustomObject]@{ FeatureName = 'SMB1Protocol'; State = 'Enabled' } }
            Mock Get-ItemProperty { [PSCustomObject]@{ } }
            Mock Write-Log { }
        }

        It "assigns CRITICAL severity to SMB1" {
            $result = Get-NetworkSecurityDrift -ErrorAction SilentlyContinue
            $smb1 = $result | Where-Object { $_.Setting -eq 'SMB1 Protocol' }
            $smb1.Severity | Should -Be 'CRITICAL'
        }

        It "assigns HIGH severity to NTLMv2" {
            $result = Get-NetworkSecurityDrift -ErrorAction SilentlyContinue
            $ntlm = $result | Where-Object { $_.Setting -eq 'NTLM Compatibility Level' }
            $ntlm.Severity | Should -Be 'HIGH'
        }

        It "assigns appropriate severity to each check" {
            $result = Get-NetworkSecurityDrift -Profile Strict -Detailed -ErrorAction SilentlyContinue
            $result | ForEach-Object {
                $_.Severity | Should -Match '^(CRITICAL|HIGH|MEDIUM|LOW|INFO)$'
            }
        }
    }

    Context "Error Handling" {
        It "continues processing after individual check failure" {
            Mock Get-WindowsOptionalFeature { throw "Access denied" }
            Mock Get-ItemProperty { [PSCustomObject]@{ LmCompatibilityLevel = 5 } }
            Mock Write-Log { }

            { Get-NetworkSecurityDrift -ErrorAction SilentlyContinue } | Should -Not -Throw
        }

        It "returns results even when some checks fail" {
            Mock Get-WindowsOptionalFeature { throw "Error" }
            Mock Get-ItemProperty { [PSCustomObject]@{ LmCompatibilityLevel = 5 } }
            Mock Write-Log { }

            $result = Get-NetworkSecurityDrift -ErrorAction SilentlyContinue
            $result | Should -Not -BeNull
        }
    }
}