Tests/Unit/Invoke-SPCTempElevationInternal.Tests.ps1
|
#Requires -Modules @{ ModuleName = 'Pester'; ModuleVersion = '5.0.0' } Describe 'Invoke-SPCTempElevationInternal' { BeforeAll { . (Join-Path $PSScriptRoot '../../Private/PnPWrappers.ps1') . (Join-Path $PSScriptRoot '../../Private/Invoke-SPCTempElevationInternal.ps1') } BeforeEach { $script:SPCContext = [PSCustomObject]@{ TenantName = 'contoso' AuthMethod = 'Interactive' OperatorUPN = 'admin@contoso.com' GraphAccessToken = 'mock_graph_token' PnPContext = [PSCustomObject]@{ Url = 'https://contoso-admin.sharepoint.com' } } } Context 'Strategy 1: CSOM Set-PnPTenantSite Success' { It 'Elevates operator to SCA via Set-PnPTenantSite when authorized' { Mock Set-PnPTenantSite {} Mock Start-Sleep {} $res = Invoke-SPCTempElevationInternal -SiteUrl 'https://contoso.sharepoint.com/sites/TestSite' $res.Success | Should -BeTrue $res.ElevationType | Should -Be 'SCA' $res.OperatorUPN | Should -Be 'admin@contoso.com' Should -Invoke -CommandName Set-PnPTenantSite -Times 1 -Exactly } } Context 'Strategy 2: REST /_api/SPO.Tenant/SetSiteAdmin' { It 'Elevates operator to SCA via Tenant REST API when Set-PnPTenantSite fails' { Mock Set-PnPTenantSite { throw 'Attempted to perform an unauthorized operation.' } Mock Invoke-PnPSPRestMethod { return [PSCustomObject]@{ value = $true } } Mock Start-Sleep {} $res = Invoke-SPCTempElevationInternal -SiteUrl 'https://contoso.sharepoint.com/sites/TestSite' $res.Success | Should -BeTrue $res.ElevationType | Should -Be 'REST_SCA' Should -Invoke -CommandName Invoke-PnPSPRestMethod -Times 1 -Exactly } } Context 'Strategy 3: Graph Group Owner Fallback' { It 'Elevates operator to M365 Group Owner via Graph when CSOM and REST throw unauthorized' { Mock Set-PnPTenantSite { throw 'Attempted to perform an unauthorized operation.' } Mock Invoke-PnPSPRestMethod { throw 'Unauthorized' } Mock Start-Sleep {} Mock Invoke-RestMethod { param($Uri, $Method, $Headers, $Body) if ($Uri -match 'groups\?\$filter=') { return [PSCustomObject]@{ value = @([PSCustomObject]@{ id = 'group-456' }) } } if ($Uri -match 'users/admin@contoso.com') { return [PSCustomObject]@{ id = 'user-oid-789' } } if ($Uri -match 'groups/group-456/owners/\$ref') { return $null } return $null } $res = Invoke-SPCTempElevationInternal -SiteUrl 'https://contoso.sharepoint.com/sites/TestGroup' $res.Success | Should -BeTrue $res.ElevationType | Should -Be 'GroupOwner' $res.GroupId | Should -Be 'group-456' } } Context 'Strategy 4: Graph Site Permissions API Fallback' { It 'Elevates operator via Graph /sites/{id}/permissions when other strategies fail' { Mock Set-PnPTenantSite { throw 'Unauthorized' } Mock Invoke-PnPSPRestMethod { throw 'Unauthorized' } Mock Start-Sleep {} Mock Invoke-RestMethod { param($Uri, $Method, $Headers, $Body) if ($Uri -match 'groups\?\$filter=') { return [PSCustomObject]@{ value = @() } } if ($Uri -match 'sites/contoso.sharepoint.com:/sites/TestSite\?\$select=id') { return [PSCustomObject]@{ id = 'site-id-999' } } if ($Uri -match 'users/admin@contoso.com') { return [PSCustomObject]@{ id = 'user-oid-789'; displayName = 'Admin User'; userPrincipalName = 'admin@contoso.com' } } if ($Uri -match 'sites/site-id-999/permissions') { return [PSCustomObject]@{ id = 'perm-id-111' } } return $null } $res = Invoke-SPCTempElevationInternal -SiteUrl 'https://contoso.sharepoint.com/sites/TestSite' $res.Success | Should -BeTrue $res.ElevationType | Should -Be 'GraphSitePerm' $res.PermissionId | Should -Be 'perm-id-111' } } Context 'Graceful Failure when all strategies are unauthorized' { It 'Returns structured failure object without throwing terminating error' { Mock Set-PnPTenantSite { throw 'Attempted to perform an unauthorized operation.' } Mock Invoke-PnPSPRestMethod { throw 'Unauthorized' } Mock Invoke-RestMethod { throw 'Unauthorized Graph call' } $res = Invoke-SPCTempElevationInternal -SiteUrl 'https://contoso.sharepoint.com/sites/StrictSite' $res.Success | Should -BeFalse $res.ElevationType | Should -Be 'None' $res.ErrorMessage | Should -Match 'All elevation strategies failed' } } Context 'Undo-SPCTempElevationInternal Rollback' { It 'Calls Remove-PnPSiteCollectionAdmin for SCA elevation' { Mock Remove-PnPSiteCollectionAdmin {} $rec = [PSCustomObject]@{ Success = $true ElevationType = 'SCA' SiteUrl = 'https://contoso.sharepoint.com/sites/TestSite' OperatorUPN = 'admin@contoso.com' } Undo-SPCTempElevationInternal -ElevationRecord $rec -SiteConnection 'conn' Should -Invoke -CommandName Remove-PnPSiteCollectionAdmin -Times 1 -Exactly } It 'Calls REST SPO.Tenant for REST_SCA elevation rollback' { Mock Invoke-PnPSPRestMethod {} $rec = [PSCustomObject]@{ Success = $true ElevationType = 'REST_SCA' SiteUrl = 'https://contoso.sharepoint.com/sites/TestSite' OperatorUPN = 'admin@contoso.com' } Undo-SPCTempElevationInternal -ElevationRecord $rec -SiteConnection 'conn' Should -Invoke -CommandName Invoke-PnPSPRestMethod -Times 1 -Exactly } It 'Removes Graph group owner for GroupOwner elevation' { Mock Invoke-RestMethod { param($Uri, $Method) if ($Uri -match 'users/admin@contoso.com') { return [PSCustomObject]@{ id = 'user-oid-789' } } return $null } $rec = [PSCustomObject]@{ Success = $true ElevationType = 'GroupOwner' SiteUrl = 'https://contoso.sharepoint.com/sites/TestGroup' OperatorUPN = 'admin@contoso.com' GroupId = 'group-456' } Undo-SPCTempElevationInternal -ElevationRecord $rec -SiteConnection $null Should -Invoke -CommandName Invoke-RestMethod -Times 2 } It 'Removes Graph site permissions for GraphSitePerm elevation' { Mock Invoke-RestMethod {} $rec = [PSCustomObject]@{ Success = $true ElevationType = 'GraphSitePerm' SiteUrl = 'https://contoso.sharepoint.com/sites/TestSite' OperatorUPN = 'admin@contoso.com' GroupId = 'site-id-999' PermissionId = 'perm-id-111' } Undo-SPCTempElevationInternal -ElevationRecord $rec -SiteConnection $null Should -Invoke -CommandName Invoke-RestMethod -Times 1 -Exactly } } } |