tests/Get-AuditPoliciesDrift.Tests.ps1

BeforeAll {
    # Import Core module (required for Write-Log)
    $corePath = (Resolve-Path "$PSScriptRoot\..\modules\Core.psm1").Path
    Import-Module $corePath -Force

    # Load the function directly to avoid module import side effects
    $functionPath = (Resolve-Path "$PSScriptRoot\..\functions\System\Drift\Get-AuditPoliciesDrift.ps1").Path
    . $functionPath

    # Load test fixtures
    $script:fixtures = Get-Content "$PSScriptRoot\fixtures\AccountPoliciesScenarios.json" | ConvertFrom-Json
}

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

Describe "Get-AuditPoliciesDrift" {
    Context "Output Structure & Return Values" {
        BeforeEach {
            Mock -CommandName 'Write-Log'
        }

        It "returns empty array when audit policies are compliant" {
            Mock -CommandName '_GetAuditPolicyOutput' -MockWith {
                return 'Success and Failure'
            }

            $result = Get-AuditPoliciesDrift
            $result | Should -BeNullOrEmpty
        }

        It "returns PSCustomObject when audit policy drift detected" {
            Mock -CommandName '_GetAuditPolicyOutput' -ParameterFilter {
                $SubcategoryName -eq 'Logon'
            } -MockWith {
                return 'Logon,Not Configured'
            }
            Mock -CommandName '_GetAuditPolicyOutput' -ParameterFilter {
                $SubcategoryName -eq 'Sensitive Privilege Use'
            } -MockWith {
                return 'Sensitive Privilege Use,Success and Failure'
            }

            $result = Get-AuditPoliciesDrift
            $result | Should -Not -BeNullOrEmpty
            $result[0] -is [System.Management.Automation.PSCustomObject] | Should -Be $true
        }

        It "includes required properties in drift objects" {
            Mock -CommandName '_GetAuditPolicyOutput' -ParameterFilter {
                $SubcategoryName -eq 'Logon'
            } -MockWith {
                return 'Logon,Not Configured'
            }
            Mock -CommandName '_GetAuditPolicyOutput' -ParameterFilter {
                $SubcategoryName -eq 'Sensitive Privilege Use'
            } -MockWith {
                return 'Sensitive Privilege Use,Success and Failure'
            }

            $result = Get-AuditPoliciesDrift
            $result[0].PSObject.Properties.Name | Should -Contain 'Category'
            $result[0].PSObject.Properties.Name | Should -Contain 'Setting'
            $result[0].PSObject.Properties.Name | Should -Contain 'Expected'
            $result[0].PSObject.Properties.Name | Should -Contain 'Actual'
            $result[0].PSObject.Properties.Name | Should -Contain 'Status'
            $result[0].PSObject.Properties.Name | Should -Contain 'Severity'
        }
    }

    Context "Audit Policy Drift Detection" {
        BeforeEach {
            Mock -CommandName 'Write-Log'
        }

        It "detects drift when Logon audit policy not configured" {
            Mock -CommandName '_GetAuditPolicyOutput' -ParameterFilter {
                $SubcategoryName -eq 'Logon'
            } -MockWith {
                return 'Logon,Not Configured'
            }
            Mock -CommandName '_GetAuditPolicyOutput' -ParameterFilter {
                $SubcategoryName -eq 'Sensitive Privilege Use'
            } -MockWith {
                return 'Sensitive Privilege Use,Success and Failure'
            }

            $result = Get-AuditPoliciesDrift
            $result | Should -Not -BeNullOrEmpty
            ($result | Where-Object Setting -eq 'Logon Auditing').Status | Should -Be 'DRIFT'
        }

        It "detects drift when audit policy only has Success configured" {
            Mock -CommandName '_GetAuditPolicyOutput' -ParameterFilter {
                $SubcategoryName -eq 'Logon'
            } -MockWith {
                return 'Logon,Success'
            }
            Mock -CommandName '_GetAuditPolicyOutput' -ParameterFilter {
                $SubcategoryName -eq 'Sensitive Privilege Use'
            } -MockWith {
                return 'Sensitive Privilege Use,Success and Failure'
            }

            $result = Get-AuditPoliciesDrift
            $result | Should -Not -BeNullOrEmpty
            ($result | Where-Object Setting -eq 'Logon Auditing').Status | Should -Be 'DRIFT'
        }

        It "detects drift when Sensitive Privilege Use audit policy not configured" {
            Mock -CommandName '_GetAuditPolicyOutput' -ParameterFilter {
                $SubcategoryName -eq 'Logon'
            } -MockWith {
                return 'Logon,Success and Failure'
            }
            Mock -CommandName '_GetAuditPolicyOutput' -ParameterFilter {
                $SubcategoryName -eq 'Sensitive Privilege Use'
            } -MockWith {
                return 'Sensitive Privilege Use,Not Configured'
            }

            $result = Get-AuditPoliciesDrift
            $result | Should -Not -BeNullOrEmpty
            ($result | Where-Object Setting -eq 'Sensitive Privilege Use Auditing').Status | Should -Be 'DRIFT'
        }

        It "detects drift in both Logon and Sensitive Privilege Use when both misconfigured" {
            Mock -CommandName '_GetAuditPolicyOutput' -ParameterFilter {
                $SubcategoryName -eq 'Logon'
            } -MockWith {
                return 'Logon,Success'
            }
            Mock -CommandName '_GetAuditPolicyOutput' -ParameterFilter {
                $SubcategoryName -eq 'Sensitive Privilege Use'
            } -MockWith {
                return 'Sensitive Privilege Use,Failure'
            }

            $result = Get-AuditPoliciesDrift
            $result.Count | Should -Be 2
            $result[0].Status | Should -Be 'DRIFT'
            $result[1].Status | Should -Be 'DRIFT'
        }

        It "marks compliant audit policies as having no drift" {
            Mock -CommandName '_GetAuditPolicyOutput' -MockWith {
                return 'Success and Failure'
            }

            $result = Get-AuditPoliciesDrift
            $result | Should -BeNullOrEmpty
        }

        It "sets severity level to MEDIUM for audit policy drift" {
            Mock -CommandName '_GetAuditPolicyOutput' -ParameterFilter {
                $SubcategoryName -eq 'Logon'
            } -MockWith {
                return 'Logon,Not Configured'
            }
            Mock -CommandName '_GetAuditPolicyOutput' -ParameterFilter {
                $SubcategoryName -eq 'Sensitive Privilege Use'
            } -MockWith {
                return 'Sensitive Privilege Use,Success and Failure'
            }

            $result = Get-AuditPoliciesDrift
            $result.Severity | Should -Be 'MEDIUM'
        }
    }

    Context "Error Handling & Resilience" {
        BeforeEach {
            Mock -CommandName 'Write-Log'
        }

        It "handles auditpol execution errors gracefully" {
            Mock -CommandName '_GetAuditPolicyOutput' -MockWith {
                throw [System.ComponentModel.Win32Exception]'Access Denied'
            }

            # Should not throw, should continue and log
            { Get-AuditPoliciesDrift } | Should -Not -Throw
        }

        It "logs error when audit policy check fails" {
            Mock -CommandName '_GetAuditPolicyOutput' -MockWith {
                throw 'Error'
            }

            Get-AuditPoliciesDrift | Out-Null
            Assert-MockCalled Write-Log -ParameterFilter {
                $Level -eq 'Error'
            } -Scope It
        }

        It "logs error message with descriptive context" {
            Mock -CommandName '_GetAuditPolicyOutput' -MockWith {
                throw 'Access Denied'
            }

            Get-AuditPoliciesDrift | Out-Null
            Assert-MockCalled Write-Log -ParameterFilter {
                $Message -match 'Error checking audit policies'
            } -Scope It
        }
    }

    Context "Logging Behavior" {
        It "logs audit policy drift finding to Write-Log" {
            Mock -CommandName 'Write-Log'
            Mock -CommandName '_GetAuditPolicyOutput' -MockWith {
                return 'Logon,Not Configured'
            }

            Get-AuditPoliciesDrift | Out-Null
            Assert-MockCalled Write-Log -ParameterFilter {
                $Level -eq 'Warning' -and $Message -match 'Audit Policy drift'
            } -Scope It
        }

        It "includes function caller name in log messages" {
            Mock -CommandName 'Write-Log'
            Mock -CommandName '_GetAuditPolicyOutput' -MockWith {
                return 'Logon,Not Configured'
            }

            Get-AuditPoliciesDrift | Out-Null
            Assert-MockCalled Write-Log -ParameterFilter {
                $Caller -eq 'Get-AuditPoliciesDrift'
            } -Scope It
        }
    }


    Context "Documentation Compliance" {
        It "function is properly defined and callable" {
            Get-Command Get-AuditPoliciesDrift | Should -Not -BeNullOrEmpty
            (Get-Command Get-AuditPoliciesDrift).CommandType | Should -Be 'Function'
        }

        It "has comment-based help with SYNOPSIS" {
            $functionSource = Get-Content $functionPath -Raw
            $functionSource | Should -Match '\.SYNOPSIS'
        }

        It "includes DEPENDENCIES in help documentation" {
            $functionSource = Get-Content $functionPath -Raw
            $functionSource | Should -Match 'DEPENDENCIES'
        }

        It "includes NOTES section in help" {
            $functionSource = Get-Content $functionPath -Raw
            $functionSource | Should -Match '\.NOTES'
        }
    }
}