tests/Get-ServiceSecurityDrift.Tests.ps1

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

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

Describe "Get-ServiceSecurityDrift" {

    Context "Parameter Validation" {
        It "accepts default parameters" {
            InModuleScope System {
                Mock Get-HardeningProfile {
                    return [PSCustomObject]@{
                        ProfileName = 'Recommended'
                        TargetSystem = 'Server'
                        Rules = @(
                            @{ Type = 'Service'; Name = 'Service-Test'; Severity = 'HIGH'; RuleDefinition = @{ ServiceName = 'spooler'; StartType = 'Disabled' } }
                        )
                    }
                }
                Mock Get-Service { }
                Mock Get-CimInstance { return [PSCustomObject]@{ ProductType = 3 } }
                Mock Write-Log { }

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

        It "accepts Profile parameter with Basis" {
            InModuleScope System {
                Mock Get-HardeningProfile {
                    return [PSCustomObject]@{
                        ProfileName = 'Basis'
                        TargetSystem = 'Server'
                        Rules = @()
                    }
                }
                Mock Get-Service { }
                Mock Get-CimInstance { return [PSCustomObject]@{ ProductType = 3 } }
                Mock Write-Log { }

                { Get-ServiceSecurityDrift -Profile Basis -ErrorAction SilentlyContinue } | Should -Not -Throw
            }
        }

        It "accepts Profile parameter with Recommended" {
            InModuleScope System {
                Mock Get-HardeningProfile {
                    return [PSCustomObject]@{
                        ProfileName = 'Recommended'
                        TargetSystem = 'Server'
                        Rules = @()
                    }
                }
                Mock Get-Service { }
                Mock Get-CimInstance { return [PSCustomObject]@{ ProductType = 3 } }
                Mock Write-Log { }

                { Get-ServiceSecurityDrift -Profile Recommended -ErrorAction SilentlyContinue } | Should -Not -Throw
            }
        }

        It "accepts Profile parameter with Strict" {
            InModuleScope System {
                Mock Get-HardeningProfile {
                    return [PSCustomObject]@{
                        ProfileName = 'Strict'
                        TargetSystem = 'Server'
                        Rules = @()
                    }
                }
                Mock Get-Service { }
                Mock Get-CimInstance { return [PSCustomObject]@{ ProductType = 3 } }
                Mock Write-Log { }

                { Get-ServiceSecurityDrift -Profile Strict -ErrorAction SilentlyContinue } | Should -Not -Throw
            }
        }

        It "accepts ComputerName parameter" {
            InModuleScope System {
                Mock Get-HardeningProfile {
                    return [PSCustomObject]@{
                        ProfileName = 'Recommended'
                        TargetSystem = 'Server'
                        Rules = @()
                    }
                }
                Mock Get-Service { }
                Mock Invoke-Command { }
                Mock Write-Log { }

                { Get-ServiceSecurityDrift -ComputerName 'testserver' -ErrorAction SilentlyContinue } | Should -Not -Throw
            }
        }

        It "accepts Credential parameter" {
            InModuleScope System {
                Mock Get-HardeningProfile {
                    return [PSCustomObject]@{
                        ProfileName = 'Recommended'
                        TargetSystem = 'Server'
                        Rules = @()
                    }
                }
                Mock Get-Service { }
                Mock Invoke-Command { }
                Mock Write-Log { }

                # PSScriptAnalyzer ignore: Test credentials
                $credential = New-Object System.Management.Automation.PSCredential('user', (ConvertTo-SecureString 'pass' -AsPlainText -Force)) # PSScriptAnalyzer ignore [PSAvoidUsingConvertToSecureStringWithPlainText]
                { Get-ServiceSecurityDrift -ComputerName 'testserver' -Credential $credential -ErrorAction SilentlyContinue } | Should -Not -Throw
            }
        }

        It "accepts Detailed switch" {
            InModuleScope System {
                Mock Get-HardeningProfile {
                    return [PSCustomObject]@{
                        ProfileName = 'Recommended'
                        TargetSystem = 'Server'
                        Rules = @()
                    }
                }
                Mock Get-Service { }
                Mock Get-CimInstance { return [PSCustomObject]@{ ProductType = 3 } }
                Mock Write-Log { }

                { Get-ServiceSecurityDrift -Detailed -ErrorAction SilentlyContinue } | Should -Not -Throw
            }
        }

        It "accepts ReportDriftOnly switch" {
            InModuleScope System {
                Mock Get-HardeningProfile {
                    return [PSCustomObject]@{
                        ProfileName = 'Recommended'
                        TargetSystem = 'Server'
                        Rules = @()
                    }
                }
                Mock Get-Service { }
                Mock Get-CimInstance { return [PSCustomObject]@{ ProductType = 3 } }
                Mock Write-Log { }

                { Get-ServiceSecurityDrift -ReportDriftOnly -ErrorAction SilentlyContinue } | Should -Not -Throw
            }
        }

        It "rejects invalid Profile value" {
            InModuleScope System {
                Mock Get-HardeningProfile { }
                Mock Get-Service { }
                Mock Write-Log { }

                { Get-ServiceSecurityDrift -Profile Invalid -ErrorAction Stop } | Should -Throw
            }
        }

        It "rejects empty ComputerName" {
            InModuleScope System {
                Mock Get-HardeningProfile { }
                Mock Get-Service { }
                Mock Write-Log { }

                { Get-ServiceSecurityDrift -ComputerName '' -ErrorAction Stop } | Should -Throw
            }
        }
    }

    Context "Service Drift Detection - Basic" {
        It "detects when service startup type is incorrect" {
            InModuleScope System {
                Mock Get-HardeningProfile {
                    return [PSCustomObject]@{
                        ProfileName = 'Recommended'
                        TargetSystem = 'Server'
                        Rules = @(
                            @{ Type = 'Service'; Name = 'Service-Test'; Severity = 'HIGH'; RuleDefinition = @{ ServiceName = 'spooler'; StartType = 'Disabled' } }
                        )
                    }
                }
                Mock Get-Service {
                    return [PSCustomObject]@{ Name = 'spooler'; DisplayName = 'Print Spooler'; StartType = 'Automatic'; Status = 'Running' }
                }
                Mock Get-CimInstance { return [PSCustomObject]@{ ProductType = 3 } }
                Mock Write-Log { }

                $result = Get-ServiceSecurityDrift -ErrorAction SilentlyContinue
                $result | Should -Not -BeNullOrEmpty
                $result.Status | Should -Contain 'DRIFT'
                $result[0].Actual | Should -Be 'Automatic'
                $result[0].Expected | Should -Be 'Disabled'
            }
        }

        It "detects when service is not found" {
            InModuleScope System {
                Mock Get-HardeningProfile {
                    return [PSCustomObject]@{
                        ProfileName = 'Recommended'
                        TargetSystem = 'Server'
                        Rules = @(
                            @{ Type = 'Service'; Name = 'Service-Test'; Severity = 'HIGH'; RuleDefinition = @{ ServiceName = 'nonexistent'; StartType = 'Disabled' } }
                        )
                    }
                }
                Mock Get-Service { return $null }
                Mock Get-CimInstance { return [PSCustomObject]@{ ProductType = 3 } }
                Mock Write-Log { }

                $result = Get-ServiceSecurityDrift -ErrorAction SilentlyContinue
                $notFound = $result | Where-Object { $_.ServiceName -eq 'nonexistent' }
                $notFound | Should -Not -BeNullOrEmpty
                $notFound.Status | Should -Be 'DRIFT'
                $notFound.Actual | Should -Be 'NOT_FOUND'
            }
        }

        It "returns no drift when service complies" {
            InModuleScope System {
                Mock Get-HardeningProfile {
                    return [PSCustomObject]@{
                        ProfileName = 'Recommended'
                        TargetSystem = 'Server'
                        Rules = @(
                            @{ Type = 'Service'; Name = 'Service-Test'; Severity = 'HIGH'; RuleDefinition = @{ ServiceName = 'spooler'; StartType = 'Disabled' } }
                        )
                    }
                }
                Mock Get-Service {
                    param($Name)
                    return $Name | ForEach-Object {
                        if ($_ -eq 'spooler') {
                            [PSCustomObject]@{ Name = $_; DisplayName = 'Print Spooler'; StartType = 'Disabled'; Status = 'Stopped' }
                        }
                    }
                }
                Mock Get-CimInstance { return [PSCustomObject]@{ ProductType = 3 } }
                Mock Write-Log { }

                $result = Get-ServiceSecurityDrift -ErrorAction SilentlyContinue
                $drift = $result | Where-Object { $_.ServiceName -eq 'spooler' -and $_.Status -eq 'DRIFT' }
                $drift | Should -BeNullOrEmpty
            }
        }
    }

    Context "Service Drift Detection - Multiple Services" {
        It "checks multiple services from profile" {
            InModuleScope System {
                Mock Get-HardeningProfile {
                    return [PSCustomObject]@{
                        ProfileName = 'Recommended'
                        TargetSystem = 'Server'
                        Rules = @(
                            @{ Type = 'Service'; Name = 'Service-Dangerous'; Severity = 'HIGH'; RuleDefinition = @{ ServiceName = 'spooler'; StartType = 'Disabled' } },
                            @{ Type = 'Service'; Name = 'Service-Unnecessary'; Severity = 'MEDIUM'; RuleDefinition = @{ Services = @('WinRM', 'TlntSvr'); StartType = 'Disabled' } }
                        )
                    }
                }
                Mock Get-Service {
                    param($Name)
                    return $Name | ForEach-Object {
                        [PSCustomObject]@{ Name = $_; DisplayName = $_; StartType = 'Automatic'; Status = 'Running' }
                    }
                }
                Mock Get-CimInstance { return [PSCustomObject]@{ ProductType = 3 } }
                Mock Write-Log { }

                $result = Get-ServiceSecurityDrift -ErrorAction SilentlyContinue
                $result.Count | Should -BeGreaterThan 0
                $result | Where-Object { $_.ServiceName -eq 'spooler' } | Should -Not -BeNullOrEmpty
                $result | Where-Object { $_.ServiceName -eq 'WinRM' } | Should -Not -BeNullOrEmpty
            }
        }

        It "detects drift in multiple services simultaneously" {
            InModuleScope System {
                Mock Get-HardeningProfile {
                    return [PSCustomObject]@{
                        ProfileName = 'Recommended'
                        TargetSystem = 'Server'
                        Rules = @(
                            @{ Type = 'Service'; Name = 'Service-Test1'; Severity = 'HIGH'; RuleDefinition = @{ Services = @('spooler', 'WinRM'); StartType = 'Disabled' } }
                        )
                    }
                }
                Mock Get-Service {
                    param($Name)
                    return $Name | ForEach-Object {
                        [PSCustomObject]@{ Name = $_; DisplayName = $_; StartType = 'Automatic'; Status = 'Running' }
                    }
                }
                Mock Get-CimInstance { return [PSCustomObject]@{ ProductType = 3 } }
                Mock Write-Log { }

                $result = Get-ServiceSecurityDrift -ErrorAction SilentlyContinue
                $driftCount = ($result | Where-Object { $_.Status -eq 'DRIFT' }).Count
                $driftCount | Should -BeGreaterThan 1
            }
        }
    }

    Context "Critical Services Monitoring" {
        It "includes Windows Update service in checks" {
            InModuleScope System {
                Mock Get-HardeningProfile {
                    return [PSCustomObject]@{
                        ProfileName = 'Recommended'
                        TargetSystem = 'Server'
                        Rules = @()
                    }
                }
                Mock Get-Service {
                    param($Name)
                    if ($Name -contains 'wuauserv') {
                        return [PSCustomObject]@{ Name = 'wuauserv'; DisplayName = 'Windows Update'; StartType = 'Automatic'; Status = 'Running' }
                    }
                }
                Mock Get-CimInstance { return [PSCustomObject]@{ ProductType = 3 } }
                Mock Write-Log { }

                $result = Get-ServiceSecurityDrift -Detailed -ErrorAction SilentlyContinue
                $result | Where-Object { $_.ServiceName -eq 'wuauserv' } | Should -Not -BeNullOrEmpty
            }
        }

        It "includes Windows Defender service in checks" {
            InModuleScope System {
                Mock Get-HardeningProfile {
                    return [PSCustomObject]@{
                        ProfileName = 'Recommended'
                        TargetSystem = 'Server'
                        Rules = @()
                    }
                }
                Mock Get-Service {
                    param($Name)
                    if ($Name -contains 'WinDefend') {
                        return [PSCustomObject]@{ Name = 'WinDefend'; DisplayName = 'Windows Defender'; StartType = 'Automatic'; Status = 'Running' }
                    }
                }
                Mock Get-CimInstance { return [PSCustomObject]@{ ProductType = 3 } }
                Mock Write-Log { }

                $result = Get-ServiceSecurityDrift -Detailed -ErrorAction SilentlyContinue
                $result | Where-Object { $_.ServiceName -eq 'WinDefend' } | Should -Not -BeNullOrEmpty
            }
        }

        It "includes Firewall service in checks" {
            InModuleScope System {
                Mock Get-HardeningProfile {
                    return [PSCustomObject]@{
                        ProfileName = 'Recommended'
                        TargetSystem = 'Server'
                        Rules = @()
                    }
                }
                Mock Get-Service {
                    param($Name)
                    if ($Name -contains 'mpssvc') {
                        return [PSCustomObject]@{ Name = 'mpssvc'; DisplayName = 'Windows Defender Firewall'; StartType = 'Automatic'; Status = 'Running' }
                    }
                }
                Mock Get-CimInstance { return [PSCustomObject]@{ ProductType = 3 } }
                Mock Write-Log { }

                $result = Get-ServiceSecurityDrift -Detailed -ErrorAction SilentlyContinue
                $result | Where-Object { $_.ServiceName -eq 'mpssvc' } | Should -Not -BeNullOrEmpty
            }
        }

        It "includes Telemetry service in checks" {
            InModuleScope System {
                Mock Get-HardeningProfile {
                    return [PSCustomObject]@{
                        ProfileName = 'Recommended'
                        TargetSystem = 'Server'
                        Rules = @()
                    }
                }
                Mock Get-Service {
                    param($Name)
                    if ($Name -contains 'DiagTrack') {
                        return [PSCustomObject]@{ Name = 'DiagTrack'; DisplayName = 'DiagTrack'; StartType = 'Disabled'; Status = 'Stopped' }
                    }
                }
                Mock Get-CimInstance { return [PSCustomObject]@{ ProductType = 3 } }
                Mock Write-Log { }

                $result = Get-ServiceSecurityDrift -Detailed -ErrorAction SilentlyContinue
                $result | Where-Object { $_.ServiceName -eq 'DiagTrack' } | Should -Not -BeNullOrEmpty
            }
        }

        It "includes Remote Desktop service in checks" {
            InModuleScope System {
                Mock Get-HardeningProfile {
                    return [PSCustomObject]@{
                        ProfileName = 'Recommended'
                        TargetSystem = 'Server'
                        Rules = @()
                    }
                }
                Mock Get-Service {
                    param($Name)
                    if ($Name -contains 'TermService') {
                        return [PSCustomObject]@{ Name = 'TermService'; DisplayName = 'Remote Desktop Services'; StartType = 'Manual'; Status = 'Stopped' }
                    }
                }
                Mock Get-CimInstance { return [PSCustomObject]@{ ProductType = 3 } }
                Mock Write-Log { }

                $result = Get-ServiceSecurityDrift -Detailed -ErrorAction SilentlyContinue
                $result | Where-Object { $_.ServiceName -eq 'TermService' } | Should -Not -BeNullOrEmpty
            }
        }
    }

    Context "Profile Support" {
        It "loads Basis profile correctly" {
            InModuleScope System {
                Mock Get-HardeningProfile {
                    return [PSCustomObject]@{
                        ProfileName = 'Basis'
                        TargetSystem = 'Server'
                        Rules = @()
                    }
                }
                Mock Get-Service { }
                Mock Get-CimInstance { return [PSCustomObject]@{ ProductType = 3 } }
                Mock Write-Log { }

                { Get-ServiceSecurityDrift -Profile Basis -ErrorAction SilentlyContinue } | Should -Not -Throw
                Assert-MockCalled Get-HardeningProfile -ParameterFilter { $ProfileName -eq 'Basis' }
            }
        }

        It "loads Recommended profile correctly" {
            InModuleScope System {
                Mock Get-HardeningProfile {
                    return [PSCustomObject]@{
                        ProfileName = 'Recommended'
                        TargetSystem = 'Server'
                        Rules = @()
                    }
                }
                Mock Get-Service { }
                Mock Get-CimInstance { return [PSCustomObject]@{ ProductType = 3 } }
                Mock Write-Log { }

                { Get-ServiceSecurityDrift -Profile Recommended -ErrorAction SilentlyContinue } | Should -Not -Throw
                Assert-MockCalled Get-HardeningProfile -ParameterFilter { $ProfileName -eq 'Recommended' }
            }
        }

        It "loads Strict profile correctly" {
            InModuleScope System {
                Mock Get-HardeningProfile {
                    return [PSCustomObject]@{
                        ProfileName = 'Strict'
                        TargetSystem = 'Server'
                        Rules = @()
                    }
                }
                Mock Get-Service { }
                Mock Get-CimInstance { return [PSCustomObject]@{ ProductType = 3 } }
                Mock Write-Log { }

                { Get-ServiceSecurityDrift -Profile Strict -ErrorAction SilentlyContinue } | Should -Not -Throw
                Assert-MockCalled Get-HardeningProfile -ParameterFilter { $ProfileName -eq 'Strict' }
            }
        }
    }

    Context "Detailed Output" {
        It "includes basic properties in output" {
            InModuleScope System {
                Mock Get-HardeningProfile {
                    return [PSCustomObject]@{
                        ProfileName = 'Recommended'
                        TargetSystem = 'Server'
                        Rules = @(
                            @{ Type = 'Service'; Name = 'Service-Test'; Severity = 'HIGH'; RuleDefinition = @{ ServiceName = 'spooler'; StartType = 'Disabled' } }
                        )
                    }
                }
                Mock Get-Service {
                    return [PSCustomObject]@{ Name = 'spooler'; DisplayName = 'Print Spooler'; StartType = 'Automatic'; Status = 'Running' }
                }
                Mock Get-CimInstance { return [PSCustomObject]@{ ProductType = 3 } }
                Mock Write-Log { }

                $result = Get-ServiceSecurityDrift -ErrorAction SilentlyContinue
                $result[0] | Get-Member -MemberType NoteProperty | Select-Object -ExpandProperty Name | Should -Contain 'ServiceName'
                $result[0] | Get-Member -MemberType NoteProperty | Select-Object -ExpandProperty Name | Should -Contain 'Expected'
                $result[0] | Get-Member -MemberType NoteProperty | Select-Object -ExpandProperty Name | Should -Contain 'Actual'
                $result[0] | Get-Member -MemberType NoteProperty | Select-Object -ExpandProperty Name | Should -Contain 'Status'
            }
        }

        It "includes Severity in output" {
            InModuleScope System {
                Mock Get-HardeningProfile {
                    return [PSCustomObject]@{
                        ProfileName = 'Recommended'
                        TargetSystem = 'Server'
                        Rules = @(
                            @{ Type = 'Service'; Name = 'Service-Test'; Severity = 'CRITICAL'; RuleDefinition = @{ ServiceName = 'spooler'; StartType = 'Disabled' } }
                        )
                    }
                }
                Mock Get-Service {
                    return [PSCustomObject]@{ Name = 'spooler'; DisplayName = 'Print Spooler'; StartType = 'Automatic'; Status = 'Running' }
                }
                Mock Get-CimInstance { return [PSCustomObject]@{ ProductType = 3 } }
                Mock Write-Log { }

                $result = Get-ServiceSecurityDrift -ErrorAction SilentlyContinue
                $result[0].Severity | Should -Be 'CRITICAL'
            }
        }

        It "includes Remediation in output" {
            InModuleScope System {
                Mock Get-HardeningProfile {
                    return [PSCustomObject]@{
                        ProfileName = 'Recommended'
                        TargetSystem = 'Server'
                        Rules = @(
                            @{ Type = 'Service'; Name = 'Service-Test'; Severity = 'HIGH'; RuleDefinition = @{ ServiceName = 'spooler'; StartType = 'Disabled' } }
                        )
                    }
                }
                Mock Get-Service {
                    return [PSCustomObject]@{ Name = 'spooler'; DisplayName = 'Print Spooler'; StartType = 'Automatic'; Status = 'Running' }
                }
                Mock Get-CimInstance { return [PSCustomObject]@{ ProductType = 3 } }
                Mock Write-Log { }

                $result = Get-ServiceSecurityDrift -ErrorAction SilentlyContinue
                $result[0] | Get-Member -MemberType NoteProperty | Select-Object -ExpandProperty Name | Should -Contain 'Remediation'
                $result[0].Remediation | Should -Match 'Set-Service'
            }
        }

        It "includes DisplayName when available" {
            InModuleScope System {
                Mock Get-HardeningProfile {
                    return [PSCustomObject]@{
                        ProfileName = 'Recommended'
                        TargetSystem = 'Server'
                        Rules = @(
                            @{ Type = 'Service'; Name = 'Service-Test'; Severity = 'HIGH'; RuleDefinition = @{ ServiceName = 'spooler'; StartType = 'Disabled' } }
                        )
                    }
                }
                Mock Get-Service {
                    return [PSCustomObject]@{ Name = 'spooler'; DisplayName = 'Print Spooler'; StartType = 'Automatic'; Status = 'Running' }
                }
                Mock Get-CimInstance { return [PSCustomObject]@{ ProductType = 3 } }
                Mock Write-Log { }

                $result = Get-ServiceSecurityDrift -ErrorAction SilentlyContinue
                $result[0].DisplayName | Should -Be 'Print Spooler'
            }
        }

        It "includes CurrentStatus with Detailed switch" {
            InModuleScope System {
                Mock Get-HardeningProfile {
                    return [PSCustomObject]@{
                        ProfileName = 'Recommended'
                        TargetSystem = 'Server'
                        Rules = @(
                            @{ Type = 'Service'; Name = 'Service-Test'; Severity = 'HIGH'; RuleDefinition = @{ ServiceName = 'spooler'; StartType = 'Disabled' } }
                        )
                    }
                }
                Mock Get-Service {
                    return [PSCustomObject]@{ Name = 'spooler'; DisplayName = 'Print Spooler'; StartType = 'Automatic'; Status = 'Running' }
                }
                Mock Get-CimInstance { return [PSCustomObject]@{ ProductType = 3 } }
                Mock Write-Log { }

                $result = Get-ServiceSecurityDrift -Detailed -ErrorAction SilentlyContinue
                $result[0] | Get-Member -MemberType NoteProperty | Select-Object -ExpandProperty Name | Should -Contain 'CurrentStatus'
            }
        }
    }

    Context "ReportDriftOnly Switch" {
        It "returns only drifted services when ReportDriftOnly is set" {
            InModuleScope System {
                Mock Get-HardeningProfile {
                    return [PSCustomObject]@{
                        ProfileName = 'Recommended'
                        TargetSystem = 'Server'
                        Rules = @(
                            @{ Type = 'Service'; Name = 'Service-Test'; Severity = 'HIGH'; RuleDefinition = @{ Services = @('spooler', 'WinRM'); StartType = 'Disabled' } }
                        )
                    }
                }
                Mock Get-Service {
                    param($Name)
                    return $Name | ForEach-Object {
                        if ($_ -eq 'spooler') {
                            [PSCustomObject]@{ Name = $_; DisplayName = 'Print Spooler'; StartType = 'Disabled'; Status = 'Stopped' }
                        }
                        else {
                            [PSCustomObject]@{ Name = $_; DisplayName = 'Windows Remote Management'; StartType = 'Automatic'; Status = 'Running' }
                        }
                    }
                }
                Mock Get-CimInstance { return [PSCustomObject]@{ ProductType = 3 } }
                Mock Write-Log { }

                $result = Get-ServiceSecurityDrift -ReportDriftOnly -ErrorAction SilentlyContinue
                $result | Where-Object { $_.ServiceName -eq 'spooler' } | Should -BeNullOrEmpty
                $result | Where-Object { $_.ServiceName -eq 'WinRM' } | Should -Not -BeNullOrEmpty
            }
        }

        It "filters to drift-only services with ReportDriftOnly" {
            InModuleScope System {
                Mock Get-HardeningProfile {
                    return [PSCustomObject]@{
                        ProfileName = 'Recommended'
                        TargetSystem = 'Server'
                        Rules = @(
                            @{ Type = 'Service'; Name = 'Service-Test'; Severity = 'HIGH'; RuleDefinition = @{ ServiceName = 'spooler'; StartType = 'Disabled' } }
                        )
                    }
                }
                Mock Get-Service {
                    param($Name)
                    return $Name | ForEach-Object {
                        if ($_ -eq 'spooler') {
                            [PSCustomObject]@{ Name = $_; DisplayName = 'Print Spooler'; StartType = 'Disabled'; Status = 'Stopped' }
                        }
                        else {
                            [PSCustomObject]@{ Name = $_; DisplayName = $_; StartType = 'Automatic'; Status = 'Running' }
                        }
                    }
                }
                Mock Get-CimInstance { return [PSCustomObject]@{ ProductType = 3 } }
                Mock Write-Log { }

                $result = Get-ServiceSecurityDrift -ReportDriftOnly -ErrorAction SilentlyContinue
                # All results should have Status = 'DRIFT'
                $result | Where-Object { $_.Status -ne 'DRIFT' } | Should -BeNullOrEmpty
            }
        }
    }

    Context "Detailed Switch" {
        It "includes compliant services with Detailed switch" {
            InModuleScope System {
                Mock Get-HardeningProfile {
                    return [PSCustomObject]@{
                        ProfileName = 'Recommended'
                        TargetSystem = 'Server'
                        Rules = @(
                            @{ Type = 'Service'; Name = 'Service-Test'; Severity = 'HIGH'; RuleDefinition = @{ ServiceName = 'spooler'; StartType = 'Disabled' } }
                        )
                    }
                }
                Mock Get-Service {
                    return [PSCustomObject]@{ Name = 'spooler'; DisplayName = 'Print Spooler'; StartType = 'Disabled'; Status = 'Stopped' }
                }
                Mock Get-CimInstance { return [PSCustomObject]@{ ProductType = 3 } }
                Mock Write-Log { }

                $result = Get-ServiceSecurityDrift -Detailed -ErrorAction SilentlyContinue
                $result | Should -Not -BeNullOrEmpty
                $result[0].Status | Should -Be 'COMPLIANT'
            }
        }

        It "marks compliant services as COMPLIANT" {
            InModuleScope System {
                Mock Get-HardeningProfile {
                    return [PSCustomObject]@{
                        ProfileName = 'Recommended'
                        TargetSystem = 'Server'
                        Rules = @(
                            @{ Type = 'Service'; Name = 'Service-Test'; Severity = 'HIGH'; RuleDefinition = @{ ServiceName = 'spooler'; StartType = 'Disabled' } }
                        )
                    }
                }
                Mock Get-Service {
                    return [PSCustomObject]@{ Name = 'spooler'; DisplayName = 'Print Spooler'; StartType = 'Disabled'; Status = 'Stopped' }
                }
                Mock Get-CimInstance { return [PSCustomObject]@{ ProductType = 3 } }
                Mock Write-Log { }

                $result = Get-ServiceSecurityDrift -Detailed -ErrorAction SilentlyContinue
                $result[0].Status | Should -Be 'COMPLIANT'
                $result[0].Remediation | Should -Be 'None'
            }
        }
    }

    Context "Remote Computer Support" {
        It "calls Invoke-Command for remote computer without credential" {
            InModuleScope System {
                Mock Get-HardeningProfile {
                    return [PSCustomObject]@{
                        ProfileName = 'Recommended'
                        TargetSystem = 'Server'
                        Rules = @()
                    }
                }
                Mock Invoke-Command { return @() }
                Mock Write-Log { }

                Get-ServiceSecurityDrift -ComputerName 'testserver' -ErrorAction SilentlyContinue
                Assert-MockCalled Invoke-Command -ParameterFilter { $ComputerName -eq 'testserver' }
            }
        }

        It "includes ComputerName in drift results for remote system" {
            InModuleScope System {
                Mock Get-HardeningProfile {
                    return [PSCustomObject]@{
                        ProfileName = 'Recommended'
                        TargetSystem = 'Server'
                        Rules = @(
                            @{ Type = 'Service'; Name = 'Service-Test'; Severity = 'HIGH'; RuleDefinition = @{ ServiceName = 'spooler'; StartType = 'Disabled' } }
                        )
                    }
                }
                Mock Invoke-Command {
                    param($ComputerName, $ScriptBlock)
                    return & $ScriptBlock -serviceNames @('spooler')
                }
                Mock Get-Service {
                    param($Name)
                    return [PSCustomObject]@{ Name = 'spooler'; DisplayName = 'Print Spooler'; StartType = 'Automatic'; Status = 'Running' }
                }
                Mock Write-Log { }

                $result = Get-ServiceSecurityDrift -ComputerName 'testserver' -ErrorAction SilentlyContinue
                $spooler = $result | Where-Object { $_.ServiceName -eq 'spooler' }
                $spooler.ComputerName | Should -Be 'testserver'
            }
        }

        It "handles remote connection with credentials" {
            InModuleScope System {
                Mock Get-HardeningProfile {
                    return [PSCustomObject]@{
                        ProfileName = 'Recommended'
                        TargetSystem = 'Server'
                        Rules = @()
                    }
                }
                Mock Invoke-Command { return @() }
                Mock Write-Log { }

                # PSScriptAnalyzer ignore: Test credentials
                $credential = New-Object System.Management.Automation.PSCredential('user', (ConvertTo-SecureString 'pass' -AsPlainText -Force)) # PSScriptAnalyzer ignore [PSAvoidUsingConvertToSecureStringWithPlainText]
                Get-ServiceSecurityDrift -ComputerName 'testserver' -Credential $credential -ErrorAction SilentlyContinue
                Assert-MockCalled Invoke-Command -ParameterFilter { $Credential -ne $null }
            }
        }
    }

    Context "WhatIf Support" {
        It "supports WhatIf parameter" {
            InModuleScope System {
                Mock Get-HardeningProfile {
                    return [PSCustomObject]@{
                        ProfileName = 'Recommended'
                        TargetSystem = 'Server'
                        Rules = @()
                    }
                }
                Mock Get-Service { }
                Mock Get-CimInstance { return [PSCustomObject]@{ ProductType = 3 } }
                Mock Write-Log { }

                { Get-ServiceSecurityDrift -WhatIf } | Should -Not -Throw
            }
        }

        It "respects WhatIf without executing Get-Service" {
            InModuleScope System {
                Mock Get-HardeningProfile {
                    return [PSCustomObject]@{
                        ProfileName = 'Recommended'
                        TargetSystem = 'Server'
                        Rules = @()
                    }
                }
                Mock Get-Service { }
                Mock Get-CimInstance { return [PSCustomObject]@{ ProductType = 3 } }
                Mock Write-Log { }

                Get-ServiceSecurityDrift -WhatIf -ErrorAction SilentlyContinue
                # WhatIf should still try to query services through ShouldProcess
                Assert-MockCalled Get-Service -Times 0 -Scope It
            }
        }
    }

    Context "Logging" {
        It "logs drift detection start" {
            InModuleScope System {
                Mock Get-HardeningProfile {
                    return [PSCustomObject]@{
                        ProfileName = 'Recommended'
                        TargetSystem = 'Server'
                        Rules = @()
                    }
                }
                Mock Get-Service { }
                Mock Get-CimInstance { return [PSCustomObject]@{ ProductType = 3 } }
                Mock Write-Log { }

                Get-ServiceSecurityDrift -ErrorAction SilentlyContinue
                Assert-MockCalled Write-Log -ParameterFilter { $Message -match 'Starting service security drift' }
            }
        }

        It "logs drift detection completion" {
            InModuleScope System {
                Mock Get-HardeningProfile {
                    return [PSCustomObject]@{
                        ProfileName = 'Recommended'
                        TargetSystem = 'Server'
                        Rules = @()
                    }
                }
                Mock Get-Service { }
                Mock Get-CimInstance { return [PSCustomObject]@{ ProductType = 3 } }
                Mock Write-Log { }

                Get-ServiceSecurityDrift -ErrorAction SilentlyContinue
                Assert-MockCalled Write-Log -ParameterFilter { $Message -match 'complete' }
            }
        }

        It "logs service drift warnings" {
            InModuleScope System {
                Mock Get-HardeningProfile {
                    return [PSCustomObject]@{
                        ProfileName = 'Recommended'
                        TargetSystem = 'Server'
                        Rules = @(
                            @{ Type = 'Service'; Name = 'Service-Test'; Severity = 'HIGH'; RuleDefinition = @{ ServiceName = 'spooler'; StartType = 'Disabled' } }
                        )
                    }
                }
                Mock Get-Service {
                    return [PSCustomObject]@{ Name = 'spooler'; DisplayName = 'Print Spooler'; StartType = 'Automatic'; Status = 'Running' }
                }
                Mock Get-CimInstance { return [PSCustomObject]@{ ProductType = 3 } }
                Mock Write-Log { }

                Get-ServiceSecurityDrift -ErrorAction SilentlyContinue
                Assert-MockCalled Write-Log -ParameterFilter { $Message -match 'drift' -and $Level -eq 'Warning' }
            }
        }
    }

    Context "System Type Detection" {
        It "detects Client system type" {
            InModuleScope System {
                Mock Get-HardeningProfile {
                    return [PSCustomObject]@{
                        ProfileName = 'Recommended'
                        TargetSystem = 'Client'
                        Rules = @()
                    }
                }
                Mock Get-Service { }
                Mock Get-CimInstance { return [PSCustomObject]@{ ProductType = 1 } }
                Mock Write-Log { }

                Get-ServiceSecurityDrift -ErrorAction SilentlyContinue
                Assert-MockCalled Get-HardeningProfile -ParameterFilter { $TargetSystem -eq 'Client' }
            }
        }

        It "detects Server system type" {
            InModuleScope System {
                Mock Get-HardeningProfile {
                    return [PSCustomObject]@{
                        ProfileName = 'Recommended'
                        TargetSystem = 'Server'
                        Rules = @()
                    }
                }
                Mock Get-Service { }
                Mock Get-CimInstance { return [PSCustomObject]@{ ProductType = 3 } }
                Mock Write-Log { }

                Get-ServiceSecurityDrift -ErrorAction SilentlyContinue
                Assert-MockCalled Get-HardeningProfile -ParameterFilter { $TargetSystem -eq 'Server' }
            }
        }
    }

    Context "Error Handling" {
        It "throws when profile loading fails" {
            InModuleScope System {
                Mock Get-HardeningProfile { return $null }
                Mock Write-Log { }
                Mock Write-ErrorLog { }

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

        It "logs errors with Write-ErrorLog" {
            InModuleScope System {
                Mock Get-HardeningProfile { throw "Test error" }
                Mock Write-ErrorLog { }
                Mock Write-Log { }

                { Get-ServiceSecurityDrift -ErrorAction Stop } | Should -Throw
                Assert-MockCalled Write-ErrorLog
            }
        }
    }

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

        It "includes all parameters in help" {
            $help = Get-Help Get-ServiceSecurityDrift
            $help.Parameters.Parameter.Name | Should -Contain 'Profile'
            $help.Parameters.Parameter.Name | Should -Contain 'ComputerName'
            $help.Parameters.Parameter.Name | Should -Contain 'Credential'
            $help.Parameters.Parameter.Name | Should -Contain 'Detailed'
            $help.Parameters.Parameter.Name | Should -Contain 'ReportDriftOnly'
        }

        It "includes examples in help" {
            $help = Get-Help Get-ServiceSecurityDrift
            $help.Examples | Should -Not -BeNullOrEmpty
        }
    }
}