Tests/LMPaginationHelper.Tests.ps1

Describe 'Pagination Helper Tests' {
    BeforeAll {
        . "$PSScriptRoot/../Private/Test-LMResponseHasPagination.ps1"
        . "$PSScriptRoot/../Private/Invoke-LMPaginatedGet.ps1"
        . "$PSScriptRoot/../Private/Invoke-LMPaginatedPostV4.ps1"
        . "$PSScriptRoot/../Private/Invoke-LMCursorPagedGet.ps1"
    }

    Describe 'Test-LMResponseHasPagination' {
        It 'Returns false for null response' {
            Test-LMResponseHasPagination -Response $null | Should -BeFalse
        }

        It 'Returns false when total property is missing' {
            $response = [PSCustomObject]@{ items = @() }
            Test-LMResponseHasPagination -Response $response | Should -BeFalse
        }

        It 'Returns true when total property exists' {
            $response = [PSCustomObject]@{
                total = 1
                items = @(
                    [PSCustomObject]@{ id = 1 }
                )
            }
            Test-LMResponseHasPagination -Response $response | Should -BeTrue
        }

        It 'Returns false when total value is not numeric' {
            $response = [PSCustomObject]@{
                total = 'abc'
                items = @()
            }
            Test-LMResponseHasPagination -Response $response | Should -BeFalse
        }
    }

    Describe 'Invoke-LMPaginatedGet' {
        It 'Returns null when request returns null response' {
            $result = Invoke-LMPaginatedGet -InvokeRequest {
                param($Offset, $PageSize)
                return $null
            }

            $result | Should -BeNullOrEmpty
        }

        It 'Returns single object for non-paged response when configured' {
            $result = Invoke-LMPaginatedGet -SingleObjectWhenNotPaged -InvokeRequest {
                param($Offset, $PageSize)
                return [PSCustomObject]@{ id = 10; name = 'single' }
            }

            $result.id | Should -Be 10
            $result.name | Should -Be 'single'
        }

        It 'Aggregates paged items across offsets' {
            $result = Invoke-LMPaginatedGet -BatchSize 2 -InvokeRequest {
                param($Offset, $PageSize)

                switch ($Offset) {
                    0 {
                        return [PSCustomObject]@{
                            total = 3
                            items = @(
                                [PSCustomObject]@{ id = 1 },
                                [PSCustomObject]@{ id = 2 }
                            )
                        }
                    }
                    default {
                        return [PSCustomObject]@{
                            total = 3
                            items = @(
                                [PSCustomObject]@{ id = 3 }
                            )
                        }
                    }
                }
            }

            ($result | Measure-Object).Count | Should -Be 3
            ($result.id -join ',') | Should -Be '1,2,3'
        }

        It 'Returns empty array for paged response with no items when configured' {
            $result = Invoke-LMPaginatedGet -NormalizeEmptyToArray -InvokeRequest {
                param($Offset, $PageSize)
                return [PSCustomObject]@{
                    total = 0
                    items = @()
                }
            }

            @($result).Count | Should -Be 0
        }

        It 'Returns collected results when a paged endpoint stalls with empty page' {
            $result = Invoke-LMPaginatedGet -BatchSize 2 -InvokeRequest {
                param($Offset, $PageSize)

                if ($Offset -eq 0) {
                    return [PSCustomObject]@{
                        total = 5
                        items = @(
                            [PSCustomObject]@{ id = 1 },
                            [PSCustomObject]@{ id = 2 }
                        )
                    }
                }

                return [PSCustomObject]@{
                    total = 5
                    items = @()
                }
            }

            ($result | Measure-Object).Count | Should -Be 2
            ($result.id -join ',') | Should -Be '1,2'
        }

        It 'Supports response extraction for nested response shapes' {
            $result = Invoke-LMPaginatedGet -SingleObjectWhenNotPaged -ExtractResponse {
                param($RawResponse)
                return $RawResponse.inner
            } -InvokeRequest {
                param($Offset, $PageSize)
                return [PSCustomObject]@{
                    inner = [PSCustomObject]@{
                        id = 42
                        name = 'nested'
                    }
                }
            }

            $result.id | Should -Be 42
            $result.name | Should -Be 'nested'
        }

        It 'Caps results with MaxItems and emits warning' {
            $WarningPreference = 'Continue'
            $result = Invoke-LMPaginatedGet -BatchSize 2 -MaxItems 3 -MaxItemsWarningMessage 'max hit' -InvokeRequest {
                param($Offset, $PageSize)

                switch ($Offset) {
                    0 {
                        return [PSCustomObject]@{
                            total = 4
                            items = @(
                                [PSCustomObject]@{ id = 1 },
                                [PSCustomObject]@{ id = 2 }
                            )
                        }
                    }
                    default {
                        return [PSCustomObject]@{
                            total = 4
                            items = @(
                                [PSCustomObject]@{ id = 3 },
                                [PSCustomObject]@{ id = 4 }
                            )
                        }
                    }
                }
            } 3>&1

            ($result | Where-Object { $_ -isnot [System.Management.Automation.WarningRecord] }).Count | Should -Be 3
            ($result | Where-Object { $_ -is [System.Management.Automation.WarningRecord] }).Count | Should -Be 1
        }

        It 'Stops when MaxItems is reached exactly' {
            $WarningPreference = 'Continue'
            $script:requestCount = 0
            $result = Invoke-LMPaginatedGet -BatchSize 2 -MaxItems 4 -MaxItemsWarningMessage 'max hit' -InvokeRequest {
                param($Offset, $PageSize)

                $script:requestCount++
                switch ($Offset) {
                    0 {
                        return [PSCustomObject]@{
                            total = 6
                            items = @(
                                [PSCustomObject]@{ id = 1 },
                                [PSCustomObject]@{ id = 2 }
                            )
                        }
                    }
                    default {
                        return [PSCustomObject]@{
                            total = 6
                            items = @(
                                [PSCustomObject]@{ id = 3 },
                                [PSCustomObject]@{ id = 4 }
                            )
                        }
                    }
                }
            } 3>&1

            ($result | Where-Object { $_ -isnot [System.Management.Automation.WarningRecord] }).Count | Should -Be 4
            ($result | Where-Object { $_ -is [System.Management.Automation.WarningRecord] }).Count | Should -Be 1
            $script:requestCount | Should -Be 2
        }
    }

    Describe 'Invoke-LMPaginatedPostV4' {
        It 'Aggregates POST pages using body offset increments' {
            $result = Invoke-LMPaginatedPostV4 -BatchSize 2 -InvokeRequest {
                param($Offset, $PageSize)

                switch ($Offset) {
                    0 { return [PSCustomObject]@{ data = [PSCustomObject]@{ items = @(1, 2) } } }
                    2 { return [PSCustomObject]@{ data = [PSCustomObject]@{ items = @(3) } } }
                    default { return [PSCustomObject]@{ data = [PSCustomObject]@{ items = @() } } }
                }
            } -ExtractItems {
                param($Response)
                return @($Response.data.items)
            }

            ($result | Measure-Object).Count | Should -Be 3
            ($result -join ',') | Should -Be '1,2,3'
        }
    }

    Describe 'Invoke-LMCursorPagedGet' {
        It 'Collects pages from cursor tokens until complete' {
            $result = Invoke-LMCursorPagedGet -InvokeRequest {
                param($Cursor, $PageIndex, $PreviousResponse)

                if (!$Cursor) {
                    return [PSCustomObject]@{ items = @(1); nextPageParams = 'cursor=abc' }
                }

                return [PSCustomObject]@{ items = @(2); nextPageParams = $null }
            } -ExtractItems {
                param($Response)
                return @($Response.items)
            } -GetNextCursor {
                param($Response)
                return $Response.nextPageParams
            } -IsComplete {
                param($Response, $PageIndex, $Cursor)
                return [string]::IsNullOrWhiteSpace([string]$Response.nextPageParams)
            }

            ($result | Measure-Object).Count | Should -Be 2
            ($result -join ',') | Should -Be '1,2'
        }

        It 'Stops when MaxPages is reached' {
            $result = Invoke-LMCursorPagedGet -MaxPages 1 -MaxPagesWarningMessage 'max pages' -InvokeRequest {
                param($Cursor, $PageIndex, $PreviousResponse)
                return [PSCustomObject]@{ items = @($PageIndex + 1); nextPageParams = "cursor=$PageIndex" }
            } -ExtractItems {
                param($Response)
                return @($Response.items)
            } -GetNextCursor {
                param($Response)
                return $Response.nextPageParams
            } -IsComplete {
                param($Response, $PageIndex, $Cursor)
                return $false
            } 6>&1

            ($result | Where-Object { $_ -isnot [System.Management.Automation.InformationRecord] }).Count | Should -Be 1
            ($result | Where-Object { $_ -is [System.Management.Automation.InformationRecord] }).Count | Should -Be 1
        }
    }
}