tests/Private/PSCumulus.PathModel.Tests.ps1

BeforeAll {
    Import-Module (Resolve-Path (Join-Path $PSScriptRoot '..\..\PSCumulus.psd1')).Path -Force
}

Describe 'CloudPath.Parse' {
    It 'Parses a full Azure instance path' {
        InModuleScope PSCumulus {
            $path = 'Azure:\prod-rg\Instances\web-server-01'
            $result = [CloudPath]::Parse($path)

            $result.Provider | Should -Be 'Azure'
            $result.Scope | Should -Be 'prod-rg'
            $result.Kind | Should -Be 'Instances'
            $result.ResourceName | Should -Be 'web-server-01'
            $result.Depth | Should -Be ([CloudPathDepth]::Resource)
        }
    }

    It 'Parses a full AWS instance path' {
        InModuleScope PSCumulus {
            $path = 'AWS:\us-east-1\Instances\app-server-01'
            $result = [CloudPath]::Parse($path)

            $result.Provider | Should -Be 'AWS'
            $result.Scope | Should -Be 'us-east-1'
            $result.Kind | Should -Be 'Instances'
            $result.ResourceName | Should -Be 'app-server-01'
            $result.Depth | Should -Be ([CloudPathDepth]::Resource)
        }
    }

    It 'Parses a full GCP instance path' {
        InModuleScope PSCumulus {
            $path = 'GCP:\my-project\Instances\gcp-vm-01'
            $result = [CloudPath]::Parse($path)

            $result.Provider | Should -Be 'GCP'
            $result.Scope | Should -Be 'my-project'
            $result.Kind | Should -Be 'Instances'
            $result.ResourceName | Should -Be 'gcp-vm-01'
            $result.Depth | Should -Be ([CloudPathDepth]::Resource)
        }
    }

    It 'Parses a root path' {
        InModuleScope PSCumulus {
            $path = 'Azure:\'
            $result = [CloudPath]::Parse($path)

            $result.Provider | Should -Be 'Azure'
            $result.Scope | Should -BeNullOrEmpty
            $result.Kind | Should -BeNullOrEmpty
            $result.ResourceName | Should -BeNullOrEmpty
            $result.Depth | Should -Be ([CloudPathDepth]::Root)
        }
    }

    It 'Parses a scope path' {
        InModuleScope PSCumulus {
            $path = 'AWS:\us-east-1'
            $result = [CloudPath]::Parse($path)

            $result.Provider | Should -Be 'AWS'
            $result.Scope | Should -Be 'us-east-1'
            $result.Kind | Should -BeNullOrEmpty
            $result.ResourceName | Should -BeNullOrEmpty
            $result.Depth | Should -Be ([CloudPathDepth]::Scope)
        }
    }

    It 'Parses a kind path' {
        InModuleScope PSCumulus {
            $path = 'GCP:\my-project\Disks'
            $result = [CloudPath]::Parse($path)

            $result.Provider | Should -Be 'GCP'
            $result.Scope | Should -Be 'my-project'
            $result.Kind | Should -Be 'Disks'
            $result.ResourceName | Should -BeNullOrEmpty
            $result.Depth | Should -Be ([CloudPathDepth]::Kind)
        }
    }

    It 'Normalizes singular kind names to plural' {
        InModuleScope PSCumulus {
            { [CloudPath]::Parse('Azure:\rg\Instances\vm') } | Should -Not -Throw

            $result = [CloudPath]::Parse('Azure:\rg\Storage\acct')
            $result.Kind | Should -Be 'Storage'

            $result = [CloudPath]::Parse('AWS:\region\Networks\vpc')
            $result.Kind | Should -Be 'Networks'

            $result = [CloudPath]::Parse('GCP:\proj\Functions\fn')
            $result.Kind | Should -Be 'Functions'

            $result = [CloudPath]::Parse('Azure:\rg\Tags\t')
            $result.Kind | Should -Be 'Tags'
        }
    }

    It 'Handles case-insensitive provider names' {
        InModuleScope PSCumulus {
            $result = [CloudPath]::Parse('azure:\rg\Instances\vm')
            $result.Provider | Should -Be 'Azure'

            $result = [CloudPath]::Parse('AZURE:\rg\Instances\vm')
            $result.Provider | Should -Be 'Azure'

            $result = [CloudPath]::Parse('aws:\rg\Instances\vm')
            $result.Provider | Should -Be 'AWS'

            $result = [CloudPath]::Parse('gcp:\rg\Instances\vm')
            $result.Provider | Should -Be 'GCP'
        }
    }

    It 'Handles case-insensitive kind names' {
        InModuleScope PSCumulus {
            $result = [CloudPath]::Parse('Azure:\rg\instances\vm')
            $result.Kind | Should -Be 'Instances'

            $result = [CloudPath]::Parse('AWS:\region\DISKS\d1')
            $result.Kind | Should -Be 'Disks'

            $result = [CloudPath]::Parse('GCP:\proj\storage\bucket')
            $result.Kind | Should -Be 'Storage'

            $result = [CloudPath]::Parse('Azure:\rg\networks\vnet')
            $result.Kind | Should -Be 'Networks'

            $result = [CloudPath]::Parse('AWS:\region\FUNCTIONS\fn')
            $result.Kind | Should -Be 'Functions'

            $result = [CloudPath]::Parse('GCP:\proj\TAGS\t1')
            $result.Kind | Should -Be 'Tags'
        }
    }

    It 'Throws on null or empty path' {
        InModuleScope PSCumulus {
            { [CloudPath]::Parse($null) } | Should -Throw -ExpectedMessage '*cannot be null or empty*'
            { [CloudPath]::Parse('') } | Should -Throw -ExpectedMessage '*cannot be null or empty*'
            { [CloudPath]::Parse(' ') } | Should -Throw -ExpectedMessage '*cannot be null or empty*'
        }
    }

    It 'Throws on missing provider separator' {
        InModuleScope PSCumulus {
            { [CloudPath]::Parse('Azure-prod-rg-Instances') } | Should -Throw -ExpectedMessage '*must contain a provider separator*'
        }
    }

    It 'Throws on invalid provider' {
        InModuleScope PSCumulus {
            { [CloudPath]::Parse('Invalid:\rg\Instances\vm') } | Should -Throw -ExpectedMessage "*Invalid provider 'Invalid'*"
        }
    }

    It 'Throws on invalid kind' {
        InModuleScope PSCumulus {
            { [CloudPath]::Parse('Azure:\rg\InvalidKind\vm') } | Should -Throw -ExpectedMessage "*Invalid kind 'InvalidKind'*"
        }
    }

    It 'Throws on too many path segments' {
        InModuleScope PSCumulus {
            { [CloudPath]::Parse('Azure:\rg\Instances\vm\extra') } | Should -Throw -ExpectedMessage '*too many segments*'
        }
    }
}

