Private/Document/Tests/Get-OpenApiPaging.Tests.ps1

BeforeAll {
    Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath '../../../tcs.openapi.psd1') -Force
}

Describe 'Get-OpenApiPaging' {
    BeforeAll {
        $script:paging = InModuleScope tcs.openapi {
            {
                param([string]$Operation)
                $context = Get-OpenApiNormalizationContext -Root $null
                $raw = ConvertFrom-OpenApiJson -Text $Operation
                $responses = ConvertTo-OpenApiResponseList -Context $context -Responses $raw['responses'] -Pointer ''
                Get-OpenApiPaging -Operation $raw -Responses $responses
            }
        }
    }

    It 'uses x-ms-pageable' {
        InModuleScope tcs.openapi -Parameters @{ Paging = $script:paging } {
            param($Paging)
            $result = & $Paging '{"x-ms-pageable":{"nextLinkName":"nextLink"},"responses":{}}'
            $result.Kind | Should -Be 'nextLink'
            $result.ItemsProperty | Should -Be 'value'
            $result.NextLinkProperty | Should -Be 'nextLink'
            (& $Paging '{"x-ms-pageable":{"nextLinkName":"@odata.nextLink","itemName":"items"}}').ItemsProperty | Should -Be 'items'
        }
    }

    It 'treats x-ms-pageable with a null nextLinkName as one page' {
        InModuleScope tcs.openapi -Parameters @{ Paging = $script:paging } {
            param($Paging)
            & $Paging '{"x-ms-pageable":{"nextLinkName":null},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"properties":{"value":{"type":"array"},"nextLink":{"type":"string"}}}}}}}}' | Should -BeNullOrEmpty
        }
    }

    It 'detects one array property plus <Name>' -TestCases @(
        @{ Name = 'nextLink' }
        @{ Name = 'next' }
        @{ Name = '@odata.nextLink' }
        @{ Name = 'NextLink' }
    ) {
        InModuleScope tcs.openapi -Parameters @{ Paging = $script:paging; Name = $Name } {
            param($Paging, $Name)
            $result = & $Paging ('{"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{}},"total":{"type":"integer"},"' + $Name + '":{"type":"string"}}}}}}}}')
            $result.Kind | Should -Be 'nextLink'
            $result.ItemsProperty | Should -Be 'data'
            $result.NextLinkProperty | Should -BeExactly $Name
        }
    }

    It 'does not detect paging with two array properties or without a next link' {
        InModuleScope tcs.openapi -Parameters @{ Paging = $script:paging } {
            param($Paging)
            & $Paging '{"responses":{"200":{"description":"","content":{"application/json":{"schema":{"properties":{"a":{"type":"array"},"b":{"type":"array"},"nextLink":{"type":"string"}}}}}}}}' | Should -BeNullOrEmpty
            & $Paging '{"responses":{"200":{"description":"","content":{"application/json":{"schema":{"properties":{"a":{"type":"array"}}}}}}}}' | Should -BeNullOrEmpty
            & $Paging '{"responses":{"200":{"description":"","content":{"application/json":{"schema":{"properties":{"a":{"type":"array"},"nextLink":{"type":"integer"}}}}}}}}' | Should -BeNullOrEmpty
        }
    }

    It 'detects a Link header' {
        InModuleScope tcs.openapi -Parameters @{ Paging = $script:paging } {
            param($Paging)
            $array = & $Paging '{"responses":{"200":{"description":"","headers":{"link":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"type":"array","items":{}}}}}}}'
            $array.Kind | Should -Be 'linkHeader'
            $array.ItemsProperty | Should -BeNullOrEmpty
            $array.NextLinkProperty | Should -BeNullOrEmpty
            $object = & $Paging '{"responses":{"200":{"description":"","headers":{"Link":{}},"content":{"application/json":{"schema":{"properties":{"items":{"type":"array"}}}}}}}}'
            $object.ItemsProperty | Should -Be 'items'
        }
    }

    It 'returns nothing for an operation without paging' {
        InModuleScope tcs.openapi -Parameters @{ Paging = $script:paging } {
            param($Paging)
            & $Paging '{"responses":{"204":{"description":""}}}' | Should -BeNullOrEmpty
        }
    }

    Context 'token paging' {
        BeforeAll {
            $script:tokenPaging = InModuleScope tcs.openapi {
                {
                    param([string]$Operation)
                    $context = Get-OpenApiNormalizationContext -Root $null
                    $raw = ConvertFrom-OpenApiJson -Text $Operation
                    $responses = ConvertTo-OpenApiResponseList -Context $context -Responses $raw['responses'] -Pointer ''
                    $parameters = @(foreach ($node in @($raw['parameters'])) {
                            if ($null -ne $node) {
                                ConvertTo-OpenApiParameter -Context $context -Node $node -Pointer ''
                            }
                        })
                    Get-OpenApiPaging -Operation $raw -Responses $responses -Parameters $parameters
                }
            }
            $script:tokenJson = {
                param([string]$Parameter, [string]$Property, [string]$Extra = '')
                '{"parameters":[{"name":"pageSize","in":"query"},{"name":"' + $Parameter + '","in":"query","schema":{"type":"string"}}],' +
                '"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{}},"traceId":{"type":"string"},"' + $Property + '":{"type":"string"}' + $Extra + '}}}}}}}'
            }
        }

        It 'detects query <Parameter> with response property <Property>' -TestCases @(
            @{ Parameter = 'nextToken'; Property = 'nextToken' }
            @{ Parameter = 'pageToken'; Property = 'nextPageToken' }
            @{ Parameter = 'next_token'; Property = 'next_token' }
            @{ Parameter = 'page_token'; Property = 'next_page_token' }
            @{ Parameter = 'cursor'; Property = 'next_cursor' }
            @{ Parameter = 'Cursor'; Property = 'nextCursor' }
            @{ Parameter = 'continuationToken'; Property = 'continuationToken' }
            @{ Parameter = 'continuation_token'; Property = 'continuation_token' }
            @{ Parameter = 'NEXTTOKEN'; Property = 'nextToken' }
        ) {
            InModuleScope tcs.openapi -Parameters @{ Paging = $script:tokenPaging; Json = (& $script:tokenJson $Parameter $Property) ; Parameter = $Parameter; Property = $Property } {
                param($Paging, $Json, $Parameter, $Property)
                $result = & $Paging $Json
                $result.PSObject.TypeNames | Should -Contain 'Tcs.OpenApi.Paging'
                $result.Kind | Should -Be 'token'
                $result.ItemsProperty | Should -Be 'data'
                $result.TokenParameter | Should -BeExactly $Parameter
                $result.TokenProperty | Should -BeExactly $Property
            }
        }

        It 'prefers the property named like the parameter' {
            InModuleScope tcs.openapi -Parameters @{ Paging = $script:tokenPaging; Json = (& $script:tokenJson 'cursor' 'nextToken' ',"cursor":{"type":"string"}') } {
                param($Paging, $Json)
                (& $Paging $Json).TokenProperty | Should -BeExactly 'cursor'
            }
        }

        It 'needs the query parameter, one array property and a string token property' {
            InModuleScope tcs.openapi -Parameters @{ Paging = $script:tokenPaging; Token = $script:tokenJson } {
                param($Paging, $Token)
                # No token query parameter
                & $Paging '{"responses":{"200":{"description":"","content":{"application/json":{"schema":{"properties":{"data":{"type":"array"},"nextToken":{"type":"string"}}}}}}}}' | Should -BeNullOrEmpty
                # The token parameter is a header
                & $Paging '{"parameters":[{"name":"nextToken","in":"header"}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"properties":{"data":{"type":"array"},"nextToken":{"type":"string"}}}}}}}}' | Should -BeNullOrEmpty
                # Two arrays, no token property, a numeric token
                & $Paging (& $Token 'nextToken' 'nextToken' ',"more":{"type":"array"}') | Should -BeNullOrEmpty
                & $Paging (& $Token 'nextToken' 'other') | Should -BeNullOrEmpty
                & $Paging ((& $Token 'nextToken' 'nextToken').Replace('"nextToken":{"type":"string"}', '"nextToken":{"type":"integer"}')) | Should -BeNullOrEmpty
            }
        }

        It 'keeps nextLink detection and x-ms-pageable ahead of token paging' {
            InModuleScope tcs.openapi -Parameters @{ Paging = $script:tokenPaging; Token = $script:tokenJson } {
                param($Paging, $Token)
                (& $Paging (& $Token 'nextToken' 'nextToken' ',"nextLink":{"type":"string"}')).Kind | Should -Be 'nextLink'
                $pageable = (& $Token 'nextToken' 'nextToken').Replace('{"parameters"', '{"x-ms-pageable":{"nextLinkName":"next","itemName":"data"},"parameters"')
                $result = & $Paging $pageable
                $result.Kind | Should -Be 'nextLink'
                $result.NextLinkProperty | Should -Be 'next'
            }
        }
    }
}