Tests/AD-UserLifecycle.Tests.ps1
|
BeforeAll { $modulePath = Split-Path -Parent $PSScriptRoot Import-Module "$modulePath\AD-UserLifecycle.psd1" -Force } Describe 'AD-UserLifecycle Module' { Context 'Module Loading' { It 'Should import without errors' { { Import-Module "$PSScriptRoot\..\AD-UserLifecycle.psd1" -Force } | Should -Not -Throw } It 'Should export exactly 3 public functions' { $commands = Get-Command -Module AD-UserLifecycle $commands.Count | Should -Be 3 } It 'Should export New-ADUserFromTemplate' { Get-Command -Module AD-UserLifecycle -Name New-ADUserFromTemplate | Should -Not -BeNullOrEmpty } It 'Should export Disable-DepartedUser' { Get-Command -Module AD-UserLifecycle -Name Disable-DepartedUser | Should -Not -BeNullOrEmpty } It 'Should export Export-ADUserReport' { Get-Command -Module AD-UserLifecycle -Name Export-ADUserReport | Should -Not -BeNullOrEmpty } It 'Should not export private functions' { { Get-Command -Module AD-UserLifecycle -Name _New-RandomPassword -ErrorAction Stop } | Should -Throw } } Context 'New-ADUserFromTemplate Parameter Validation' { It 'Should have mandatory FirstName parameter in Single set' { (Get-Command New-ADUserFromTemplate).Parameters['FirstName'].Attributes.Mandatory | Should -Contain $true } It 'Should have mandatory LastName parameter in Single set' { (Get-Command New-ADUserFromTemplate).Parameters['LastName'].Attributes.Mandatory | Should -Contain $true } It 'Should have mandatory Template parameter in Single set' { (Get-Command New-ADUserFromTemplate).Parameters['Template'].Attributes.Mandatory | Should -Contain $true } It 'Should have mandatory CsvPath parameter in Bulk set' { (Get-Command New-ADUserFromTemplate).Parameters['CsvPath'].ParameterSets['Bulk'].IsMandatory | Should -BeTrue } It 'Should support -WhatIf' { (Get-Command New-ADUserFromTemplate).Parameters.ContainsKey('WhatIf') | Should -BeTrue } It 'Should have two parameter sets: Single and Bulk' { $cmd = Get-Command New-ADUserFromTemplate $cmd.ParameterSets.Name | Should -Contain 'Single' $cmd.ParameterSets.Name | Should -Contain 'Bulk' } It 'Should default to Single parameter set' { (Get-Command New-ADUserFromTemplate).DefaultParameterSet | Should -Be 'Single' } It 'Should validate CsvPath exists' { $validateScript = (Get-Command New-ADUserFromTemplate).Parameters['CsvPath'].Attributes | Where-Object { $_ -is [System.Management.Automation.ValidateScriptAttribute] } $validateScript | Should -Not -BeNullOrEmpty } } Context 'New-ADUserFromTemplate Mocked Execution' { BeforeAll { # Mock AD cmdlets to test logic without a domain Mock -ModuleName AD-UserLifecycle Import-Module { } Mock -ModuleName AD-UserLifecycle Get-ADUser { if ($Identity -eq 'Template.IT') { [PSCustomObject]@{ DistinguishedName = 'CN=Template.IT,OU=Templates,OU=IT,DC=contoso,DC=com' MemberOf = @('CN=IT-Staff,OU=Groups,DC=contoso,DC=com') StreetAddress = '123 Main St' City = 'Seattle' State = 'WA' PostalCode = '98101' Office = 'HQ' Company = 'Contoso' } } elseif ($Filter) { $null } # SAMAccountName uniqueness check } Mock -ModuleName AD-UserLifecycle Get-ADDomain { [PSCustomObject]@{ DNSRoot = 'contoso.com' } } Mock -ModuleName AD-UserLifecycle New-ADUser { } Mock -ModuleName AD-UserLifecycle Add-ADGroupMember { } Mock -ModuleName AD-UserLifecycle Start-Transcript { } Mock -ModuleName AD-UserLifecycle Stop-Transcript { } } It 'Should call New-ADUser with correct display name format' { New-ADUserFromTemplate -FirstName 'Jane' -LastName 'Smith' -Template 'Template.IT' -EnableAccount -Confirm:$false Should -Invoke -CommandName New-ADUser -ModuleName AD-UserLifecycle -ParameterFilter { $Name -eq 'Smith, Jane' -and $DisplayName -eq 'Smith, Jane' } } It 'Should generate SAMAccountName as lastnamefirstinitial' { New-ADUserFromTemplate -FirstName 'Jane' -LastName 'Smith' -Template 'Template.IT' -EnableAccount -Confirm:$false Should -Invoke -CommandName New-ADUser -ModuleName AD-UserLifecycle -ParameterFilter { $SAMAccountName -eq 'smithj' } } It 'Should copy group memberships from template' { New-ADUserFromTemplate -FirstName 'Jane' -LastName 'Smith' -Template 'Template.IT' -EnableAccount -Confirm:$false Should -Invoke -CommandName Add-ADGroupMember -ModuleName AD-UserLifecycle -Times 1 } It 'Should return result object with Created status' { $result = New-ADUserFromTemplate -FirstName 'Jane' -LastName 'Smith' -Template 'Template.IT' -EnableAccount -Confirm:$false $result.Status | Should -Be 'Created' $result.SAMAccountName | Should -Be 'smithj' } } Context 'Disable-DepartedUser Parameter Validation' { It 'Should have mandatory Identity parameter' { (Get-Command Disable-DepartedUser).Parameters['Identity'].Attributes.Mandatory | Should -Contain $true } It 'Should accept pipeline input for Identity' { (Get-Command Disable-DepartedUser).Parameters['Identity'].Attributes.ValueFromPipeline | Should -Contain $true } It 'Should support -WhatIf' { (Get-Command Disable-DepartedUser).Parameters.ContainsKey('WhatIf') | Should -BeTrue } It 'Should have High ConfirmImpact' { $cmdletBinding = (Get-Command Disable-DepartedUser).ScriptBlock.Attributes | Where-Object { $_ -is [System.Management.Automation.CmdletBindingAttribute] } $cmdletBinding.ConfirmImpact | Should -Be 'High' } It 'Should accept SAMAccountName as alias for Identity' { (Get-Command Disable-DepartedUser).Parameters['Identity'].Aliases | Should -Contain 'SAMAccountName' } } Context 'Disable-DepartedUser Mocked Execution' { BeforeAll { Mock -ModuleName AD-UserLifecycle Import-Module { } Mock -ModuleName AD-UserLifecycle Get-ADUser { [PSCustomObject]@{ SAMAccountName = 'jsmith' GivenName = 'Jane' Surname = 'Smith' DistinguishedName = 'CN=Jane Smith,OU=IT,DC=contoso,DC=com' HomeDirectory = $null MemberOf = @( 'CN=IT-Staff,OU=Groups,DC=contoso,DC=com', 'CN=VPN-Users,OU=Groups,DC=contoso,DC=com' ) Description = 'Systems Administrator' } } Mock -ModuleName AD-UserLifecycle Get-ADGroup { [PSCustomObject]@{ Name = 'IT-Staff' } } Mock -ModuleName AD-UserLifecycle Disable-ADAccount { } Mock -ModuleName AD-UserLifecycle Set-ADAccountPassword { } Mock -ModuleName AD-UserLifecycle Remove-ADGroupMember { } Mock -ModuleName AD-UserLifecycle Set-ADUser { } Mock -ModuleName AD-UserLifecycle Move-ADObject { } Mock -ModuleName AD-UserLifecycle Start-Transcript { } Mock -ModuleName AD-UserLifecycle Stop-Transcript { } } It 'Should disable the account' { Disable-DepartedUser -Identity 'jsmith' -DisabledOU 'OU=Disabled,DC=contoso,DC=com' -SkipHomeFolder -Confirm:$false Should -Invoke -CommandName Disable-ADAccount -ModuleName AD-UserLifecycle -Times 1 } It 'Should reset the password' { Disable-DepartedUser -Identity 'jsmith' -DisabledOU 'OU=Disabled,DC=contoso,DC=com' -SkipHomeFolder -Confirm:$false Should -Invoke -CommandName Set-ADAccountPassword -ModuleName AD-UserLifecycle -Times 1 } It 'Should remove from all groups' { Disable-DepartedUser -Identity 'jsmith' -DisabledOU 'OU=Disabled,DC=contoso,DC=com' -SkipHomeFolder -Confirm:$false Should -Invoke -CommandName Remove-ADGroupMember -ModuleName AD-UserLifecycle -Times 2 } It 'Should move to the Disabled OU' { Disable-DepartedUser -Identity 'jsmith' -DisabledOU 'OU=Disabled,DC=contoso,DC=com' -SkipHomeFolder -Confirm:$false Should -Invoke -CommandName Move-ADObject -ModuleName AD-UserLifecycle -ParameterFilter { $TargetPath -eq 'OU=Disabled,DC=contoso,DC=com' } } It 'Should return Offboarded status' { $result = Disable-DepartedUser -Identity 'jsmith' -DisabledOU 'OU=Disabled,DC=contoso,DC=com' -SkipHomeFolder -Confirm:$false $result.Status | Should -Be 'Offboarded' } } Context 'Export-ADUserReport Parameter Validation' { It 'Should default OutputFormat to HTML' { (Get-Command Export-ADUserReport).Parameters['OutputFormat'].ParameterSets.Values.HelpMessage | Should -BeNullOrEmpty } It 'Should validate OutputFormat values' { $validateSet = (Get-Command Export-ADUserReport).Parameters['OutputFormat'].Attributes | Where-Object { $_ -is [System.Management.Automation.ValidateSetAttribute] } $validateSet.ValidValues | Should -Contain 'HTML' $validateSet.ValidValues | Should -Contain 'CSV' $validateSet.ValidValues | Should -Contain 'Both' } It 'Should default DaysInactive to 90' { (Get-Command Export-ADUserReport).Parameters.ContainsKey('DaysInactive') | Should -BeTrue } } Context '_New-RandomPassword' { BeforeAll { # Access private function via module scope $pwFunc = & (Get-Module AD-UserLifecycle) { Get-Command _New-RandomPassword } } It 'Should return a SecureString' { $result = & (Get-Module AD-UserLifecycle) { _New-RandomPassword } $result | Should -BeOfType [System.Security.SecureString] } It 'Should generate password of specified length' { $result = & (Get-Module AD-UserLifecycle) { _New-RandomPassword -Length 20 } $plain = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto( [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($result) ) $plain.Length | Should -Be 20 } It 'Should include uppercase, lowercase, digits, and special characters' { $result = & (Get-Module AD-UserLifecycle) { _New-RandomPassword -Length 32 } $plain = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto( [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($result) ) $plain | Should -Match '[A-Z]' $plain | Should -Match '[a-z]' $plain | Should -Match '[0-9]' $plain | Should -Match '[!@#$%&*?]' } } } |