Skip to content
4 changes: 4 additions & 0 deletions src/common/ModuleUtils.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ function Export-Types {
[PSModuleInfo]$Module = (Get-PSCallStack)[0].InvocationInfo.MyCommand.ScriptBlock.Module
)

if (-not $Types -or $Types.Count -eq 0) {
return
}

if (-not $Module) {
throw [System.InvalidOperationException]::new('This function must be called from within a module.');
}
Expand Down
2 changes: 1 addition & 1 deletion src/common/Registry.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -213,4 +213,4 @@ function Invoke-OnEachUserHive {
}
}

Export-ModuleMember -Function New-RegistryKey, Remove-RegistryKey, Test-RegistryKey, Get-RegistryKey, Set-RegistryKey, Invoke-OnEachUserHive;
Export-ModuleMember -Function Invoke-EnsureRegistryPath, Remove-RegistryKey, Test-RegistryKey, Get-RegistryKey, Set-RegistryKey, Invoke-OnEachUserHive;
26 changes: 26 additions & 0 deletions tests/common/Analyser/Analyser.Tests.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
BeforeDiscovery { Import-Module "$PSScriptRoot/../../../src/common/Analyser.psm1" }

Describe 'Analyser Module Tests' {
Context 'SuppressAnalyserAttribute Functionality' {
It 'Should create SuppressAnalyserAttribute with all properties' {
$Attribute = [Compiler.Analyser.SuppressAnalyserAttribute]::new('TestCheck', 'TestData')
$Attribute.Justification = 'This is a test justification'

$Attribute | Should -Not -BeNullOrEmpty
$Attribute.CheckType | Should -Be 'TestCheck'
$Attribute.Data | Should -Be 'TestData'
$Attribute.Justification | Should -Be 'This is a test justification'
}

It 'Should support various data types for Data parameter' {
$StringAttr = [Compiler.Analyser.SuppressAnalyserAttribute]::new('StringCheck', 'StringData')
$StringAttr.Data | Should -Be 'StringData'

$NumberAttr = [Compiler.Analyser.SuppressAnalyserAttribute]::new('NumberCheck', 42)
$NumberAttr.Data | Should -Be 42

$NullAttr = [Compiler.Analyser.SuppressAnalyserAttribute]::new('NullCheck', $null)
$NullAttr.Data | Should -Be $null
}
}
}
3 changes: 0 additions & 3 deletions tests/common/Assert/Assert-Equal.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,4 @@ Describe 'Assert-Equal Tests' {
It 'Should not throw an error if the object equals the expected value' {
Assert-Equal -Object 'foo' -Expected 'foo';
}

Context 'Error Message Formatting' {
}
}
5 changes: 4 additions & 1 deletion tests/common/Cache/Get-CachedLocation.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ BeforeDiscovery { Import-Module -Force -Name $PSScriptRoot/../../../src/common/C

Describe 'Get-CachedLocation Tests' {
BeforeAll {
$CacheName = "UNIQUE_CACHE_NAME";
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
'UseDeclaredVarsMoreThanAssignments',
$null
)]$CacheName = 'UNIQUE_CACHE_NAME';
InModuleScope Cache {
$Script:Folder = "$((Get-PSDrive TestDrive).Root)\PSCache"
}
Expand Down
323 changes: 323 additions & 0 deletions tests/common/Connection/Connection.Tests.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,323 @@
Describe "Connection Module Tests" {
BeforeAll {
# Import required modules
Import-Module "$PSScriptRoot/../../../src/common/Connection.psm1" -Force

# Mock external dependencies for cross-platform testing
Mock Connect-ExchangeOnline { } -ModuleName Connection
Mock Disconnect-ExchangeOnline { } -ModuleName Connection
Mock Connect-IPPSSession { } -ModuleName Connection
Mock Connect-MgGraph { } -ModuleName Connection
Mock Disconnect-MgGraph { } -ModuleName Connection
Mock Get-ConnectionInformation {
[PSCustomObject]@{
UserPrincipalName = '[email protected]'
ConnectionId = 'test-connection-id'
}
} -ModuleName Connection
Mock Get-MgContext {
[PSCustomObject]@{
Account = '[email protected]'
Scopes = @('User.Read', 'Mail.Read')
}
} -ModuleName Connection
Mock Get-UserConfirmation { $true } -ModuleName Connection
}

Context "Module Import" {
It "Should import Connection module successfully" {
Get-Module -Name Connection* | Should -Not -BeNullOrEmpty
}

It "Should export expected functions" {
$ExportedFunctions = (Get-Module -Name Connection*).ExportedFunctions.Keys
$ExportedFunctions | Should -Contain 'Connect-Service'
}
}



Context "ExchangeOnline Service Tests" {
BeforeEach {
Mock Get-ConnectionInformation { $null } -ModuleName Connection
}

It "Should handle ExchangeOnline connection" {
Mock Connect-ExchangeOnline { } -ModuleName Connection
Mock Get-ConnectionInformation {
[PSCustomObject]@{
UserPrincipalName = '[email protected]'
ConnectionId = ange-connection'
}
} -ModuleName Connection

{ Connect-Service -Services 'ExchangeOnline' -DontConfirm } | Should -Not -Throw

Should -Invoke Connect-ExchangeOnline -Times 1 -ModuleName Connection
}

It "Should handle existing ExchangeOnline connection" {
Mock Get-ConnectionInformation {
[PSCustomObject]@{
UserPrincipalName = '[email protected]'
ConnectionId = 'existing-connection'
}
} -ModuleName Connection
Mock Get-UserConfirmation { $true } -ModuleName Connection

{ Connect-Service -Services 'ExchangeOnline' } | Should -Not -Throw

# Should not call Connect-ExchangeOnline if already connected and user confirms
Should -Invoke Get-UserConfirmation -Times 1 -ModuleName Connection
}

It "Should disconnect and reconnect if user declines" {
Mock Get-ConnectionInformation {
[PSCustomObject]@{
UserPrincipalName = '[email protected]'
ConnectionId = 'existing-connection'
}
} -ModuleName Connection
Mock Get-UserConfirmation { $false } -ModuleName Connection
Mock Disconnect-ExchangeOnline { } -ModuleName Connection
Mock Connect-ExchangeOnline { } -ModuleName Connection

{ Connect-Service -Services 'ExchangeOnline' } | Should -Not -Throw

Should -Invoke Disconnect-ExchangeOnline -Times 1 -ModuleName Connection
Should -Invoke Connect-ExchangeOnline -Times 1 -ModuleName Connection
}
}

Context "SecurityComplience Service Tests" {
BeforeEach {
Mock Get-ConnectionInformation { $null } -ModuleName Connection
}

It "Should handle SecurityComplience connection" {
Mock Connect-IPPSSession { } -ModuleName Connection
Mock Get-ConnectionInformation {
[PSCustomObject]@{
UserPrincipalName = '[email protected]'
ConnectionId = 'ipps-connection'
}
} -ModuleName Connection

{ Connect-Service -Services 'SecurityComplience' -DontConfirm } | Should -Not -Throw

Should -Invoke Connect-IPPSSession -Times 1 -ModuleName Connection
}

It "Should handle SecurityComplience disconnection" {
Mock Get-ConnectionInformation {
[PSCustomObject]@{
UserPrincipalName = '[email protected]'
ConnectionId = 'ipps-connection'
}
} -ModuleName Connection
Mock Disconnect-ExchangeOnline { } -ModuleName Connection

# The disconnect for SecurityComplience uses Disconnect-ExchangeOnline
{ Connect-Service -Services 'SecurityComplience' -CheckOnly } | Should -Not -Throw
}
}

Context "Graph Service Tests" {
BeforeEach {
Mock Get-MgContext { $null } -ModuleName Connection
}

It "Should handle Graph connection without scopes" {
Mock Connect-MgGraph { } -ModuleName Connection
Mock Get-MgContext {
[PSCustomObject]@{
Account = '[email protected]'
Scopes = @()
}
} -ModuleName Connection

{ Connect-Service -Services 'Graph' -DontConfirm } | Should -Not -Throw

Should -Invoke Connect-MgGraph -Times 1 -ModuleName Connection
}

It "Should handle Graph connection with scopes" {
Mock Connect-MgGraph { } -ModuleName Connection
Mock Get-MgContext {
[PSCustomObject]@{
Account = '[email protected]'
Scopes = @('User.Read', 'Mail.Read')
}
} -ModuleName Connection

$Scopes = @('User.Read', 'Mail.Read')
{ Connect-Service -Services 'Graph' -Scopes $Scopes -DontConfirm } | Should -Not -Throw

Should -Invoke Connect-MgGraph -Times 1 -ModuleName Connection
}

It "Should handle Graph connection with access token" {
Mock Connect-MgGraph { } -ModuleName Connection
Mock Get-MgContext {
[PSCustomObject]@{
Account = '[email protected]'
Scopes = @('User.Read')
}
} -ModuleName Connection

$SecureToken = ConvertTo-SecureString 'token123' -AsPlainText -Force
{ Connect-Service -Services 'Graph' -AccessToken $SecureToken -DontConfirm } | Should -Not -Throw

Should -Invoke Connect-MgGraph -Times 1 -ModuleName Connection -ParameterFilter { $AccessToken -ne $null }
}

It "Should handle insufficient scopes in Graph connection" {
Mock Connect-MgGraph { } -ModuleName Connection
Mock Get-MgContext {
[PSCustomObject]@{
Account = '[email protected]'
Scopes = @('User.Read') # Missing Mail.Read
}
} -ModuleName Connection
Mock Disconnect-MgGraph { } -ModuleName Connection

$RequiredScopes = @('User.Read', 'Mail.Read')
{ Connect-Service -Services 'Graph' -Scopes $RequiredScopes -DontConfirm } | Should -Not -Throw

# Should disconnect due to insufficient scopes
Should -Invoke Disconnect-MgGraph -Times 1 -ModuleName Connection
}

It "Should handle Graph disconnection" {
Mock Disconnect-MgGraph { } -ModuleName Connection

{ Disconnect-MgGraph } | Should -Not -Throw

Should -Invoke Disconnect-MgGraph -Times 1 -ModuleName Connection
}
}

Context 'CheckOnly Parameter Tests' {
It 'Should check connection status without connecting' {
Mock Get-ConnectionInformation { $null } -ModuleName Connection
Mock Invoke-FailedExit { throw 'Not connected' } -ModuleName Connection

{ Connect-Service -Services 'ExchangeOnline' -CheckOnly } | Should -Throw 'Not connected'

# Should not attempt to connect
Should -Invoke Connect-ExchangeOnline -Times 0 -ModuleName Connection
}

It "Should pass check when already connected" {
Mock Get-ConnectionInformation {
[PSCustomObject]@{
UserPrincipalName = '[email protected]'
ConnectionId = 'existing-connection'
}
} -ModuleName Connection

{ Connect-Service -Services 'ExchangeOnline' -CheckOnly } | Should -Not -Throw
}
}

Context "DontConfirm Parameter Tests" {
It "Should skip confirmation when DontConfirm is used" {
Mock Get-ConnectionInformation {
[PSCustomObject]@{
UserPrincipalName = '[email protected]'
ConnectionId = 'existing-connection'
}
} -ModuleName Connection

{ Connect-Service -Services 'ExchangeOnline' -DontConfirm } | Should -Not -Throw

# Should not call Get-UserConfirmation
Should -Invoke Get-UserConfirmation -Times 0 -ModuleName Connection
}

It "Should prompt for confirmation by default" {
Mock Get-ConnectionInformation {
[PSCustomObject]@{
UserPrincipalName = '[email protected]'
ConnectionId = 'existing-connection'
}
} -ModuleName Connection
Mock Get-UserConfirmation { $true } -ModuleName Connection

{ Connect-Service -Services 'ExchangeOnline' } | Should -Not -Throw

Should -Invoke Get-UserConfirmation -Times 1 -ModuleName Connection
}
}

Context "Error Handling" {
It "Should handle connection failures" {
Mock Get-ConnectionInformation { $null } -ModuleName Connection
Mock Connect-ExchangeOnline { throw "Connection failed" } -ModuleName Connection
Mock Invoke-FailedExit { throw "Failed to connect" } -ModuleName Connection

{ Connect-Service -Services 'ExchangeOnline' -DontConfirm } | Should -Throw

Should -Invoke Invoke-FailedExit -Times 1 -ModuleName Connection
}

It "Should handle disconnection failures" {
Mock Disconnect-ExchangeOnline { throw "Disconnect failed" } -ModuleName Connection
Mock Invoke-FailedExit { throw "Failed to disconnect" } -ModuleName Connection

{ Disconnect-ExchangeOnline } | Should -Throw
}

It "Should handle Graph context retrieval failures" {
Mock Get-MgContext { throw "Graph context error" } -ModuleName Connection
Mock Connect-MgGraph { } -ModuleName Connection

{ Connect-Service -Services 'Graph' -DontConfirm } | Should -Not -Throw

# Should attempt to connect when context retrieval fails
Should -Invoke Connect-MgGraph -Times 1 -ModuleName Connection
}
}

Context "Multiple Services Integration" {
It "Should handle connecting to multiple services" {
Mock Get-ConnectionInformation { $null } -ModuleName Connection
Mock Get-MgContext { $null } -ModuleName Connection
Mock Connect-ExchangeOnline { } -ModuleName Connection
Mock Connect-MgGraph { } -ModuleName Connection

{ Connect-Service -Services @('ExchangeOnline', 'Graph') -DontConfirm } | Should -Not -Throw

Should -Invoke Connect-ExchangeOnline -Times 1 -ModuleName Connection
Should -Invoke Connect-MgGraph -Times 1 -ModuleName Connection
}

It "Should handle mixed connection states" {
# ExchangeOnline already connected, Graph not connected
Mock Get-ConnectionInformation {
param($ConnectionId)
if ($ConnectionId) {
[PSCustomObject]@{
UserPrincipalName = '[email protected]'
ConnectionId = $ConnectionId
}
} else {
[PSCustomObject]@{
UserPrincipalName = '[email protected]'
ConnectionId = 'exchange-connection'
}
}
} -ModuleName Connection
Mock Get-MgContext { $null } -ModuleName Connection
Mock Connect-MgGraph { } -ModuleName Connection

{ Connect-Service -Services @('ExchangeOnline', 'Graph') -DontConfirm } | Should -Not -Throw

# Should only connect to Graph
Should -Invoke Connect-ExchangeOnline -Times 0 -ModuleName Connection
Should -Invoke Connect-MgGraph -Times 1 -ModuleName Connection
}
}


}
Loading
Loading