Describe 'CloudPath.IsValid' {
    It 'Returns true for valid paths' {
        InModuleScope PSCumulus {
            [CloudPath]::IsValid('Azure:\prod-rg\Instances\web-server-01') | Should -Be $true
            [CloudPath]::IsValid('AWS:\us-east-1\Disks') | Should -Be $true
            [CloudPath]::IsValid('GCP:\my-project') | Should -Be $true
            [CloudPath]::IsValid('Azure:\') | Should -Be $true
        }
    }

    It 'Returns false for invalid paths' {
        InModuleScope PSCumulus {
            [CloudPath]::IsValid('Invalid:\rg\Instances\vm') | Should -Be $false
            [CloudPath]::IsValid('Azure:\rg\InvalidKind\vm') | Should -Be $false
            [CloudPath]::IsValid('') | Should -Be $false
            [CloudPath]::IsValid($null) | Should -Be $false
        }
    }
}

Describe 'CloudPath.ToString' {
    It 'Roundtrips a root path' {
        InModuleScope PSCumulus {
            $original = 'Azure:\'
            $parsed = [CloudPath]::Parse($original)
            $parsed.ToString() | Should -Be $original
        }
    }

    It 'Roundtrips a scope path' {
        InModuleScope PSCumulus {
            $original = 'AWS:\us-east-1'
            $parsed = [CloudPath]::Parse($original)
            $parsed.ToString() | Should -Be $original
        }
    }

    It 'Roundtrips a kind path' {
        InModuleScope PSCumulus {
            $original = 'GCP:\my-project\Disks'
            $parsed = [CloudPath]::Parse($original)
            $parsed.ToString() | Should -Be $original
        }
    }

    It 'Roundtrips a resource path' {
        InModuleScope PSCumulus {
            $original = 'Azure:\prod-rg\Instances\web-server-01'
            $parsed = [CloudPath]::Parse($original)
            $parsed.ToString() | Should -Be $original
        }
    }

    It 'Normalizes singular to plural in ToString output' {
        InModuleScope PSCumulus {
            $parsed = [CloudPath]::Parse('Azure:\rg\Instances\vm')
            $parsed.Kind | Should -Be 'Instances'
            $parsed.ToString() | Should -Be 'Azure:\rg\Instances\vm'
        }
    }
}

Describe 'CloudPathResolver.Resolve' {
    It 'Resolves Azure instance path correctly' {
        InModuleScope PSCumulus {
            $cloudPath = [CloudPath]::Parse('Azure:\prod-rg\Instances\web-server-01')
            $resolved = [CloudPathResolver]::Resolve($cloudPath)

            $resolved.CommandName | Should -Be 'Get-AzureInstanceData'
            $resolved.ArgumentMap.ResourceGroup | Should -Be 'prod-rg'
            $resolved.ArgumentMap.Name | Should -Be 'web-server-01'
        }
    }

    It 'Resolves Azure disk path correctly' {
        InModuleScope PSCumulus {
            $cloudPath = [CloudPath]::Parse('Azure:\prod-rg\Disks\disk-01')
            $resolved = [CloudPathResolver]::Resolve($cloudPath)

            $resolved.CommandName | Should -Be 'Get-AzureDiskData'
            $resolved.ArgumentMap.ResourceGroup | Should -Be 'prod-rg'
            $resolved.ArgumentMap.Name | Should -Be 'disk-01'
        }
    }

    It 'Resolves AWS instance path correctly' {
        InModuleScope PSCumulus {
            $cloudPath = [CloudPath]::Parse('AWS:\us-east-1\Instances\i-12345')
            $resolved = [CloudPathResolver]::Resolve($cloudPath)

            $resolved.CommandName | Should -Be 'Get-AWSInstanceData'
            $resolved.ArgumentMap.Region | Should -Be 'us-east-1'
            $resolved.ArgumentMap.Name | Should -Be 'i-12345'
        }
    }

    It 'Resolves GCP instance path correctly' {
        InModuleScope PSCumulus {
            $cloudPath = [CloudPath]::Parse('GCP:\my-project\Instances\gcp-vm')
            $resolved = [CloudPathResolver]::Resolve($cloudPath)

            $resolved.CommandName | Should -Be 'Get-GCPInstanceData'
            $resolved.ArgumentMap.Project | Should -Be 'my-project'
            $resolved.ArgumentMap.Name | Should -Be 'gcp-vm'
        }
    }

    It 'Resolves kind-level path without resource name' {
        InModuleScope PSCumulus {
            $cloudPath = [CloudPath]::Parse('Azure:\prod-rg\Storage')
            $resolved = [CloudPathResolver]::Resolve($cloudPath)

            $resolved.CommandName | Should -Be 'Get-AzureStorageData'
            $resolved.ArgumentMap.ResourceGroup | Should -Be 'prod-rg'
            $resolved.ArgumentMap.ContainsKey('Name') | Should -Be $false
        }
    }
}

Describe 'CloudPathResolver.GetBackendCommand' {
    It 'Returns correct command for each kind' {
        InModuleScope PSCumulus {
            [CloudPathResolver]::GetBackendCommand('Azure', 'Instances') | Should -Be 'Get-AzureInstanceData'
            [CloudPathResolver]::GetBackendCommand('Azure', 'Disks') | Should -Be 'Get-AzureDiskData'
            [CloudPathResolver]::GetBackendCommand('Azure', 'Storage') | Should -Be 'Get-AzureStorageData'
            [CloudPathResolver]::GetBackendCommand('Azure', 'Networks') | Should -Be 'Get-AzureNetworkData'
            [CloudPathResolver]::GetBackendCommand('Azure', 'Functions') | Should -Be 'Get-AzureFunctionData'
            [CloudPathResolver]::GetBackendCommand('Azure', 'Tags') | Should -Be 'Get-AzureTagData'

            [CloudPathResolver]::GetBackendCommand('AWS', 'Instances') | Should -Be 'Get-AWSInstanceData'
            [CloudPathResolver]::GetBackendCommand('GCP', 'Instances') | Should -Be 'Get-GCPInstanceData'
        }
    }

    It 'Throws on invalid kind' {
        InModuleScope PSCumulus {
            { [CloudPathResolver]::GetBackendCommand('Azure', 'Invalid') } | Should -Throw
        }
    }
}

Describe 'CloudPathResolver.GetScopeArgument' {
    It 'Returns correct scope argument for each provider' {
        InModuleScope PSCumulus {
            $azureScope = [CloudPathResolver]::GetScopeArgument('Azure', 'prod-rg')
            $azureScope.ResourceGroup | Should -Be 'prod-rg'
            $azureScope.Count | Should -Be 1

            $awsScope = [CloudPathResolver]::GetScopeArgument('AWS', 'us-east-1')
            $awsScope.Region | Should -Be 'us-east-1'
            $awsScope.Count | Should -Be 1

            $gcpScope = [CloudPathResolver]::GetScopeArgument('GCP', 'my-project')
            $gcpScope.Project | Should -Be 'my-project'
            $gcpScope.Count | Should -Be 1
        }
    }

    It 'Throws on invalid provider' {
        InModuleScope PSCumulus {
            { [CloudPathResolver]::GetScopeArgument('Invalid', 'scope') } | Should -Throw
        }
    }
}