Tests/LMActionChain.Tests.ps1

Describe 'Action Chain Cmdlet Tests' {
    BeforeAll {
        . "$PSScriptRoot/../Private/Test-LMResponseHasPagination.ps1"
        . "$PSScriptRoot/../Private/Add-ObjectTypeInfo.ps1"
        . "$PSScriptRoot/../Private/Format-LMData.ps1"
        . "$PSScriptRoot/../Private/Format-LMFilter.ps1"
        . "$PSScriptRoot/../Private/Get-LMPortalURI.ps1"
        . "$PSScriptRoot/../Private/Invoke-LMPaginatedGet.ps1"
        . "$PSScriptRoot/../Private/New-LMHeader.ps1"
        . "$PSScriptRoot/../Private/Resolve-LMDebugInfo.ps1"
        . "$PSScriptRoot/../Private/Test-LookupResult.ps1"
        . "$PSScriptRoot/../Private/Invoke-LMRestMethod.ps1"
        . "$PSScriptRoot/../Public/Get-LMActionChain.ps1"
        . "$PSScriptRoot/../Public/New-LMActionChain.ps1"
        . "$PSScriptRoot/../Public/Set-LMActionChain.ps1"
        . "$PSScriptRoot/../Public/Remove-LMActionChain.ps1"
    }

    BeforeEach {
        $Script:LMAuth = @{
            Valid  = $true
            Type   = 'SessionSync'
            Portal = 'unit-test'
        }

        Mock Get-LMPortalURI { 'logicmonitor.com/santaba/rest' }
        Mock New-LMHeader { @(@{}, $null) }
        Mock Resolve-LMDebugInfo { }
        Mock Test-LookupResult { $false }
    }

    Context 'Get-LMActionChain' {
        It 'Returns a single action chain by Id' {
            Mock Invoke-LMRestMethod {
                return [PSCustomObject]@{
                    id      = 10
                    name    = 'Test Chain'
                    stages  = @()
                    PSTypeName = 'LogicMonitor.ActionChain'
                }
            }

            $result = Get-LMActionChain -Id 10
            $result.id | Should -Be 10
            $result.name | Should -Be 'Test Chain'
        }

        It 'Builds name filter query for list lookup' {
            Mock Invoke-LMRestMethod {
                param($Uri)
                $Uri | Should -Match 'filter=name:"Test Chain"'
                return [PSCustomObject]@{
                    total = 1
                    items = @(
                        [PSCustomObject]@{ id = 10; name = 'Test Chain' }
                    )
                }
            }

            $result = Get-LMActionChain -Name 'Test Chain'
            $result.id | Should -Be 10
        }
    }

    Context 'New-LMActionChain' {
        It 'Posts stages in the request body' {
            Mock Invoke-LMRestMethod {
                param($Body)
                $payload = $Body | ConvertFrom-Json
                $payload.name | Should -Be 'Disk Chain'
                $payload.stages.Count | Should -Be 2
                $payload.stages[0].type | Should -Be 'diagnosticSource'
                return [PSCustomObject]@{ id = 99; name = 'Disk Chain'; stages = $payload.stages }
            }

            $result = New-LMActionChain -Name 'Disk Chain' -Stages @(
                [PSCustomObject]@{ id = 1; type = 'diagnosticSource' },
                [PSCustomObject]@{ id = 2; type = 'remediationSource' }
            ) -Confirm:$false

            $result.id | Should -Be 99
        }
    }

    Context 'Set-LMActionChain' {
        It 'Resolves name to id before patching' {
            Mock Get-LMActionChain {
                return [PSCustomObject]@{ id = 55; name = 'Lookup Chain' }
            }
            Mock Invoke-LMRestMethod {
                param($Uri, $Body)
                $Uri | Should -Match '/setting/action/chains/55$'
                $payload = $Body | ConvertFrom-Json
                $payload.description | Should -Be 'Updated'
                return [PSCustomObject]@{ id = 55; description = 'Updated' }
            }

            $result = Set-LMActionChain -Name 'Lookup Chain' -Description 'Updated' -Confirm:$false
            $result.id | Should -Be 55
        }
    }

    Context 'Remove-LMActionChain' {
        It 'Deletes by Id' {
            Mock Invoke-LMRestMethod { return $null }

            $result = Remove-LMActionChain -Id 77 -Confirm:$false
            $result.Id | Should -Be 77
            $result.Message | Should -Match 'Successfully removed'
        }
    }
}

