Testing/Unit/PowerShell/Support/Get-CyberAssessmentAppPermission.Tests.ps1

Import-Module (Join-Path -Path $PSScriptRoot -ChildPath "../../../../Modules/Support/ServicePrincipal.psm1") -Force

InModuleScope ServicePrincipal {
    Describe "Get-CyberAssessmentAppPermission" {
        BeforeAll {
            # Load mock resource service principals - one for each API
            $MockResourceSP_Graph = Get-Content (Join-Path -Path $PSScriptRoot -ChildPath "ServicePrincipalSnippets/MockResourceSP_Graph.json") | ConvertFrom-Json
            $MockResourceSP_Exchange = Get-Content (Join-Path -Path $PSScriptRoot -ChildPath "ServicePrincipalSnippets/MockResourceSP_Exchange.json") | ConvertFrom-Json
            $MockResourceSP_SharePoint = Get-Content (Join-Path -Path $PSScriptRoot -ChildPath "ServicePrincipalSnippets/MockResourceSP_SharePoint.json") | ConvertFrom-Json

            # Mock all external dependencies
            Mock -CommandName Connect-GraphHelper -MockWith { return $null }

            Mock -CommandName Get-ServicePrincipalPermissions -MockWith {
                return @(
                    [PSCustomObject]@{
                        leastPermissions    = @('PrivilegedAccess.Read.AzureADGroup')
                        resourceAPIAppId    = '00000003-0000-0000-c000-000000000000'
                        cyberGearProduct    = 'aad'
                    },
                    [PSCustomObject]@{
                        leastPermissions    = @('Policy.Read.All')
                        resourceAPIAppId    = '00000003-0000-0000-c000-000000000000'
                        cyberGearProduct    = 'aad'
                    },
                    [PSCustomObject]@{
                        leastPermissions    = @('Exchange.ManageAsApp')
                        resourceAPIAppId    = '00000002-0000-0ff1-ce00-000000000000'
                        cyberGearProduct    = 'exo'
                    },
                    [PSCustomObject]@{
                        leastPermissions    = @('Sites.FullControl.All')
                        resourceAPIAppId    = '00000003-0000-0ff1-ce00-000000000000'
                        cyberGearProduct    = 'sharepoint'
                    },
                    [PSCustomObject]@{
                        leastPermissions    = @('PrivilegedEligibilitySchedule.Read.AzureADGroup')
                        resourceAPIAppId    = '00000003-0000-0000-c000-000000000000'
                        cyberGearProduct    = 'aad'
                    },
                    [PSCustomObject]@{
                        leastPermissions    = @('RoleManagementPolicy.Read.AzureADGroup')
                        resourceAPIAppId    = '00000003-0000-0000-c000-000000000000'
                        cyberGearProduct    = 'aad'
                    },
                    [PSCustomObject]@{
                        leastPermissions    = @('Directory.Read.All')
                        resourceAPIAppId    = '00000003-0000-0000-c000-000000000000'
                        cyberGearProduct    = 'aad'
                    },
                    [PSCustomObject]@{
                        leastPermissions    = @('RoleManagement.Read.Directory')
                        resourceAPIAppId    = '00000003-0000-0000-c000-000000000000'
                        cyberGearProduct    = 'aad'
                    },
                    [PSCustomObject]@{
                        leastPermissions    = @('User.Read.All')
                        resourceAPIAppId    = '00000003-0000-0000-c000-000000000000'
                        cyberGearProduct    = 'aad'
                    }
                )
            }

            Mock -CommandName Get-CyberAssessmentPermissions -MockWith {
                param($OutAs)
                if ($OutAs -eq 'role') {
                    return @('Global Reader')
                }
                return @()
            }

            Mock -CommandName Get-CyberAssessmentAppRoleID -MockWith {
                return @(
                    [PSCustomObject]@{
                        resourceAPIAppId = '00000003-0000-0000-c000-000000000000'
                        APIName = 'PrivilegedAccess.Read.AzureADGroup'
                        AppRoleID = '01e37dc9-c035-40bd-b438-b2879c4870a6'
                        Product = 'aad'
                    },
                    [PSCustomObject]@{
                        resourceAPIAppId = '00000003-0000-0000-c000-000000000000'
                        APIName = 'Policy.Read.All'
                        AppRoleID = '246dd0d5-5bd0-4def-940b-0421030a5b68'
                        Product = 'aad'
                    },
                    [PSCustomObject]@{
                        resourceAPIAppId = '00000003-0000-0000-c000-000000000000'
                        APIName = 'PrivilegedEligibilitySchedule.Read.AzureADGroup'
                        AppRoleID = 'edb419d6-7edc-42a3-9345-509bfdf5d87c'
                        Product = 'aad'
                    },
                    [PSCustomObject]@{
                        resourceAPIAppId = '00000003-0000-0000-c000-000000000000'
                        APIName = 'RoleManagementPolicy.Read.AzureADGroup'
                        AppRoleID = '69e67828-780e-47fd-b28c-7b27d14864e6'
                        Product = 'aad'
                    },
                    [PSCustomObject]@{
                        resourceAPIAppId = '00000003-0000-0000-c000-000000000000'
                        APIName = 'Directory.Read.All'
                        AppRoleID = '7ab1d382-f21e-4acd-a863-ba3e13f7da61'
                        Product = 'aad'
                    },
                    [PSCustomObject]@{
                        resourceAPIAppId = '00000003-0000-0000-c000-000000000000'
                        APIName = 'RoleManagement.Read.Directory'
                        AppRoleID = '483bed4a-2ad3-4361-a73b-c83ccdbdc53c'
                        Product = 'aad'
                    },
                    [PSCustomObject]@{
                        resourceAPIAppId = '00000003-0000-0000-c000-000000000000'
                        APIName = 'User.Read.All'
                        AppRoleID = 'df021288-bdef-4463-88db-98f22de89214'
                        Product = 'aad'
                    },
                    [PSCustomObject]@{
                        resourceAPIAppId = '00000002-0000-0ff1-ce00-000000000000'
                        APIName = 'Exchange.ManageAsApp'
                        AppRoleID = 'dc50a0fb-09a3-484d-be87-e023b12c6440'
                        Product = 'exo'
                    },
                    [PSCustomObject]@{
                        resourceAPIAppId = '00000003-0000-0ff1-ce00-000000000000'
                        APIName = 'Sites.FullControl.All'
                        AppRoleID = '678536fe-1083-478a-9c59-b99265e6b0d3'
                        Product = 'sharepoint'
                    }
                )
            }

            # Mock service principal and application lookups
            Mock -CommandName Invoke-GraphDirectly -MockWith {
                param($Commandlet)

                if ($Commandlet -eq 'Get-MgServicePrincipal') {
                    return @{
                        Value = $script:MockScenario.servicePrincipal
                    }
                }
                elseif ($Commandlet -eq 'Get-MgBetaApplication') {
                    return @{
                        Value = $script:MockScenario.application
                    }
                }
                elseif ($Commandlet -eq 'Get-MgServicePrincipalAppRoleAssignment') {
                    return @{
                        Value = $script:MockScenario.appRoleAssignments
                    }
                }
                elseif ($Commandlet -eq 'Get-MgServicePrincipalOauth2PermissionGrant') {
                    return @{
                        Value = $script:MockScenario.oauth2Grants
                    }
                }
                return @{ Value = @() }
            }

            # Mock batch request for resource service principals - accurately simulate the real behavior
            Mock -CommandName Invoke-GraphBatchRequest -MockWith {
                param($Requests)
                $results = @{}

                foreach ($req in $Requests) {
                    $requestId = $req.id

                    # Determine which resource SP to return based on the request type
                    if ($requestId -like "spById_*") {
                        # Direct lookup by ID (returns object directly in body)
                        $resourceId = $requestId -replace '^spById_', ''

                        $body = switch ($resourceId) {
                            "resource-sp-graph" { $MockResourceSP_Graph }
                            "resource-sp-exo" { $MockResourceSP_Exchange }
                            "resource-sp-sharepoint" { $MockResourceSP_SharePoint }
                            default { $null }
                        }

                        if ($body) {
                            $results[$requestId] = @{
                                id = $requestId
                                status = 200
                                body = $body
                            }
                        } else {
                            $results[$requestId] = @{
                                id = $requestId
                                status = 404
                                body = @{ error = @{ message = "Service principal not found" } }
                            }
                        }
                    }
                    elseif ($requestId -like "spByAppId_*") {
                        # Lookup by AppId (returns array in 'value' property for filter queries)
                        $appId = $requestId -replace '^spByAppId_', ''

                        $body = switch ($appId) {
                            "00000003-0000-0000-c000-000000000000" { $MockResourceSP_Graph }
                            "00000002-0000-0ff1-ce00-000000000000" { $MockResourceSP_Exchange }
                            "00000003-0000-0ff1-ce00-000000000000" { $MockResourceSP_SharePoint }
                            default { $null }
                        }

                        if ($body) {
                            # spByAppId returns results in a 'value' array (Graph filter queries)
                            $results[$requestId] = @{
                                id = $requestId
                                status = 200
                                body = @{
                                    value = @($body)
                                }
                            }
                        } else {
                            $results[$requestId] = @{
                                id = $requestId
                                status = 200
                                body = @{
                                    value = @()
                                }
                            }
                        }
                    }
                }

                return $results
            }

            # Mock permission comparison
            Mock -CommandName Compare-CyberAssessmentPermission -MockWith {
                return $script:MockScenario.permissionComparison
            }

            # Mock role comparison
            Mock -CommandName Compare-CyberAssessmentRole -MockWith {
                return $script:MockScenario.roleComparison
            }

            # Mock Power Platform check
            Mock -CommandName Test-PowerPlatformAppRegistration -MockWith {
                return $script:MockScenario.powerPlatformRegistered
            }
        }

        Context "When testing with scenario: OptimalPermissions" {
            BeforeEach {
                $script:MockScenario = Get-Content (Join-Path -Path $PSScriptRoot -ChildPath "ServicePrincipalSnippets/MockOptimalPermissions.json") | ConvertFrom-Json
            }

            It "Should not have FixPermissionIssues property when permissions are optimal" {
                $result = Get-CyberAssessmentAppPermission -AppID $script:MockScenario.servicePrincipal.appId -M365Environment 'commercial' -ProductNames $script:MockScenario.productNames

                $result.PSObject.Properties.Name | Should -Not -Contain 'FixPermissionIssues'
            }

            It "Should report 'No action needed' status" {
                $result = Get-CyberAssessmentAppPermission -AppID $script:MockScenario.servicePrincipal.appId -M365Environment 'commercial' -ProductNames $script:MockScenario.productNames

                $result.Status | Should -Match $script:MockScenario.expectedResult.statusPattern
            }

            It "Should have MissingPermissions as false" {
                $result = Get-CyberAssessmentAppPermission -AppID $script:MockScenario.servicePrincipal.appId -M365Environment 'commercial' -ProductNames $script:MockScenario.productNames

                $result.MissingPermissions | Should -Be $false
            }

            It "Should have ExtraPermissions as false" {
                $result = Get-CyberAssessmentAppPermission -AppID $script:MockScenario.servicePrincipal.appId -M365Environment 'commercial' -ProductNames $script:MockScenario.productNames

                $result.ExtraPermissions | Should -Be $false
            }

            It "Should have MissingRoles as false" {
                $result = Get-CyberAssessmentAppPermission -AppID $script:MockScenario.servicePrincipal.appId -M365Environment 'commercial' -ProductNames $script:MockScenario.productNames

                $result.MissingRoles | Should -Be $false
            }

            It "Should have Power Platform as registered equal true" {
                $result = Get-CyberAssessmentAppPermission -AppID $script:MockScenario.servicePrincipal.appId -M365Environment 'commercial' -ProductNames $script:MockScenario.productNames

                $result.PowerPlatformRegistered | Should -Be $true
            }
        }

        Context "When testing with scenario: MissingPermissions" {
            BeforeEach {
                $script:MockScenario = Get-Content (Join-Path -Path $PSScriptRoot -ChildPath "ServicePrincipalSnippets/MockMissingPermissions.json") | ConvertFrom-Json
            }

            It "Should have FixPermissionIssues property when permissions are missing" {
                $result = Get-CyberAssessmentAppPermission -AppID $script:MockScenario.servicePrincipal.appId -M365Environment 'commercial' -ProductNames $script:MockScenario.productNames

                $result.PSObject.Properties.Name | Should -Contain 'FixPermissionIssues'
                $result.FixPermissionIssues | Should -Not -BeNullOrEmpty
            }

            It "Should report 'Action needed' with missing permissions in status" {
                $result = Get-CyberAssessmentAppPermission -AppID $script:MockScenario.servicePrincipal.appId -M365Environment 'commercial' -ProductNames $script:MockScenario.productNames

                $result.Status | Should -Match $script:MockScenario.expectedResult.statusPattern
            }

            It "Should detect missing permissions" {
                $result = Get-CyberAssessmentAppPermission -AppID $script:MockScenario.servicePrincipal.appId -M365Environment 'commercial' -ProductNames $script:MockScenario.productNames

                $result.MissingPermissions | Should -Not -Be $false
                @($result.MissingPermissions).Count | Should -BeGreaterThan 0
            }
        }

        Context "When testing with scenario: ExtraPermissions" {
            BeforeEach {
                $script:MockScenario = Get-Content (Join-Path -Path $PSScriptRoot -ChildPath "ServicePrincipalSnippets/MockExtraPermissions.json") | ConvertFrom-Json
            }

            It "Should have FixPermissionIssues property when extra permissions exist" {
                $result = Get-CyberAssessmentAppPermission -AppID $script:MockScenario.servicePrincipal.appId -M365Environment 'commercial' -ProductNames $script:MockScenario.productNames

                $result.PSObject.Properties.Name | Should -Contain 'FixPermissionIssues'
                $result.FixPermissionIssues | Should -Not -BeNullOrEmpty
            }

            It "Should report 'Action needed' with extra permissions in status" {
                $result = Get-CyberAssessmentAppPermission -AppID $script:MockScenario.servicePrincipal.appId -M365Environment 'commercial' -ProductNames $script:MockScenario.productNames

                $result.Status | Should -Match $script:MockScenario.expectedResult.statusPattern
            }

            It "Should detect extra permissions" {
                $result = Get-CyberAssessmentAppPermission -AppID $script:MockScenario.servicePrincipal.appId -M365Environment 'commercial' -ProductNames $script:MockScenario.productNames

                $result.ExtraPermissions | Should -Not -Be $false
                @($result.ExtraPermissions).Count | Should -BeGreaterThan 0
            }
        }

        Context "When testing with scenario: MissingRole" {
            BeforeEach {
                $script:MockScenario = Get-Content (Join-Path -Path $PSScriptRoot -ChildPath "ServicePrincipalSnippets/MockMissingRole.json") | ConvertFrom-Json
            }

            It "Should have FixPermissionIssues property when role is missing" {
                $result = Get-CyberAssessmentAppPermission -AppID $script:MockScenario.servicePrincipal.appId -M365Environment 'commercial' -ProductNames $script:MockScenario.productNames

                $result.PSObject.Properties.Name | Should -Contain 'FixPermissionIssues'
                $result.FixPermissionIssues | Should -Not -BeNullOrEmpty
            }

            It "Should report 'Action needed' with missing role in status" {
                $result = Get-CyberAssessmentAppPermission -AppID $script:MockScenario.servicePrincipal.appId -M365Environment 'commercial' -ProductNames $script:MockScenario.productNames

                $result.Status | Should -Match $script:MockScenario.expectedResult.statusPattern
            }

            It "Should detect missing role" {
                $result = Get-CyberAssessmentAppPermission -AppID $script:MockScenario.servicePrincipal.appId -M365Environment 'commercial' -ProductNames $script:MockScenario.productNames

                $result.MissingRoles | Should -Not -Be $false
            }
        }

        Context "When testing with scenario: PowerPlatformUnneeded" {
            BeforeEach {
                $script:MockScenario = Get-Content (Join-Path -Path $PSScriptRoot -ChildPath "ServicePrincipalSnippets/MockPowerPlatformUnneeded.json") | ConvertFrom-Json
            }

            It "Should have FixPermissionIssues property when Power Platform is registered but not needed" {
                $result = Get-CyberAssessmentAppPermission -AppID $script:MockScenario.servicePrincipal.appId -M365Environment 'commercial' -ProductNames $script:MockScenario.productNames

                $result.PSObject.Properties.Name | Should -Contain 'FixPermissionIssues'
                $result.FixPermissionIssues | Should -Not -BeNullOrEmpty
            }

            It "Should report 'Action needed' with Power Platform status in message" {
                $result = Get-CyberAssessmentAppPermission -AppID $script:MockScenario.servicePrincipal.appId -M365Environment 'commercial' -ProductNames $script:MockScenario.productNames

                $result.Status | Should -Match $script:MockScenario.expectedResult.statusPattern
            }

            It "Should report Power Platform as registered" {
                $result = Get-CyberAssessmentAppPermission -AppID $script:MockScenario.servicePrincipal.appId -M365Environment 'commercial' -ProductNames $script:MockScenario.productNames

                $result.PowerPlatformRegistered | Should -Be $true
            }
        }

        Context "When testing with scenario: PowerPlatformNeeded" {
            BeforeEach {
                $script:MockScenario = Get-Content (Join-Path -Path $PSScriptRoot -ChildPath "ServicePrincipalSnippets/MockPowerPlatformNeeded.json") | ConvertFrom-Json
            }

            It "Should have FixPermissionIssues property when Power Platform is needed but not registered" {
                $result = Get-CyberAssessmentAppPermission -AppID $script:MockScenario.servicePrincipal.appId -M365Environment 'commercial' -ProductNames $script:MockScenario.productNames

                $result.PSObject.Properties.Name | Should -Contain 'FixPermissionIssues'
                $result.FixPermissionIssues | Should -Not -BeNullOrEmpty
            }

            It "Should report 'Action needed' with Power Platform not registered status" {
                $result = Get-CyberAssessmentAppPermission -AppID $script:MockScenario.servicePrincipal.appId -M365Environment 'commercial' -ProductNames $script:MockScenario.productNames

                $result.Status | Should -Match $script:MockScenario.expectedResult.statusPattern
            }

            It "Should report Power Platform as not registered" {
                $result = Get-CyberAssessmentAppPermission -AppID $script:MockScenario.servicePrincipal.appId -M365Environment 'commercial' -ProductNames $script:MockScenario.productNames

                $result.PowerPlatformRegistered | Should -Be $false
            }
        }
    }
}