tests/Get-AccountPoliciesDrift.Tests.ps1

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

    # Load System.Test module (contains only Get-AccountPoliciesDrift, avoids elevation issues)
    $testModulePath = Resolve-Path "$PSScriptRoot\..\modules\System.Test.psm1"
    Import-Module $testModulePath -Force

    # Load test data
    $fixturesPath = "$PSScriptRoot\fixtures\AccountPoliciesScenarios.json"
    if (Test-Path $fixturesPath) {
        $scenarios = Get-Content -Path $fixturesPath | ConvertFrom-Json
    }
}

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

Describe "Get-AccountPoliciesDrift" {
    Context "Compliant System" {
        BeforeEach {
            Mock Get-ItemProperty {
                if ($Name -eq "MinimumPasswordLength") {
                    return [PSCustomObject]@{ MinimumPasswordLength = 12 }
                }
                elseif ($Name -eq "PasswordComplexity") {
                    return [PSCustomObject]@{ PasswordComplexity = 1 }
                }
            } -ModuleName System.Test

            Mock Write-Log -ModuleName System.Test
        }

        It "returns empty array when all policies are compliant" {
            $result = Get-AccountPoliciesDrift

            $result | Should -BeNullOrEmpty
        }

        It "does not log any drift when compliant" {
            Get-AccountPoliciesDrift

            Assert-MockCalled Write-Log -Times 0 -ModuleName System.Test
        }
    }

    Context "Drift Detection - Minimum Password Length" {
        BeforeEach {
            Mock Get-ItemProperty {
                if ($Name -eq "MinimumPasswordLength") {
                    return [PSCustomObject]@{ MinimumPasswordLength = 8 }
                }
                elseif ($Name -eq "PasswordComplexity") {
                    return [PSCustomObject]@{ PasswordComplexity = 1 }
                }
                return $null
            } -ModuleName System.Test -ParameterFilter { $Path -match "Netlogon" }

            Mock Write-Log -ModuleName System.Test
        }

        It "detects password length drift" {
            InModuleScope System.Test {
                $result = Get-AccountPoliciesDrift

                $result | Should -Not -BeNullOrEmpty
                $result.Count | Should -Be 1
            }
        }

        It "includes drift setting name in result" {
            $result = Get-AccountPoliciesDrift

            $result.Setting | Should -Contain "Minimum Password Length"
        }

        It "includes expected and actual values" {
            $result = Get-AccountPoliciesDrift

            $result[0].Expected | Should -Match "12 characters"
            $result[0].Actual | Should -Match "8 characters"
        }

        It "marks finding as DRIFT status" {
            $result = Get-AccountPoliciesDrift

            $result[0].Status | Should -Be "DRIFT"
        }

        It "marks finding as HIGH severity" {
            $result = Get-AccountPoliciesDrift

            $result[0].Severity | Should -Be "HIGH"
        }

        It "includes Account Policy category" {
            $result = Get-AccountPoliciesDrift

            $result[0].Category | Should -Be "Account Policy"
        }

        It "logs drift warning message" {
            Get-AccountPoliciesDrift

            Assert-MockCalled Write-Log -ModuleName System.Test -ParameterFilter {
                $Level -eq "Warning" -and $Message -match "Min password length"
            }
        }

        It "accepts custom minimum password length parameter" {
            Mock Get-ItemProperty {
                if ($Name -eq "MinimumPasswordLength") {
                    return [PSCustomObject]@{ MinimumPasswordLength = 10 }
                }
                elseif ($Name -eq "PasswordComplexity") {
                    return [PSCustomObject]@{ PasswordComplexity = 1 }
                }
                return $null
            } -ModuleName System.Test -ParameterFilter { $Path -match "Netlogon" }

            $result = Get-AccountPoliciesDrift -MinimumPasswordLength 10

            $result | Should -BeNullOrEmpty
        }
    }

    Context "Drift Detection - Password Complexity" {
        BeforeEach {
            Mock Get-ItemProperty {
                if ($Name -eq "MinimumPasswordLength") {
                    return [PSCustomObject]@{ MinimumPasswordLength = 12 }
                }
                elseif ($Name -eq "PasswordComplexity") {
                    return [PSCustomObject]@{ PasswordComplexity = 0 }
                }
            } -ModuleName System.Test

            Mock Write-Log -ModuleName System.Test
        }

        It "detects password complexity drift" {
            InModuleScope System.Test {
                $result = Get-AccountPoliciesDrift

                $result | Should -Not -BeNullOrEmpty
                $result.Count | Should -Be 1
            }
        }

        It "indicates complexity is disabled when expected enabled" {
            $result = Get-AccountPoliciesDrift

            $result[0].Expected | Should -Match "Enabled"
            $result[0].Actual | Should -Match "Disabled"
        }

        It "allows disabling complexity requirement" {
            $result = Get-AccountPoliciesDrift -RequirePasswordComplexity $false

            $result | Should -BeNullOrEmpty
        }

        It "logs complexity drift warning" {
            Get-AccountPoliciesDrift

            Assert-MockCalled Write-Log -ModuleName System.Test -ParameterFilter {
                $Level -eq "Warning" -and $Message -match "Password complexity"
            }
        }
    }

    Context "Multiple Drifts" {
        BeforeEach {
            Mock Get-ItemProperty {
                if ($Name -eq "MinimumPasswordLength") {
                    return [PSCustomObject]@{ MinimumPasswordLength = 6 }
                }
                elseif ($Name -eq "PasswordComplexity") {
                    return [PSCustomObject]@{ PasswordComplexity = 0 }
                }
            } -ModuleName System.Test

            Mock Write-Log -ModuleName System.Test
        }

        It "detects multiple policy drifts" {
            $result = Get-AccountPoliciesDrift

            $result.Count | Should -Be 2
        }

        It "includes both password length and complexity drifts" {
            $result = Get-AccountPoliciesDrift

            $result.Setting | Should -Contain "Minimum Password Length"
            $result.Setting | Should -Contain "Password Complexity"
        }

        It "marks all drifts as HIGH severity" {
            $result = Get-AccountPoliciesDrift

            $result | ForEach-Object { $_.Severity | Should -Be "HIGH" }
        }
    }

    Context "Registry Access Failure" {
        BeforeEach {
            Mock Get-ItemProperty {
                throw [System.Management.Automation.ItemNotFoundException]::new("Registry key not found")
            } -ModuleName System.Test

            Mock Write-Log -ModuleName System.Test
        }

        It "throws error when registry access fails" {
            { Get-AccountPoliciesDrift -ErrorAction Stop } | Should -Throw
        }

        It "logs error message" {
            InModuleScope System.Test {
                { Get-AccountPoliciesDrift -ErrorAction SilentlyContinue } | Out-Null

                Assert-MockCalled Write-Log -ParameterFilter {
                    $Level -eq "Error"
                }
            }
        }
    }

    Context "Missing Registry Values" {
        BeforeEach {
            Mock Get-ItemProperty {
                return $null
            } -ModuleName System.Test

            Mock Write-Log -ModuleName System.Test
        }

        It "handles missing minimum password length gracefully" {
            $result = Get-AccountPoliciesDrift -ErrorAction SilentlyContinue

            $result.Count | Should -Be 2
        }

        It "detects drift when values are null" {
            $result = Get-AccountPoliciesDrift -ErrorAction SilentlyContinue

            $result[0].Actual | Should -Match "characters"
        }
    }

    Context "Parameter Validation" {
        BeforeEach {
            Mock Get-ItemProperty {
                if ($Name -eq "MinimumPasswordLength") {
                    return [PSCustomObject]@{ MinimumPasswordLength = 12 }
                }
                elseif ($Name -eq "PasswordComplexity") {
                    return [PSCustomObject]@{ PasswordComplexity = 1 }
                }
            } -ModuleName System.Test

            Mock Write-Log -ModuleName System.Test
        }

        It "accepts MinimumPasswordLength parameter" {
            $result = Get-AccountPoliciesDrift -MinimumPasswordLength 14

            $result | Should -Not -BeNullOrEmpty
        }

        It "accepts RequirePasswordComplexity parameter" {
            Mock Get-ItemProperty {
                if ($Name -eq "MinimumPasswordLength") {
                    return [PSCustomObject]@{ MinimumPasswordLength = 12 }
                }
                elseif ($Name -eq "PasswordComplexity") {
                    return [PSCustomObject]@{ PasswordComplexity = 0 }
                }
            } -ModuleName System.Test

            $result = Get-AccountPoliciesDrift -RequirePasswordComplexity $false

            $result | Should -BeNullOrEmpty
        }

        It "supports WhatIf switch" {
            { Get-AccountPoliciesDrift -WhatIf } | Should -Not -Throw
        }
    }

    Context "Return Object Structure" {
        BeforeEach {
            Mock Get-ItemProperty {
                if ($Name -eq "MinimumPasswordLength") {
                    return [PSCustomObject]@{ MinimumPasswordLength = 8 }
                }
                elseif ($Name -eq "PasswordComplexity") {
                    return [PSCustomObject]@{ PasswordComplexity = 1 }
                }
            } -ModuleName System.Test

            Mock Write-Log -ModuleName System.Test
        }

        It "returns PSCustomObject with required properties" {
            $result = Get-AccountPoliciesDrift

            $result[0] | Should -HaveProperty "Category"
            $result[0] | Should -HaveProperty "Setting"
            $result[0] | Should -HaveProperty "Expected"
            $result[0] | Should -HaveProperty "Actual"
            $result[0] | Should -HaveProperty "Status"
            $result[0] | Should -HaveProperty "Severity"
        }

        It "has valid status values" {
            $result = Get-AccountPoliciesDrift

            @("DRIFT", "COMPLIANT") | Should -Contain $result[0].Status
        }

        It "has valid severity values" {
            $result = Get-AccountPoliciesDrift

            @("LOW", "MEDIUM", "HIGH", "CRITICAL") | Should -Contain $result[0].Severity
        }
    }

    Context "Documentation" {
        It "has complete help documentation" {
            $help = Get-Help Get-AccountPoliciesDrift

            $help.Synopsis | Should -Not -BeNullOrEmpty
            $help.Description | Should -Not -BeNullOrEmpty
        }

        It "includes parameter descriptions" {
            $help = Get-Help Get-AccountPoliciesDrift

            $help.Parameters.Parameter.Name | Should -Contain "MinimumPasswordLength"
            $help.Parameters.Parameter.Name | Should -Contain "RequirePasswordComplexity"
        }

        It "includes usage example" {
            $help = Get-Help Get-AccountPoliciesDrift

            $help.Examples | Should -Not -BeNullOrEmpty
        }
    }
}