Describe 'Action Rule Cmdlet Tests' {
    BeforeAll {
        . "$PSScriptRoot/../Private/Test-LMResponseHasPagination.ps1"
        . "$PSScriptRoot/../Private/Add-ObjectTypeInfo.ps1"
        . "$PSScriptRoot/../Private/Format-LMFilter.ps1"
        . "$PSScriptRoot/../Private/Get-LMPortalURI.ps1"
        . "$PSScriptRoot/../Private/Invoke-LMPaginatedGet.ps1"
        . "$PSScriptRoot/../Private/New-LMHeader.ps1"
        . "$PSScriptRoot/../Private/Resolve-LMDebugInfo.ps1"
        . "$PSScriptRoot/../Private/Test-LookupResult.ps1"
        . "$PSScriptRoot/../Private/Invoke-LMRestMethod.ps1"
        . "$PSScriptRoot/../Public/Get-LMActionRule.ps1"
        . "$PSScriptRoot/../Public/New-LMActionRule.ps1"
        . "$PSScriptRoot/../Public/Set-LMActionRule.ps1"
        . "$PSScriptRoot/../Public/Remove-LMActionRule.ps1"
    }

    BeforeEach {
        $Script:LMAuth = @{
            Valid  = $true
            Type   = 'SessionSync'
            Portal = 'unit-test'
        }

        Mock Get-LMPortalURI { 'logicmonitor.com/santaba/rest' }
        Mock New-LMHeader { @(@{}, $null) }
        Mock Resolve-LMDebugInfo { }
        Mock Test-LookupResult { $false }
    }

    Context 'New-LMActionRule' {
        It 'Creates an action rule with required fields' {
            Mock Invoke-LMRestMethod {
                param($Uri, $Body)
                $payload = $Body | ConvertFrom-Json
                $payload.actionChainId | Should -Be 456
                $payload.deviceGroups[0] | Should -Be '/Servers'
                return [PSCustomObject]@{ id = 1; name = 'Disk Rule'; actionChainId = 456 }
            }

            $result = New-LMActionRule -Name 'Disk Rule' -ActionChainId 456 -DeviceGroups @('/Servers') -LevelStr Critical -Confirm:$false
            $result.id | Should -Be 1
        }

        It 'Calls status endpoint when Enabled is specified' {
            Mock Invoke-LMRestMethod {
                param($Uri, $Body)
                if ($Uri -like '*/status') {
                    $script:StatusCalled = $true
                    $payload = $Body | ConvertFrom-Json
                    $payload.enabled | Should -Be $false
                    return [PSCustomObject]@{ id = 1; enabled = $false }
                }
                return [PSCustomObject]@{ id = 1; name = 'Disk Rule' }
            }

            $script:StatusCalled = $false
            $null = New-LMActionRule -Name 'Disk Rule' -ActionChainId 456 -DeviceGroups @('/Servers') -LevelStr Critical -Enabled:$false -Confirm:$false
            $script:StatusCalled | Should -Be $true
        }
    }

    Context 'Set-LMActionRule' {
        It 'Patches status endpoint when Enabled is specified' {
            Mock Invoke-LMRestMethod {
                param($Uri, $Body)
                $Uri | Should -Match '/status$'
                $payload = $Body | ConvertFrom-Json
                $payload.enabled | Should -Be $true
                return [PSCustomObject]@{ id = 5; enabled = $true }
            }

            $result = Set-LMActionRule -Id 5 -Enabled $true -Confirm:$false
            $result.enabled | Should -Be $true
        }
    }

    Context 'Get-LMActionRule' {
        It 'Returns typed action rules from list response' {
            Mock Invoke-LMRestMethod {
                return [PSCustomObject]@{
                    total = 1
                    items = @(
                        [PSCustomObject]@{ id = 3; name = 'Rule A'; enabled = $true }
                    )
                }
            }

            $result = Get-LMActionRule
            $result[0].PSTypeNames[0] | Should -Be 'LogicMonitor.ActionRule'
        }
    }
}

