Tests/EAI/EAIRecordsQueryPayload.Tests.ps1

BeforeAll {
    if ($Module) {
        $script:DevModuleName = Import-Module $Module -Force -PassThru | Select-Object -ExpandProperty Name
    }
    else {
        $devModule = Join-Path $PSScriptRoot '..' '..' 'Dev.Logic.Monitor.psd1'
        $script:DevModuleName = Import-Module $devModule -Force -PassThru | Select-Object -ExpandProperty Name
    }
}

Describe 'Build-EAIRecordsQueryPayload' {
    It 'Builds a payload matching the Dexda-UIQuery record fixture shape' {
        InModuleScope -ModuleName $script:DevModuleName {
            $filter = [PSCustomObject]@{
                schemaName    = 'filterCondition'
                schemaVersion = 4
                expression    = @{
                    WITHIN = @(
                        @{ field = 'cf.eventTime'; type = 'long' }
                        @{ unit = 'hour'; duration = 1 }
                    )
                }
            }

            $result = Build-EAIRecordsQueryPayload `
                -RecordType events `
                -Field '_id', 'cf.eventName', 'cf.eventSource' `
                -Filter $filter `
                -Timezone 'Europe/London' `
                -Order @{ field = '_id'; type = 'asc' } `
                -Size 1

            $result.recordType | Should -Be 'events'
            $result.env.timezone | Should -Be 'Europe/London'
            @($result.fields) | Should -Be @('_id', 'cf.eventName', 'cf.eventSource')
            $result.filter.schemaName | Should -Be 'filterCondition'
            $result.filter.schemaVersion | Should -Be '4'
            $result.order[0].field | Should -Be '_id'
            $result.order[0].type | Should -Be 'asc'
            $result.size | Should -Be 1
        }
    }

    It 'Uses default order when Order is omitted' {
        InModuleScope -ModuleName $script:DevModuleName {
            $filter = New-EAISdtFilterObject -Expression @{
                AND = @()
            }

            $result = Build-EAIRecordsQueryPayload `
                -RecordType events `
                -Field '_id' `
                -Filter $filter

            $result.order.Count | Should -Be 1
            $result.order[0].field | Should -Be 'meta.eventTimestamp'
            $result.order[0].type | Should -Be 'desc'
            $result.PSObject.Properties.Name | Should -Not -Contain 'size'
        }
    }

    It 'Builds order from OrderField and OrderDirection' {
        InModuleScope -ModuleName $script:DevModuleName {
            $filter = New-EAISdtFilterObject -Expression @{ AND = @() }

            $result = Build-EAIRecordsQueryPayload `
                -RecordType events `
                -Field '_id' `
                -Filter $filter `
                -OrderField 'cf.eventName' `
                -OrderDirection asc

            $result.order[0].field | Should -Be 'cf.eventName'
            $result.order[0].type | Should -Be 'asc'
        }
    }

    It 'Defaults OrderDirection to desc when only OrderField is specified' {
        InModuleScope -ModuleName $script:DevModuleName {
            $filter = New-EAISdtFilterObject -Expression @{ AND = @() }

            $result = Build-EAIRecordsQueryPayload `
                -RecordType events `
                -Field '_id' `
                -Filter $filter `
                -OrderField 'cf.eventName'

            $result.order[0].field | Should -Be 'cf.eventName'
            $result.order[0].type | Should -Be 'desc'
        }
    }

    It 'Uses default order field when only OrderDirection is specified' {
        InModuleScope -ModuleName $script:DevModuleName {
            $filter = New-EAISdtFilterObject -Expression @{ AND = @() }

            $result = Build-EAIRecordsQueryPayload `
                -RecordType events `
                -Field '_id' `
                -Filter $filter `
                -OrderDirection asc

            $result.order[0].field | Should -Be 'meta.eventTimestamp'
            $result.order[0].type | Should -Be 'asc'
        }
    }

    It 'Uses meta.firstEventTimestamp for alerts default order' {
        InModuleScope -ModuleName $script:DevModuleName {
            $result = Build-EAIRecordsQueryPayload -RecordType alerts -Field '_id'

            $result.order[0].field | Should -Be 'meta.firstEventTimestamp'
            $result.order[0].type | Should -Be 'desc'
        }
    }

    It 'Rejects combining Order with OrderField' {
        InModuleScope -ModuleName $script:DevModuleName {
            $filter = New-EAISdtFilterObject -Expression @{ AND = @() }

            {
                Build-EAIRecordsQueryPayload `
                    -RecordType events `
                    -Field '_id' `
                    -Filter $filter `
                    -Order @{ field = '_id'; type = 'asc' } `
                    -OrderField 'cf.eventName'
            } | Should -Throw '*Cannot specify -Order together with -OrderField or -OrderDirection*'
        }
    }

    It 'Rejects size above the API maximum' {
        InModuleScope -ModuleName $script:DevModuleName {
            $filter = New-EAISdtFilterObject -Expression @{ AND = @() }

            {
                Build-EAIRecordsQueryPayload -RecordType events -Field '_id' -Filter $filter -Size 201
            } | Should -Throw '*cannot exceed 200*'
        }
    }

    It 'Uses a projected typename when fields are specified' {
        InModuleScope -ModuleName $script:DevModuleName {
            $filter = New-EAISdtFilterObject -Expression @{ AND = @() }
            $result = Build-EAIRecordsQueryPayload -RecordType events -Field '_id' -Filter $filter
            $typed = Add-EAIQueryRecordTypeInfo -InputObject ([PSCustomObject]@{ _id = 'abc' }) -RecordType events -Projected

            $typed.PSTypeNames | Should -Contain 'Edwin.Event.Projected'
        }
    }

    It 'Omits fields (null) when Field is not specified' {
        InModuleScope -ModuleName $script:DevModuleName {
            $result = Build-EAIRecordsQueryPayload -RecordType events

            $result.PSObject.Properties.Name | Should -Contain 'fields'
            $result.fields | Should -BeNullOrEmpty
            $json = ConvertTo-EAIQueryRequestJson -InputObject $result
            $json | Should -Match '"fields"\s*:\s*null'
        }
    }

    It 'Applies the events UI default filter when Filter is omitted' {
        InModuleScope -ModuleName $script:DevModuleName {
            $result = Build-EAIRecordsQueryPayload -RecordType events -Field '_id'

            $result.filter.expression.AND[0].OR[0].AND[0].WITHIN[0].field | Should -Be 'meta.eventTimestamp'
            $result.filter.expression.AND[0].OR[0].AND[0].WITHIN[1].duration | Should -Be 24
            $result.filter.expression.AND[1].OR[2].EMPTY[0].field | Should -Be 'meta.insightType'
        }
    }

    It 'Applies the alerts UI default filter when Filter is omitted' {
        InModuleScope -ModuleName $script:DevModuleName {
            $result = Build-EAIRecordsQueryPayload -RecordType alerts -Field '_id'

            $result.filter.expression.AND[0].OR[0].AND[0].WITHIN[0].field | Should -Be 'meta.firstEventTimestamp'
        }
    }

    It 'Uses an explicit filter when supplied' {
        InModuleScope -ModuleName $script:DevModuleName {
            $custom = New-EAISdtFilterObject -Expression @{
                EQUALS = @(
                    (New-EAISdtFilterFieldReference -Field 'cf.eventSource' -Type 'string')
                    'LogicMonitor'
                )
            }

            $result = Build-EAIRecordsQueryPayload -RecordType events -Field '_id' -Filter $custom

            $result.filter.expression.EQUALS[1] | Should -Be 'LogicMonitor'
        }
    }

    It 'Serializes order as a JSON array' {
        InModuleScope -ModuleName $script:DevModuleName {
            $filter = New-EAISdtFilterObject -Expression @{ AND = @() }
            $payload = Build-EAIRecordsQueryPayload -RecordType events -Field '_id' -Filter $filter
            $json = ConvertTo-EAIQueryRequestJson -InputObject $payload

            $json | Should -Match '"order"\s*:\s*\['
        }
    }
}

Describe 'Format-EAIQueryRecordPath' {
    It 'URL-encodes record IDs that contain slashes' {
        InModuleScope -ModuleName $script:DevModuleName {
            $path = Format-EAIQueryRecordPath -RecordType events -RecordId 'eventId/12345abc'
            $path | Should -Be '/ui/query/record/events/eventId%2F12345abc'
        }
    }
}