Tests/GraphShell.Tests.ps1

#Requires -Modules Pester

<#
    Pester tests for the GraphShell PowerShell module.
    Run locally with:
        Invoke-Pester -Path .\powershell\GraphShell\Tests\GraphShell.Tests.ps1 -Output Detailed
#>


BeforeAll {
    $script:ModuleRoot = Split-Path -Parent $PSScriptRoot
    $script:ManifestPath = Join-Path $ModuleRoot 'GraphShell.psd1'

    Remove-Module GraphShell -ErrorAction SilentlyContinue
    Import-Module $ManifestPath -Force -ErrorAction Stop
}

AfterAll {
    Remove-Module GraphShell -ErrorAction SilentlyContinue
}

Describe 'GraphShell module packaging' {
    It 'imports without error' {
        { Import-Module $script:ManifestPath -Force } | Should -Not -Throw
    }

    It 'has a valid module manifest' {
        { Test-ModuleManifest -Path $script:ManifestPath -ErrorAction Stop } | Should -Not -Throw
    }

    It 'exports exactly Get-GraphMapping and Get-GraphDetail' {
        $exported = (Get-Command -Module GraphShell).Name | Sort-Object
        $exported | Should -Be @('Get-GraphDetail', 'Get-GraphMapping')
    }

    It 'declares the GraphShell/Microsoft.Graph distinction in the manifest description' {
        $manifest = Test-ModuleManifest -Path $script:ManifestPath
        $manifest.Description | Should -Match 'does not replace the Microsoft Graph PowerShell SDK'
    }
}

Describe 'Get-GraphMapping -Cmdlet' {
    It 'resolves a known cmdlet to its REST relationship' {
        $result = Get-GraphMapping -Cmdlet 'Get-MgUser'
        $result | Should -Not -BeNullOrEmpty
        ($result | Where-Object Endpoint -eq '/users').Method | Should -Be 'GET'
        $result[0].PSObject.TypeNames | Should -Contain 'GraphShell.Mapping'
    }

    It 'resolves a renamed cmdlet to the current cmdlet' {
        $result = Get-GraphMapping -Cmdlet 'Remove-MgApplicationOwnerByRef'
        $result | Should -Not -BeNullOrEmpty
        $result[0].Cmdlet | Should -Be 'Remove-MgApplicationOwnerDirectoryObjectByRef'
        $result[0].RenamedFrom | Should -Be 'Remove-MgApplicationOwnerByRef'
    }

    It 'returns nothing for a cmdlet outside the Microsoft Graph SDK' {
        Get-GraphMapping -Cmdlet 'Get-AzRoleAssignment' | Should -BeNullOrEmpty
    }
}

Describe 'Get-GraphMapping -Endpoint' {
    It 'resolves a known REST endpoint to its cmdlet(s)' {
        $result = Get-GraphMapping -Endpoint '/users'
        $result | Should -Not -BeNullOrEmpty
        ($result.Cmdlet -contains 'Get-MgUser') | Should -Be $true
    }
}

Describe 'Get-GraphMapping -Permission' {
    It 'uses the permission reverse index to list related operations' {
        $result = Get-GraphMapping -Permission 'User.Read.All'
        $result | Should -Not -BeNullOrEmpty
        $result[0].MatchedPermission | Should -Be 'User.Read.All'
        ($result.Cmdlet -contains 'Get-MgUser') | Should -Be $true
    }

    It 'returns nothing for an unknown permission' {
        Get-GraphMapping -Permission 'Not.A.Real.Permission' | Should -BeNullOrEmpty
    }
}

Describe 'Get-GraphMapping -Module' {
    It 'returns operations for a known module name' {
        $result = Get-GraphMapping -Module 'Microsoft.Graph.Users'
        $result | Should -Not -BeNullOrEmpty
        ($result.ModuleName -contains 'Microsoft.Graph.Users') | Should -Be $true
    }
}

Describe 'Get-GraphMapping -Query' {
    It 'finds PIM-eligible role operations from a free-text concept' {
        $result = Get-GraphMapping -Query 'PIM eligible'
        $result | Should -Not -BeNullOrEmpty
        ($result | Where-Object Cmdlet -like '*RoleEligibilitySchedule*') | Should -Not -BeNullOrEmpty
    }
}

Describe 'Get-GraphMapping output shape' {
    It 'returns real PowerShell objects usable with Select-Object/Where-Object' {
        $result = Get-GraphMapping -Permission 'User.Read.All' |
            Where-Object ApiVersion -eq 'v1.0' |
            Select-Object -First 1 Cmdlet, Module, Method, Endpoint
        $result | Should -Not -BeNullOrEmpty
        $result.PSObject.Properties.Name | Should -Contain 'Cmdlet'
    }
}

Describe 'Get-GraphDetail' {
    It 'loads only the necessary detail shard for a cmdlet' {
        $result = Get-GraphDetail -Name 'Get-MgUser'
        $result | Should -Not -BeNullOrEmpty
        ($result | Where-Object Endpoint -eq '/users').Permissions | Should -Not -BeNullOrEmpty
        $result[0].DetailShard | Should -Be 'g'
    }

    It 'skips generating a REST equivalent when the endpoint needs a path parameter' {
        $result = Get-GraphDetail -Name 'Get-MgUser' | Where-Object Endpoint -eq '/users/{user-id}'
        $result.RestEquivalent | Should -BeNullOrEmpty
        $result.RestNote | Should -Match 'path parameter'
    }

    It 'generates an Invoke-MgGraphRequest equivalent when the endpoint has no path parameters' {
        $result = Get-GraphDetail -Name 'Get-MgUser' | Where-Object Endpoint -eq '/users'
        $result.RestEquivalent | Should -Match 'Invoke-MgGraphRequest -Method GET'
    }

    It 'accepts pipeline input from Get-GraphMapping' {
        $result = Get-GraphMapping -Cmdlet 'Get-MgUser' | Select-Object -First 1 | Get-GraphDetail
        $result | Should -Not -BeNullOrEmpty
    }

    It 'warns and returns nothing for an unknown cmdlet' {
        $warnings = @()
        Get-GraphDetail -Name 'Get-MgDoesNotExist' -WarningVariable warnings -WarningAction SilentlyContinue |
            Should -BeNullOrEmpty
        $warnings.Count | Should -BeGreaterThan 0
    }
}

Describe 'GraphShell catalog cache' {
    It 'reuses the cached index across calls instead of reloading the file' {
        InModuleScope GraphShell {
            $script:GraphCatalogCache = $null
            $first = Get-GraphCatalogIndex
            $second = Get-GraphCatalogIndex
            [object]::ReferenceEquals($first, $second) | Should -Be $true
        }
    }
}