Describe 'ADR Cmdlet Tests' {
    BeforeAll {
        . "$PSScriptRoot/../Private/Test-LMResponseHasPagination.ps1"
        . "$PSScriptRoot/../Private/Add-ObjectTypeInfo.ps1"
        . "$PSScriptRoot/../Private/Get-LMPortalURI.ps1"
        . "$PSScriptRoot/../Private/New-LMHeader.ps1"
        . "$PSScriptRoot/../Private/Resolve-LMDebugInfo.ps1"
        . "$PSScriptRoot/../Private/Test-LookupResult.ps1"
        . "$PSScriptRoot/../Private/Invoke-LMRestMethod.ps1"
        . "$PSScriptRoot/../Public/Get-LMDiagnosticRemediationModule.ps1"
        . "$PSScriptRoot/../Public/Get-LMDiagnosticRemediationExecutionResult.ps1"
        . "$PSScriptRoot/../Public/Get-LMDiagnosticSource.ps1"
        . "$PSScriptRoot/../Public/Get-LMRemediationSource.ps1"
    }

    BeforeEach {
        $Script:LMAuth = @{
            Valid  = $true
            Type   = 'SessionSync'
            Portal = 'unit-test'
        }

        Mock Get-LMPortalURI { 'logicmonitor.com/santaba/rest' }
        Mock New-LMHeader { @(@{}, $null) }
        Mock Resolve-LMDebugInfo { }
        Mock Test-LookupResult { $false }
    }

    Context 'Get-LMDiagnosticRemediationModule' {
        It 'Builds query string from parameters' {
            Mock Invoke-LMRestMethod {
                param($Uri)
                $Uri | Should -Match 'alertId=DS12345'
                $Uri | Should -Match 'moduleType=diagnostic'
                return @(
                    [PSCustomObject]@{ id = 1; name = 'Module A'; type = 'diagnostic' }
                )
            }

            $result = Get-LMDiagnosticRemediationModule -AlertId 'DS12345' -ModuleType diagnostic
            $result[0].name | Should -Be 'Module A'
        }
    }

    Context 'Get-LMDiagnosticRemediationExecutionResult' {
        It 'Includes hostId in the request URI' {
            Mock Invoke-LMRestMethod {
                param($Uri)
                $Uri | Should -Match 'hostId=10'
                $Uri | Should -Match 'perPageCount=1000'
                return [PSCustomObject]@{
                    items = @([PSCustomObject]@{ executionId = 1; moduleName = 'Diag A' })
                    cursor = $null
                    remediationCursor = $null
                }
            }

            $result = Get-LMDiagnosticRemediationExecutionResult -HostId 10
            $result[0].moduleName | Should -Be 'Diag A'
        }

        It 'Aggregates all pages by default' {
            $script:CallCount = 0
            Mock Invoke-LMRestMethod {
                $script:CallCount++
                if ($script:CallCount -eq 1) {
                    return [PSCustomObject]@{
                        items = @([PSCustomObject]@{ executionId = 1; moduleName = 'Diag A' })
                        cursor = 'next'
                        remediationCursor = $null
                    }
                }
                return [PSCustomObject]@{
                    items = @([PSCustomObject]@{ executionId = 2; moduleName = 'Diag B' })
                    cursor = $null
                    remediationCursor = $null
                }
            }

            $result = Get-LMDiagnosticRemediationExecutionResult -HostId 10
            ($result | Measure-Object).Count | Should -Be 2
            $script:CallCount | Should -Be 2
        }
    }
}