Tests/ServiceAccount-Audit.Tests.ps1

BeforeAll {
    $ModuleRoot = Split-Path -Path $PSScriptRoot -Parent
    $ModuleName = 'ServiceAccount-Audit'
    $ManifestPath = Join-Path -Path $ModuleRoot -ChildPath "$ModuleName.psd1"

    # Remove module if already loaded, then import fresh
    Get-Module -Name $ModuleName -ErrorAction SilentlyContinue | Remove-Module -Force
    Import-Module $ManifestPath -Force -ErrorAction Stop
}

AfterAll {
    Get-Module -Name 'ServiceAccount-Audit' -ErrorAction SilentlyContinue | Remove-Module -Force
}

Describe 'Module: ServiceAccount-Audit' {

    Context 'Module Loading' {

        It 'Should import without errors' {
            $Module = Get-Module -Name 'ServiceAccount-Audit'
            $Module | Should -Not -BeNullOrEmpty
        }

        It 'Should export exactly 5 public functions' {
            $Module = Get-Module -Name 'ServiceAccount-Audit'
            $Module.ExportedFunctions.Count | Should -Be 5
        }

        It 'Should export Invoke-ServiceAccountAudit' {
            $Module = Get-Module -Name 'ServiceAccount-Audit'
            $Module.ExportedFunctions.Keys | Should -Contain 'Invoke-ServiceAccountAudit'
        }

        It 'Should export Get-ServiceAccountInventory' {
            $Module = Get-Module -Name 'ServiceAccount-Audit'
            $Module.ExportedFunctions.Keys | Should -Contain 'Get-ServiceAccountInventory'
        }

        It 'Should export Get-ServiceAccountAge' {
            $Module = Get-Module -Name 'ServiceAccount-Audit'
            $Module.ExportedFunctions.Keys | Should -Contain 'Get-ServiceAccountAge'
        }

        It 'Should export Get-ServiceAccountUsage' {
            $Module = Get-Module -Name 'ServiceAccount-Audit'
            $Module.ExportedFunctions.Keys | Should -Contain 'Get-ServiceAccountUsage'
        }

        It 'Should export Get-SPNReport' {
            $Module = Get-Module -Name 'ServiceAccount-Audit'
            $Module.ExportedFunctions.Keys | Should -Contain 'Get-SPNReport'
        }

        It 'Should not export private functions' {
            $Module = Get-Module -Name 'ServiceAccount-Audit'
            $Module.ExportedFunctions.Keys | Should -Not -Contain 'New-HtmlDashboard'
        }
    }

    Context 'Manifest Validation' {

        It 'Should have a valid manifest' {
            { Test-ModuleManifest -Path $ManifestPath -ErrorAction Stop } | Should -Not -Throw
        }

        It 'Should have the correct GUID' {
            $Manifest = Test-ModuleManifest -Path $ManifestPath
            $Manifest.GUID.ToString() | Should -Be 'b2c3d4e5-9f80-4b6c-ad7e-3f4a5b6c7d8e'
        }

        It 'Should require PowerShell 5.1' {
            $Manifest = Test-ModuleManifest -Path $ManifestPath
            $Manifest.PowerShellVersion.ToString() | Should -Be '5.1'
        }

        It 'Should have the correct author' {
            $Manifest = Test-ModuleManifest -Path $ManifestPath
            $Manifest.Author | Should -BeLike '*Larry Roberts*'
        }

        It 'Should have security-related tags' {
            $Manifest = Test-ModuleManifest -Path $ManifestPath
            $Manifest.PrivateData.PSData.Tags | Should -Contain 'Security'
            $Manifest.PrivateData.PSData.Tags | Should -Contain 'ServiceAccount'
            $Manifest.PrivateData.PSData.Tags | Should -Contain 'SPN'
        }

        It 'Should have a ProjectUri' {
            $Manifest = Test-ModuleManifest -Path $ManifestPath
            $Manifest.PrivateData.PSData.ProjectUri | Should -Not -BeNullOrEmpty
        }
    }

    Context 'Parameter Validation' {

        It 'Invoke-ServiceAccountAudit should have SearchBase parameter' {
            $Cmd = Get-Command -Name 'Invoke-ServiceAccountAudit'
            $Cmd.Parameters.Keys | Should -Contain 'SearchBase'
        }

        It 'Invoke-ServiceAccountAudit should have ComputerName parameter as string array' {
            $Cmd = Get-Command -Name 'Invoke-ServiceAccountAudit'
            $Cmd.Parameters['ComputerName'].ParameterType.Name | Should -Be 'String[]'
        }

        It 'Invoke-ServiceAccountAudit should have OutputPath parameter' {
            $Cmd = Get-Command -Name 'Invoke-ServiceAccountAudit'
            $Cmd.Parameters.Keys | Should -Contain 'OutputPath'
        }

        It 'Invoke-ServiceAccountAudit DaysOldThreshold should default to 365' {
            $Cmd = Get-Command -Name 'Invoke-ServiceAccountAudit'
            $Cmd.Parameters['DaysOldThreshold'].DefaultValue | Should -Be 365
        }

        It 'Invoke-ServiceAccountAudit should have IncludeMSA switch' {
            $Cmd = Get-Command -Name 'Invoke-ServiceAccountAudit'
            $Cmd.Parameters['IncludeMSA'].SwitchParameter | Should -BeTrue
        }

        It 'Get-ServiceAccountInventory NamingPattern should default to svc*,service*' {
            $Cmd = Get-Command -Name 'Get-ServiceAccountInventory'
            $Cmd.Parameters['NamingPattern'].DefaultValue | Should -Be 'svc*,service*'
        }

        It 'Get-ServiceAccountInventory should have IncludeMSA switch' {
            $Cmd = Get-Command -Name 'Get-ServiceAccountInventory'
            $Cmd.Parameters['IncludeMSA'].SwitchParameter | Should -BeTrue
        }

        It 'Get-ServiceAccountInventory should have IncludeGMSA switch' {
            $Cmd = Get-Command -Name 'Get-ServiceAccountInventory'
            $Cmd.Parameters['IncludeGMSA'].SwitchParameter | Should -BeTrue
        }

        It 'Get-ServiceAccountAge DaysOldThreshold should default to 365' {
            $Cmd = Get-Command -Name 'Get-ServiceAccountAge'
            $Cmd.Parameters['DaysOldThreshold'].DefaultValue | Should -Be 365
        }

        It 'Get-ServiceAccountUsage ComputerName should be mandatory' {
            $Cmd = Get-Command -Name 'Get-ServiceAccountUsage'
            $Cmd.Parameters['ComputerName'].Attributes |
                Where-Object { $_ -is [System.Management.Automation.ParameterAttribute] } |
                ForEach-Object { $_.Mandatory } |
                Should -Contain $true
        }

        It 'Get-ServiceAccountUsage ComputerName should accept string array' {
            $Cmd = Get-Command -Name 'Get-ServiceAccountUsage'
            $Cmd.Parameters['ComputerName'].ParameterType.Name | Should -Be 'String[]'
        }

        It 'Get-SPNReport should have IncludeComputers switch' {
            $Cmd = Get-Command -Name 'Get-SPNReport'
            $Cmd.Parameters['IncludeComputers'].SwitchParameter | Should -BeTrue
        }
    }

    Context 'Get-ServiceAccountInventory Mock Tests' {

        BeforeAll {
            # Build mock AD user objects
            $MockServiceAccounts = @(
                # Old password, no owner, no description, in privileged group
                [PSCustomObject]@{
                    SAMAccountName       = 'svc-sqlprod'
                    DisplayName          = 'SQL Production Service'
                    DistinguishedName    = 'CN=svc-sqlprod,OU=ServiceAccounts,DC=contoso,DC=com'
                    Enabled              = $true
                    PasswordLastSet      = (Get-Date).AddDays(-1825)
                    PasswordNeverExpires = $true
                    LastLogonDate        = (Get-Date).AddDays(-1)
                    MemberOf             = @('CN=Domain Admins,CN=Users,DC=contoso,DC=com')
                    ServicePrincipalName = @('MSSQLSvc/sql01.contoso.com:1433')
                    Description          = $null
                    ManagedBy            = $null
                    ObjectClass          = 'user'
                    'msDS-ManagedPasswordInterval' = $null
                }
                # Disabled but still in groups
                [PSCustomObject]@{
                    SAMAccountName       = 'svc-legacy'
                    DisplayName          = 'Legacy Service'
                    DistinguishedName    = 'CN=svc-legacy,OU=ServiceAccounts,DC=contoso,DC=com'
                    Enabled              = $false
                    PasswordLastSet      = (Get-Date).AddDays(-2000)
                    PasswordNeverExpires = $true
                    LastLogonDate        = (Get-Date).AddDays(-400)
                    MemberOf             = @('CN=Backup Operators,CN=Builtin,DC=contoso,DC=com')
                    ServicePrincipalName = $null
                    Description          = 'Old backup service'
                    ManagedBy            = $null
                    ObjectClass          = 'user'
                    'msDS-ManagedPasswordInterval' = $null
                }
                # Clean account (recent password, has owner, has description)
                [PSCustomObject]@{
                    SAMAccountName       = 'svc-webapp'
                    DisplayName          = 'Web Application Service'
                    DistinguishedName    = 'CN=svc-webapp,OU=ServiceAccounts,DC=contoso,DC=com'
                    Enabled              = $true
                    PasswordLastSet      = (Get-Date).AddDays(-30)
                    PasswordNeverExpires = $false
                    LastLogonDate        = (Get-Date).AddDays(-1)
                    MemberOf             = @('CN=WebAppPool,OU=Groups,DC=contoso,DC=com')
                    ServicePrincipalName = $null
                    Description          = 'IIS App Pool identity for web app'
                    ManagedBy            = 'CN=John Smith,OU=Users,DC=contoso,DC=com'
                    ObjectClass          = 'user'
                    'msDS-ManagedPasswordInterval' = $null
                }
            )

            Mock Get-ADUser -ModuleName 'ServiceAccount-Audit' -MockWith {
                param($Filter, $Identity, $Properties, $SearchBase)
                if ($Identity) {
                    return $MockServiceAccounts | Where-Object { $_.SAMAccountName -eq $Identity }
                }
                return $MockServiceAccounts
            }

            Mock Get-ADOrganizationalUnit -ModuleName 'ServiceAccount-Audit' -MockWith {
                return @()
            }

            Mock Get-ADServiceAccount -ModuleName 'ServiceAccount-Audit' -MockWith {
                return @()
            }
        }

        It 'Should return discovered service accounts' {
            $Results = Get-ServiceAccountInventory
            $Results | Should -Not -BeNullOrEmpty
            $Results.Count | Should -BeGreaterOrEqual 1
        }

        It 'Should flag PASSWORD OLD for accounts with old passwords' {
            $Results = Get-ServiceAccountInventory
            $OldAccount = $Results | Where-Object { $_.SAMAccountName -eq 'svc-sqlprod' }
            $OldAccount.Finding | Should -Match 'PASSWORD OLD'
        }

        It 'Should flag NEVER EXPIRES for PasswordNeverExpires accounts' {
            $Results = Get-ServiceAccountInventory
            $NeverExpires = $Results | Where-Object { $_.SAMAccountName -eq 'svc-sqlprod' }
            $NeverExpires.Finding | Should -Match 'NEVER EXPIRES'
        }

        It 'Should flag IN PRIVILEGED GROUP for Domain Admins membership' {
            $Results = Get-ServiceAccountInventory
            $Privileged = $Results | Where-Object { $_.SAMAccountName -eq 'svc-sqlprod' }
            $Privileged.Finding | Should -Match 'IN PRIVILEGED GROUP'
        }

        It 'Should flag NO OWNER for accounts without ManagedBy' {
            $Results = Get-ServiceAccountInventory
            $NoOwner = $Results | Where-Object { $_.SAMAccountName -eq 'svc-sqlprod' }
            $NoOwner.Finding | Should -Match 'NO OWNER'
        }

        It 'Should flag NO DESCRIPTION for accounts without Description' {
            $Results = Get-ServiceAccountInventory
            $NoDesc = $Results | Where-Object { $_.SAMAccountName -eq 'svc-sqlprod' }
            $NoDesc.Finding | Should -Match 'NO DESCRIPTION'
        }

        It 'Should flag DISABLED BUT IN GROUPS for disabled accounts with group membership' {
            $Results = Get-ServiceAccountInventory
            $Disabled = $Results | Where-Object { $_.SAMAccountName -eq 'svc-legacy' }
            $Disabled.Finding | Should -Match 'DISABLED BUT IN GROUPS'
        }

        It 'Should detect HasSPN for accounts with ServicePrincipalName' {
            $Results = Get-ServiceAccountInventory
            $WithSPN = $Results | Where-Object { $_.SAMAccountName -eq 'svc-sqlprod' }
            $WithSPN.HasSPN | Should -BeTrue
        }

        It 'Should calculate PasswordAge correctly' {
            $Results = Get-ServiceAccountInventory
            $Account = $Results | Where-Object { $_.SAMAccountName -eq 'svc-sqlprod' }
            $Account.PasswordAge | Should -BeGreaterThan 1800
        }
    }

    Context 'Get-ServiceAccountAge Mock Tests' {

        BeforeAll {
            $Now = Get-Date

            $MockAgeAccounts = @(
                # CRITICAL: >3 years old
                [PSCustomObject]@{
                    SAMAccountName       = 'svc-ancient'
                    DisplayName          = 'Ancient Service'
                    DistinguishedName    = 'CN=svc-ancient,OU=ServiceAccounts,DC=contoso,DC=com'
                    Enabled              = $true
                    PasswordLastSet      = $Now.AddDays(-1500)
                    PasswordNeverExpires = $true
                    LastLogonDate        = $Now.AddDays(-1)
                    ServicePrincipalName = @('HTTP/ancient.contoso.com')
                    Description          = 'Very old service'
                    ManagedBy            = $null
                }
                # HIGH: >2 years old
                [PSCustomObject]@{
                    SAMAccountName       = 'svc-aging'
                    DisplayName          = 'Aging Service'
                    DistinguishedName    = 'CN=svc-aging,OU=ServiceAccounts,DC=contoso,DC=com'
                    Enabled              = $true
                    PasswordLastSet      = $Now.AddDays(-900)
                    PasswordNeverExpires = $true
                    LastLogonDate        = $Now.AddDays(-5)
                    ServicePrincipalName = $null
                    Description          = 'Getting old'
                    ManagedBy            = $null
                }
                # MEDIUM: >1 year old
                [PSCustomObject]@{
                    SAMAccountName       = 'svc-medium'
                    DisplayName          = 'Medium Risk Service'
                    DistinguishedName    = 'CN=svc-medium,OU=ServiceAccounts,DC=contoso,DC=com'
                    Enabled              = $true
                    PasswordLastSet      = $Now.AddDays(-500)
                    PasswordNeverExpires = $false
                    LastLogonDate        = $Now.AddDays(-2)
                    ServicePrincipalName = $null
                    Description          = 'Medium risk'
                    ManagedBy            = 'CN=Admin,DC=contoso,DC=com'
                }
                # LOW: <1 year old (should not appear with default threshold)
                [PSCustomObject]@{
                    SAMAccountName       = 'svc-recent'
                    DisplayName          = 'Recent Service'
                    DistinguishedName    = 'CN=svc-recent,OU=ServiceAccounts,DC=contoso,DC=com'
                    Enabled              = $true
                    PasswordLastSet      = $Now.AddDays(-90)
                    PasswordNeverExpires = $false
                    LastLogonDate        = $Now.AddDays(-1)
                    ServicePrincipalName = $null
                    Description          = 'Recent'
                    ManagedBy            = 'CN=Admin,DC=contoso,DC=com'
                }
            )

            Mock Get-ADUser -ModuleName 'ServiceAccount-Audit' -MockWith {
                return $MockAgeAccounts
            }
        }

        It 'Should assign CRITICAL rating for passwords >3 years old' {
            $Results = Get-ServiceAccountAge -DaysOldThreshold 1
            $Critical = $Results | Where-Object { $_.SAMAccountName -eq 'svc-ancient' }
            $Critical.RiskRating | Should -Be 'CRITICAL'
        }

        It 'Should assign HIGH rating for passwords >2 years old' {
            $Results = Get-ServiceAccountAge -DaysOldThreshold 1
            $High = $Results | Where-Object { $_.SAMAccountName -eq 'svc-aging' }
            $High.RiskRating | Should -Be 'HIGH'
        }

        It 'Should assign MEDIUM rating for passwords >1 year old' {
            $Results = Get-ServiceAccountAge -DaysOldThreshold 1
            $Medium = $Results | Where-Object { $_.SAMAccountName -eq 'svc-medium' }
            $Medium.RiskRating | Should -Be 'MEDIUM'
        }

        It 'Should assign LOW rating for passwords <1 year old' {
            $Results = Get-ServiceAccountAge -DaysOldThreshold 1
            $Low = $Results | Where-Object { $_.SAMAccountName -eq 'svc-recent' }
            $Low.RiskRating | Should -Be 'LOW'
        }

        It 'Should filter by threshold and exclude recent accounts' {
            $Results = Get-ServiceAccountAge -DaysOldThreshold 365
            $Results | Where-Object { $_.SAMAccountName -eq 'svc-recent' } | Should -BeNullOrEmpty
        }

        It 'Should sort by password age descending (oldest first)' {
            $Results = Get-ServiceAccountAge -DaysOldThreshold 1
            $Results[0].PasswordAge | Should -BeGreaterThan $Results[-1].PasswordAge
        }

        It 'Should include HasSPN flag in output' {
            $Results = Get-ServiceAccountAge -DaysOldThreshold 1
            $WithSPN = $Results | Where-Object { $_.SAMAccountName -eq 'svc-ancient' }
            $WithSPN.HasSPN | Should -BeTrue
        }
    }

    Context 'Get-SPNReport Mock Tests' {

        BeforeAll {
            $Now = Get-Date

            $MockSPNAccounts = @(
                # Kerberoastable user with old password and weak encryption
                [PSCustomObject]@{
                    SAMAccountName                  = 'svc-sqlkrb'
                    DisplayName                     = 'SQL Kerberoastable'
                    DistinguishedName               = 'CN=svc-sqlkrb,OU=ServiceAccounts,DC=contoso,DC=com'
                    Enabled                         = $true
                    PasswordLastSet                 = $Now.AddDays(-1200)
                    PasswordNeverExpires            = $true
                    ServicePrincipalName            = @('MSSQLSvc/sql01.contoso.com:1433', 'MSSQLSvc/sql01.contoso.com')
                    'msDS-SupportedEncryptionTypes' = 4  # RC4 only
                    LastLogonDate                   = $Now.AddDays(-1)
                    Description                     = 'SQL service'
                    MemberOf                        = @()
                    ObjectClass                     = 'user'
                }
                # Kerberoastable user with recent password and AES
                [PSCustomObject]@{
                    SAMAccountName                  = 'svc-iis'
                    DisplayName                     = 'IIS Service'
                    DistinguishedName               = 'CN=svc-iis,OU=ServiceAccounts,DC=contoso,DC=com'
                    Enabled                         = $true
                    PasswordLastSet                 = $Now.AddDays(-60)
                    PasswordNeverExpires            = $false
                    ServicePrincipalName            = @('HTTP/web01.contoso.com')
                    'msDS-SupportedEncryptionTypes' = 24  # AES128 + AES256
                    LastLogonDate                   = $Now.AddDays(-1)
                    Description                     = 'IIS app pool'
                    MemberOf                        = @()
                    ObjectClass                     = 'user'
                }
            )

            Mock Get-ADUser -ModuleName 'ServiceAccount-Audit' -MockWith {
                return $MockSPNAccounts
            }

            Mock Get-ADComputer -ModuleName 'ServiceAccount-Audit' -MockWith {
                return @()
            }
        }

        It 'Should flag user accounts with SPNs as KERBEROASTABLE' {
            $Results = Get-SPNReport
            $Krb = $Results | Where-Object { $_.SAMAccountName -eq 'svc-sqlkrb' }
            $Krb | Should -Not -BeNullOrEmpty
            $Krb[0].Finding | Should -Match 'KERBEROASTABLE'
        }

        It 'Should flag RC4 encryption as WEAK ENCRYPTION' {
            $Results = Get-SPNReport
            $Weak = $Results | Where-Object { $_.SAMAccountName -eq 'svc-sqlkrb' }
            $Weak[0].Finding | Should -Match 'WEAK ENCRYPTION'
        }

        It 'Should flag OLD PASSWORD + SPN for old passwords on SPN accounts' {
            $Results = Get-SPNReport
            $Old = $Results | Where-Object { $_.SAMAccountName -eq 'svc-sqlkrb' }
            $Old[0].Finding | Should -Match 'OLD PASSWORD \+ SPN'
        }

        It 'Should return one row per SPN (multiple SPNs = multiple rows)' {
            $Results = Get-SPNReport
            $SqlRows = @($Results | Where-Object { $_.SAMAccountName -eq 'svc-sqlkrb' })
            $SqlRows.Count | Should -Be 2
        }

        It 'Should include EncryptionTypes in output' {
            $Results = Get-SPNReport
            $Account = $Results | Where-Object { $_.SAMAccountName -eq 'svc-sqlkrb' } | Select-Object -First 1
            $Account.EncryptionTypes | Should -Match 'RC4'
        }

        It 'Should not flag AES-only accounts as WEAK ENCRYPTION' {
            $Results = Get-SPNReport
            $AES = $Results | Where-Object { $_.SAMAccountName -eq 'svc-iis' }
            $AES[0].Finding | Should -Not -Match 'WEAK ENCRYPTION'
        }

        It 'Should not flag recent passwords as OLD PASSWORD + SPN' {
            $Results = Get-SPNReport
            $Recent = $Results | Where-Object { $_.SAMAccountName -eq 'svc-iis' }
            $Recent[0].Finding | Should -Not -Match 'OLD PASSWORD \+ SPN'
        }
    }

    Context 'Get-ServiceAccountUsage Mock Tests' {

        BeforeAll {
            $MockRemoteServices = @(
                # Domain service account
                [PSCustomObject]@{
                    Name            = 'MSSQLSERVER'
                    DisplayName     = 'SQL Server (MSSQLSERVER)'
                    StartName       = 'CONTOSO\svc-sqlprod'
                    StartMode       = 'Auto'
                    State           = 'Running'
                    PSComputerName  = 'SQL01'
                }
                # Another domain account on different server
                [PSCustomObject]@{
                    Name            = 'MSSQLSERVER'
                    DisplayName     = 'SQL Server (MSSQLSERVER)'
                    StartName       = 'CONTOSO\svc-sqlprod'
                    StartMode       = 'Auto'
                    State           = 'Running'
                    PSComputerName  = 'SQL02'
                }
                # Built-in account (should be filtered)
                [PSCustomObject]@{
                    Name            = 'Spooler'
                    DisplayName     = 'Print Spooler'
                    StartName       = 'NT AUTHORITY\SYSTEM'
                    StartMode       = 'Auto'
                    State           = 'Running'
                    PSComputerName  = 'SQL01'
                }
                # UPN format domain account
                [PSCustomObject]@{
                    Name            = 'AppService'
                    DisplayName     = 'Application Service'
                    StartName       = 'svc-app@contoso.com'
                    StartMode       = 'Auto'
                    State           = 'Running'
                    PSComputerName  = 'APP01'
                }
                # LocalService (should be filtered)
                [PSCustomObject]@{
                    Name            = 'EventLog'
                    DisplayName     = 'Windows Event Log'
                    StartName       = 'NT AUTHORITY\LocalService'
                    StartMode       = 'Auto'
                    State           = 'Running'
                    PSComputerName  = 'APP01'
                }
            )

            Mock Invoke-Command -ModuleName 'ServiceAccount-Audit' -MockWith {
                return $MockRemoteServices
            }
        }

        It 'Should discover domain service accounts on remote servers' {
            $Results = Get-ServiceAccountUsage -ComputerName 'SQL01', 'SQL02', 'APP01'
            $Results | Should -Not -BeNullOrEmpty
        }

        It 'Should find CONTOSO\svc-sqlprod running on multiple servers' {
            $Results = Get-ServiceAccountUsage -ComputerName 'SQL01', 'SQL02'
            $SqlAccount = @($Results | Where-Object { $_.StartName -eq 'CONTOSO\svc-sqlprod' })
            $SqlAccount.Count | Should -BeGreaterOrEqual 2
        }

        It 'Should filter out NT AUTHORITY\LocalService' {
            $Results = Get-ServiceAccountUsage -ComputerName 'APP01'
            $LocalSvc = $Results | Where-Object { $_.StartName -eq 'NT AUTHORITY\LocalService' }
            $LocalSvc | Should -BeNullOrEmpty
        }

        It 'Should detect UPN format service accounts' {
            $Results = Get-ServiceAccountUsage -ComputerName 'APP01'
            $UPN = $Results | Where-Object { $_.StartName -eq 'svc-app@contoso.com' }
            $UPN | Should -Not -BeNullOrEmpty
        }

        It 'Should include ComputerName in results' {
            $Results = Get-ServiceAccountUsage -ComputerName 'SQL01', 'SQL02'
            $Results[0].ComputerName | Should -Not -BeNullOrEmpty
        }

        It 'Should include ServiceName and ServiceDisplayName' {
            $Results = Get-ServiceAccountUsage -ComputerName 'SQL01'
            $Svc = $Results | Where-Object { $_.StartName -eq 'CONTOSO\svc-sqlprod' } | Select-Object -First 1
            $Svc.ServiceName | Should -Be 'MSSQLSERVER'
            $Svc.ServiceDisplayName | Should -Be 'SQL Server (MSSQLSERVER)'
        }

        It 'Should include StartMode and State' {
            $Results = Get-ServiceAccountUsage -ComputerName 'SQL01'
            $Svc = $Results | Select-Object -First 1
            $Svc.StartMode | Should -Not -BeNullOrEmpty
            $Svc.State | Should -Not -BeNullOrEmpty
        }
    }
}