Tests/EntraID-SecurityAudit.Tests.ps1
|
BeforeAll { $modulePath = Split-Path -Parent (Split-Path -Parent $PSScriptRoot) $moduleName = 'EntraID-SecurityAudit' $manifestPath = Join-Path $modulePath "$moduleName\$moduleName.psd1" if (Get-Module $moduleName) { Remove-Module $moduleName -Force } Import-Module $manifestPath -Force } Describe 'Module: EntraID-SecurityAudit' { Context 'Module Loading' { It 'Imports without errors' { { Import-Module $manifestPath -Force } | Should -Not -Throw } It 'Exports exactly 5 public functions' { $exported = (Get-Module $moduleName).ExportedFunctions.Keys $exported.Count | Should -Be 5 } It 'Exports Invoke-EntraSecurityAudit' { Get-Command -Module $moduleName -Name 'Invoke-EntraSecurityAudit' | Should -Not -BeNullOrEmpty } It 'Exports Get-EntraUserRiskReport' { Get-Command -Module $moduleName -Name 'Get-EntraUserRiskReport' | Should -Not -BeNullOrEmpty } It 'Exports Get-EntraAppPermissionAudit' { Get-Command -Module $moduleName -Name 'Get-EntraAppPermissionAudit' | Should -Not -BeNullOrEmpty } It 'Exports Get-EntraSignInAnalysis' { Get-Command -Module $moduleName -Name 'Get-EntraSignInAnalysis' | Should -Not -BeNullOrEmpty } It 'Exports Get-EntraPrivilegedRoleReview' { Get-Command -Module $moduleName -Name 'Get-EntraPrivilegedRoleReview' | Should -Not -BeNullOrEmpty } It 'Does not export private functions' { $exported = (Get-Module $moduleName).ExportedFunctions.Keys $exported | Should -Not -Contain 'Test-GraphConnection' $exported | Should -Not -Contain 'New-HtmlDashboard' } } Context 'Parameter Validation' { It 'Invoke-EntraSecurityAudit has OutputPath parameter' { (Get-Command Invoke-EntraSecurityAudit).Parameters.Keys | Should -Contain 'OutputPath' } It 'Invoke-EntraSecurityAudit has DaysBack with ValidateRange 1-90' { $param = (Get-Command Invoke-EntraSecurityAudit).Parameters['DaysBack'] $param | Should -Not -BeNullOrEmpty $rangeAttr = $param.Attributes | Where-Object { $_ -is [System.Management.Automation.ValidateRangeAttribute] } $rangeAttr.MinRange | Should -Be 1 $rangeAttr.MaxRange | Should -Be 90 } It 'Invoke-EntraSecurityAudit has SkipSignIns switch' { $param = (Get-Command Invoke-EntraSecurityAudit).Parameters['SkipSignIns'] $param.SwitchParameter | Should -Be $true } It 'Get-EntraUserRiskReport has RiskLevelFilter with ValidateSet' { $param = (Get-Command Get-EntraUserRiskReport).Parameters['RiskLevelFilter'] $validateSet = $param.Attributes | Where-Object { $_ -is [System.Management.Automation.ValidateSetAttribute] } $validateSet.ValidValues | Should -Contain 'High' $validateSet.ValidValues | Should -Contain 'All' } It 'Get-EntraUserRiskReport has IncludeGuests switch' { $param = (Get-Command Get-EntraUserRiskReport).Parameters['IncludeGuests'] $param.SwitchParameter | Should -Be $true } It 'Get-EntraAppPermissionAudit has ExpirationWarningDays parameter' { $param = (Get-Command Get-EntraAppPermissionAudit).Parameters['ExpirationWarningDays'] $param | Should -Not -BeNullOrEmpty } It 'Get-EntraSignInAnalysis has FailureThreshold parameter' { $param = (Get-Command Get-EntraSignInAnalysis).Parameters['FailureThreshold'] $param | Should -Not -BeNullOrEmpty } It 'Get-EntraPrivilegedRoleReview has MaxGlobalAdmins with ValidateRange' { $param = (Get-Command Get-EntraPrivilegedRoleReview).Parameters['MaxGlobalAdmins'] $rangeAttr = $param.Attributes | Where-Object { $_ -is [System.Management.Automation.ValidateRangeAttribute] } $rangeAttr.MinRange | Should -Be 1 $rangeAttr.MaxRange | Should -Be 20 } } Context 'Mock-Based Execution' { It 'Get-EntraUserRiskReport processes users and flags missing MFA' { Mock -ModuleName $moduleName Get-MgContext { @{ Scopes = @('User.Read.All','Directory.Read.All','Application.Read.All'); Account = 'test@contoso.com'; TenantId = 'test-tenant' } } Mock -ModuleName $moduleName Get-MgUser { @( [PSCustomObject]@{ Id = '001'; DisplayName = 'Alice Admin'; UserPrincipalName = 'alice@contoso.com' UserType = 'Member'; AccountEnabled = $true RiskLevel = 'high'; RiskState = 'atRisk' SignInActivity = @{ LastSignInDateTime = (Get-Date).AddDays(-5).ToString('o') } }, [PSCustomObject]@{ Id = '002'; DisplayName = 'Bob User'; UserPrincipalName = 'bob@contoso.com' UserType = 'Member'; AccountEnabled = $true RiskLevel = $null; RiskState = $null SignInActivity = @{ LastSignInDateTime = (Get-Date).AddDays(-120).ToString('o') } } ) } Mock -ModuleName $moduleName Get-MgUserAuthenticationMethod { @([PSCustomObject]@{ AdditionalProperties = @{ '@odata.type' = '#microsoft.graph.passwordAuthenticationMethod' } }) } $result = Get-EntraUserRiskReport $result.Count | Should -Be 2 ($result | Where-Object { $_.Finding -match 'HIGH RISK' }).Count | Should -BeGreaterThan 0 ($result | Where-Object { $_.Finding -match 'NO MFA' }).Count | Should -Be 2 } It 'Get-EntraAppPermissionAudit processes apps and flags multi-tenant' { Mock -ModuleName $moduleName Get-MgContext { @{ Scopes = @('Application.Read.All'); Account = 'test@contoso.com'; TenantId = 'test-tenant' } } Mock -ModuleName $moduleName Get-MgApplication { @( [PSCustomObject]@{ Id = 'app-001'; DisplayName = 'Internal Tool'; AppId = 'guid-1' SignInAudience = 'AzureADMyOrg'; CreatedDateTime = (Get-Date).AddMonths(-6) RequiredResourceAccess = @(); PasswordCredentials = @(); KeyCredentials = @() }, [PSCustomObject]@{ Id = 'app-002'; DisplayName = 'External App'; AppId = 'guid-2' SignInAudience = 'AzureADMultipleOrgs'; CreatedDateTime = (Get-Date).AddMonths(-12) RequiredResourceAccess = @(); PasswordCredentials = @(); KeyCredentials = @() } ) } Mock -ModuleName $moduleName Get-MgApplicationOwner { @() } $result = Get-EntraAppPermissionAudit $result.Count | Should -Be 2 ($result | Where-Object { $_.Finding -match 'MULTI-TENANT' }).Count | Should -Be 1 ($result | Where-Object { $_.Finding -match 'NO OWNER' }).Count | Should -Be 2 } It 'Get-EntraPrivilegedRoleReview processes role assignments' { Mock -ModuleName $moduleName Get-MgContext { @{ Scopes = @('Directory.Read.All','RoleManagement.Read.Directory'); Account = 'test@contoso.com'; TenantId = 'test-tenant' } } Mock -ModuleName $moduleName Get-MgDirectoryRole { @( [PSCustomObject]@{ Id = 'role-001'; DisplayName = 'Global Administrator' }, [PSCustomObject]@{ Id = 'role-002'; DisplayName = 'User Administrator' } ) } Mock -ModuleName $moduleName Get-MgDirectoryRoleMember { param($DirectoryRoleId) @([PSCustomObject]@{ Id = 'user-001' AdditionalProperties = @{ displayName = 'Alice Admin' userPrincipalName = 'alice@contoso.com' '@odata.type' = '#microsoft.graph.user' } }) } Mock -ModuleName $moduleName Get-MgRoleManagementDirectoryRoleEligibilitySchedule { throw 'Not available' } $result = Get-EntraPrivilegedRoleReview $result.Count | Should -BeGreaterThan 0 ($result | Where-Object { $_.RoleName -eq 'Global Administrator' }).Count | Should -Be 1 } } Context 'HTML Report Generation' { It 'New-HtmlDashboard creates valid HTML file' { $testSections = @( @{ Title = 'Test Section' Data = @([PSCustomObject]@{ Name = 'Test'; Finding = 'OK' }) Summary = '1 item tested' } ) $testFile = Join-Path $TestDrive 'test-report.html' & (Get-Module $moduleName) { New-HtmlDashboard -Sections $args[0] -OutputFile $args[1] -ReportTitle 'Test Report' } $testSections $testFile Test-Path $testFile | Should -Be $true $content = Get-Content $testFile -Raw $content | Should -Match '<title>Test Report</title>' $content | Should -Match 'EntraID-SecurityAudit' $content | Should -Match 'Test Section' } } Context 'Module Manifest' { It 'Has a valid manifest' { { Test-ModuleManifest -Path $manifestPath } | Should -Not -Throw } It 'Has the correct version' { (Test-ModuleManifest -Path $manifestPath).Version | Should -Be '1.0.0' } It 'Has the correct author' { (Test-ModuleManifest -Path $manifestPath).Author | Should -Be 'Larry Roberts' } It 'Has ProjectUri set' { (Test-ModuleManifest -Path $manifestPath).ProjectUri | Should -Not -BeNullOrEmpty } It 'Has LicenseUri set' { (Test-ModuleManifest -Path $manifestPath).LicenseUri | Should -Not -BeNullOrEmpty } It 'Has relevant tags' { $tags = (Test-ModuleManifest -Path $manifestPath).Tags $tags | Should -Contain 'EntraID' $tags | Should -Contain 'Security' } } } |