Public/Tests/Remove-IntuneAppGroup.Tests.ps1

BeforeAll {
    $env:TCS_CONFIG_ROOT = Join-Path -Path $TestDrive -ChildPath 'config'
    $env:TCS_SKIP_UPDATE_CHECK = '1'
    $env:TCS_TELEMETRY_OPTOUT = '1'
    $ModuleRoot = Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent
    $RepoRoot = Split-Path -Path (Split-Path -Path $ModuleRoot -Parent) -Parent
    Import-Module -Name (Join-Path -Path $ModuleRoot -ChildPath 'tcs.azure.psd1') -Force
    . (Join-Path -Path $RepoRoot -ChildPath 'tests/Helpers/EntraStubs.ps1')
    $script:CreatedStubs = Add-TcsEntraStub
}

AfterAll {
    Remove-TcsEntraStub -Name $script:CreatedStubs
    Remove-Module -Name tcs.azure -Force -ErrorAction SilentlyContinue
}

Describe 'Remove-IntuneAppGroup' {
    BeforeEach {
        Mock -ModuleName tcs.azure Get-EntraContext { [pscustomobject]@{ Account = 'admin@contoso.com' } }
        Mock -ModuleName tcs.azure Get-EntraGroup { } -ParameterFilter { $Filter -like "*'Intune-ACG-*" }
        Mock -ModuleName tcs.azure Get-EntraGroup {
            @(
                [pscustomobject]@{ Id = '1'; DisplayName = 'Intune-AG-App1-Available' }
                [pscustomobject]@{ Id = '2'; DisplayName = 'Intune-AG-App1-Required' }
                [pscustomobject]@{ Id = '3'; DisplayName = 'Intune-AG-App1-Uninstall' }
            )
        } -ParameterFilter { $Filter -like "*'Intune-AG-*" }
        Mock -ModuleName tcs.azure Get-EntraGroup { [pscustomobject]@{ Id = $GroupId; DisplayName = "Intune-AG-ById-$GroupId" } } -ParameterFilter { $GroupId }
        Mock -ModuleName tcs.azure Remove-EntraGroup { }
    }

    It 'Has ConfirmImpact High' {
        $binding = (Get-Command -Name Remove-IntuneAppGroup).ScriptBlock.Attributes | Where-Object { $_ -is [System.Management.Automation.CmdletBindingAttribute] }
        $binding.ConfirmImpact | Should -Be 'High'
        $binding.SupportsShouldProcess | Should -BeTrue
    }

    It 'Removes every group of an application' {
        Remove-IntuneAppGroup -Name 'App1' -Confirm:$false
        Should -Invoke -ModuleName tcs.azure Remove-EntraGroup -Times 3 -Exactly
        Should -Invoke -ModuleName tcs.azure Remove-EntraGroup -ParameterFilter { $GroupId -eq '1' } -Times 1 -Exactly
    }

    It 'Removes only the requested intents' {
        Remove-IntuneAppGroup -Name 'App1' -Intent Uninstall -Confirm:$false
        Should -Invoke -ModuleName tcs.azure Remove-EntraGroup -Times 1 -Exactly
        Should -Invoke -ModuleName tcs.azure Remove-EntraGroup -ParameterFilter { $GroupId -eq '3' } -Times 1 -Exactly
    }

    It 'Removes nothing with -WhatIf' {
        Remove-IntuneAppGroup -Name 'App1' -WhatIf
        Should -Invoke -ModuleName tcs.azure Remove-EntraGroup -Times 0 -Exactly
    }

    It 'Writes nothing to the pipeline' {
        Remove-IntuneAppGroup -Name 'App1' -Confirm:$false | Should -BeNullOrEmpty
    }

    It 'Writes an error when no groups are found' {
        Mock -ModuleName tcs.azure Get-EntraGroup { } -ParameterFilter { $Filter }
        Remove-IntuneAppGroup -Name 'Missing' -Confirm:$false -ErrorVariable errors -ErrorAction SilentlyContinue
        $errors[0].FullyQualifiedErrorId | Should -BeLike 'IntuneAppGroupNotFound*'
        Should -Invoke -ModuleName tcs.azure Remove-EntraGroup -Times 0 -Exactly
    }

    It 'Removes groups by Id' {
        Remove-IntuneAppGroup -Id 'abc', 'def' -Confirm:$false
        Should -Invoke -ModuleName tcs.azure Remove-EntraGroup -ParameterFilter { $GroupId -eq 'abc' } -Times 1 -Exactly
        Should -Invoke -ModuleName tcs.azure Remove-EntraGroup -ParameterFilter { $GroupId -eq 'def' } -Times 1 -Exactly
    }

    It 'Accepts the output of Get-IntuneAppGroup from the pipeline' {
        Get-IntuneAppGroup -Name 'App1' | Remove-IntuneAppGroup -Confirm:$false
        Should -Invoke -ModuleName tcs.azure Remove-EntraGroup -Times 3 -Exactly
        Should -Invoke -ModuleName tcs.azure Get-EntraGroup -ParameterFilter { $GroupId -eq '2' } -Times 1 -Exactly
    }

    It 'Accepts application names from the pipeline' {
        'App1' | Remove-IntuneAppGroup -Intent Available -Confirm:$false
        Should -Invoke -ModuleName tcs.azure Remove-EntraGroup -ParameterFilter { $GroupId -eq '1' } -Times 1 -Exactly
    }

    It 'Refuses to remove a group that is not an Intune app group' {
        Mock -ModuleName tcs.azure Get-EntraGroup { [pscustomobject]@{ Id = $GroupId; DisplayName = 'Domain Admins' } } -ParameterFilter { $GroupId }
        Remove-IntuneAppGroup -Id 'xyz' -Confirm:$false -ErrorVariable errors -ErrorAction SilentlyContinue
        $errors[0].FullyQualifiedErrorId | Should -BeLike 'NotAnIntuneAppGroup*'
        Should -Invoke -ModuleName tcs.azure Remove-EntraGroup -Times 0 -Exactly
    }

    It 'Continues and records failed telemetry when a group cannot be removed' {
        Mock -ModuleName tcs.azure Invoke-TelemetryCollection { }
        Mock -ModuleName tcs.azure Remove-EntraGroup { throw 'Insufficient privileges' } -ParameterFilter { $GroupId -eq '1' }
        Remove-IntuneAppGroup -Name 'App1' -Confirm:$false -ErrorVariable errors -ErrorAction SilentlyContinue
        ($errors | Out-String) | Should -Match 'Insufficient privileges'
        Should -Invoke -ModuleName tcs.azure Remove-EntraGroup -Times 3 -Exactly
        Should -Invoke -ModuleName tcs.azure Invoke-TelemetryCollection -ParameterFilter { $Stage -eq 'End' -and $Failed } -Times 1 -Exactly
    }

    It 'Throws a helpful error when the Entra commands are missing' {
        Mock -ModuleName tcs.azure Get-Command { } -ParameterFilter { $Name -eq 'Remove-EntraGroup' }
        { Remove-IntuneAppGroup -Name 'App1' -Confirm:$false } | Should -Throw '*Remove-EntraGroup*Install-Module*'
    }

    It 'Records start and end telemetry' {
        Mock -ModuleName tcs.azure Invoke-TelemetryCollection { }
        Remove-IntuneAppGroup -Name 'App1' -Confirm:$false
        Should -Invoke -ModuleName tcs.azure Invoke-TelemetryCollection -ParameterFilter { $Stage -eq 'Start' } -Times 1 -Exactly
        Should -Invoke -ModuleName tcs.azure Invoke-TelemetryCollection -ParameterFilter { $Stage -eq 'End' -and -not $Failed } -Times 1 -Exactly
    }
}