Tests/GitHubCodespaces.tests.ps1

# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.

<#
.Synopsis
   Tests for GitHubCodespaces.ps1 module
#>


[CmdletBinding()]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '',
    Justification = 'Suppress false positives in Pester code blocks')]
param()

# This is common test code setup logic for all Pester test files
BeforeAll {
    $moduleRootPath = Split-Path -Path $PSScriptRoot -Parent
    . (Join-Path -Path $moduleRootPath -ChildPath 'Tests\Common.ps1')

    # Define Script-scoped, readonly, hidden variables.
    @{
        defaultRepositoryName = ([Guid]::NewGuid().Guid)
    }.GetEnumerator() | ForEach-Object {
        Set-Variable -Force -Scope Script -Option ReadOnly -Visibility Private -Name $_.Key -Value $_.Value
    }

    $newGitHubRepositoryParms = @{
        AutoInit = $true
        Private = $true
        RepositoryName = $defaultRepositoryName
        OrganizationName = $script:organizationName
    }
    $repo = New-GitHubRepository @newGitHubRepositoryParms
    # Get the main/master branch name
    $mainBranchName = $repo | Get-GitHubRepositoryBranch | Select-Object -ExpandProperty name -First 1
}

Describe 'GitHubCodespaces\Delete-GitHubCodespace' {
    Context 'When deleting a codespace for the authenticated user' {
        BeforeEach {
            # Suppress HTTP 202 warning for codespace creation
            # TODO: Suppression is not working as intended here.
            $WarningPreference = 'SilentlyContinue'

            $newGitHubCodespaceParms = @{
                OwnerName = $repo.owner.login
                RepositoryName = $defaultRepositoryName
            }
            $codespace = New-GitHubCodespace @newGitHubCodespaceParms -Wait
        }

        It 'Should get no content using -Confirm:$false' {
            # Also asserts pipeline input
            $codespace | Remove-GitHubCodespace -Confirm:$false
            { Get-GitHubCodespace -CodespaceName $codespace.name } | Should -Throw
        }

        It 'Should get no content using -Force' {
            # Also assert CodespaceName input
            Remove-GitHubCodespace -CodespaceName $codespace.name -Force
            { Get-GitHubCodespace -CodespaceName $codespace.name } | Should -Throw
        }
    }
}

Describe 'GitHubCodespaces\Get-GitHubCodespace' {
    BeforeAll {
        # Suppress HTTP 202 warning for codespace creation
        $WarningPreference = 'SilentlyContinue'

        $newGitHubCodespaceParms = @{
            OwnerName = $repo.owner.login
            RepositoryName = $defaultRepositoryName
        }
        $null = New-GitHubCodespace @newGitHubCodespaceParms -Wait
    }

    Context 'When getting codespaces for the authenticated user' {
        BeforeAll {
            $codespaces = Get-GitHubCodespace |
                Where-Object { $_.repository.name -eq $defaultRepositoryName }
        }

        It 'Should return objects of the correct type' {
            $codespaces[0].PSObject.TypeNames[0] | Should -Be 'GitHub.Codespace'
        }

        It 'Should return one or more results' {
            @($codespaces | Where-Object { $_ }).Count | Should -BeGreaterOrEqual 1
        }

        It 'Should return the correct properties' {
            foreach ($codespace in $codespaces)
            {
                $codespace.owner.login | Should -Be $script:OwnerName
                $codespace.repository.name | Should -Be $newGitHubCodespaceParms.RepositoryName
            }
        }
    }

    Context 'When getting a codespace for a specified owner and repository' {
        BeforeAll {
            $codespaces = Get-GitHubCodespace @newGitHubCodespaceParms
        }

        It 'Should return objects of the correct type' {
            $codespaces[0].PSObject.TypeNames[0] | Should -Be 'GitHub.Codespace'
        }

        It 'Should return one or more results' {
            @($codespaces | Where-Object { $_ }).Count | Should -BeGreaterOrEqual 1
        }

        It 'Should return the correct properties' {
            foreach ($codespace in $codespaces)
            {
                $codespace.owner.login | Should -Be $script:OwnerName
                $codespace.repository.name | Should -Be $newGitHubCodespaceParms.RepositoryName
            }
        }
    }

    Context 'When getting a codespace for a specified organization user' {
        BeforeAll {
            $codespaces = Get-GitHubCodespace -OrganizationName $script:organizationName
            $userCodespaces = Get-GitHubCodespace -OrganizationName $script:organizationName -UserName $script:OwnerName
        }

        It 'Should have results for the organization user' {
            $userCodespaces.name | Should -BeIn $codespaces.name
        }

        It 'Should return the correct properties' {
            foreach ($codespace in $userCodespaces)
            {
                $codespace.owner.login | Should -Be $script:OwnerName
            }
        }
    }

    Context 'When getting a codespace for a specified codespace name' {
        BeforeAll {
            $codespaces = Get-GitHubCodespace
            $codespace = Get-GitHubCodespace -CodespaceName $codespaces[0].name
        }

        It 'Should return objects of the correct type' {
            $codespace.PSObject.TypeNames[0] | Should -Be 'GitHub.Codespace'
        }

        It 'Should return the correct properties' {
            $codespace.owner.login | Should -Be $script:OwnerName
        }
    }

    Context 'When specifiying the Uri parameter' {
        BeforeAll {
            $codespace = Get-GitHubCodespace -Uri $repo.RepositoryUrl
        }

        It 'Should return objects of the correct type' {
            $codespace.PSObject.TypeNames[0] | Should -Be 'GitHub.Codespace'
        }

        It 'Should return the correct properties' {
            $codespace.owner.login | Should -Be $script:OwnerName
            $codespace.repository.name | Should -Be $repo.name
        }
    }

    Context "When specifiying the Uri parameter from the pipeline" {
        BeforeAll {
            $codespace = $repo | Get-GitHubCodespace
        }

        It 'Should return objects of the correct type' {
            $codespace.PSObject.TypeNames[0] | Should -Be 'GitHub.Codespace'
        }

        It 'Should return the correct properties' {
            $codespace.repository.name | Should -Be $repo.name
        }
    }

    AfterAll {
        Get-GitHubCodespace @newGitHubCodespaceParms | Remove-GitHubCodespace -Confirm:$false -Force
    }
}


Describe 'GitHubCodespaces\New-GitHubCodespace' {
    Context -Name 'When creating a repository for the authenticated user' {
        Context -Name 'When creating a codespace with default settings with RepositoryId' {
            BeforeAll {
                $newGitHubCodespaceParms = @{
                    RepositoryId = $repo.Id
                }
                $codespace = New-GitHubCodespace @newGitHubCodespaceParms -Wait
            }

            It 'Should return an object of the correct type' {
                $codespace | Should -BeOfType PSCustomObject
            }

            It 'Should return the correct properties' {
                $codespace.display_name | Should -Not -BeNullOrEmpty
                $codespace.repository.name | Should -Be $repo.name
                $codespace.owner.UserName | Should -Be $script:OwnerName
                $codespace.template | Should -BeNullOrEmpty
            }

            AfterAll {
                if ($codespace)
                {
                    Remove-GitHubCodespace -CodespaceName $codespace.name -Confirm:$false
                }
            }
        }

        Context -Name 'When creating a codespace with default settings with Ref' {
            BeforeAll {
                $prBranchName = 'testCodespaceByRef'
                $prBranch = $repo | New-GitHubRepositoryBranch -BranchName $mainBranchName -TargetBranchName $prBranchName
                $setContentParms = @{
                    Path = 'README.md'
                    Content = 'When creating a codespace with default settings with Ref'
                    CommitMessage = $prBranchName
                    BranchName = $prBranchName
                }
                $repo | Set-GitHubContent @setContentParms
                $pullRequest = $prBranch | New-GitHubPullRequest -Title $prBranchName -Head $prBranchName -Base $mainBranchName

                $newGitHubCodespaceParms = @{
                    Ref = $pullRequest.head.ref
                    RepositoryId = $repo.Id
                }
                $codespace = New-GitHubCodespace @newGitHubCodespaceParms -Wait
            }

            It 'Should return an object of the correct type' {
                $codespace | Should -BeOfType PSCustomObject
            }

            It 'Should return the correct properties' {
                $codespace.display_name | Should -Not -BeNullOrEmpty
                $codespace.git_status.ref | Should -Be $pullRequest.head.ref
                $codespace.repository.name | Should -Be $repo.name
                $codespace.owner.UserName | Should -Be $script:OwnerName
                $codespace.template | Should -BeNullOrEmpty
            }

            AfterAll {
                if ($codespace)
                {
                    Remove-GitHubCodespace -CodespaceName $codespace.name -Confirm:$false
                }
            }
        }

        Context -Name 'When creating a codespace with default settings from a PullRequest' {
            BeforeAll {
                $prBranchName = 'testCodespaceFromPR'
                $prBranch = $repo | New-GitHubRepositoryBranch -BranchName $mainBranchName -TargetBranchName $prBranchName
                $setContentParms = @{
                    Path = 'README.md'
                    Content = 'When creating a codespace with default settings from a PullRequest'
                    CommitMessage = $prBranchName
                    BranchName = $prBranchName
                }
                $repo | Set-GitHubContent @setContentParms
                $pullRequest = $prBranch | New-GitHubPullRequest -Title $prBranchName -Head $prBranchName -Base $mainBranchName

                $newGitHubCodespaceParms = @{
                    PullRequest = $pullRequest.number
                    RepositoryId = $repo.Id
                }
                $codespace = New-GitHubCodespace @newGitHubCodespaceParms -Wait
            }

            It 'Should return an object of the correct type' {
                $codespace | Should -BeOfType PSCustomObject
            }

            It 'Should return the correct properties' {
                $codespace.display_name | Should -Not -BeNullOrEmpty
                $codespace.repository.name | Should -Be $repo.name
                $codespace.owner.UserName | Should -Be $script:OwnerName
                $codespace.pulls_url | Should -Be $pullRequest.url
                $codespace.template | Should -BeNullOrEmpty
            }

            AfterAll {
                if ($codespace)
                {
                    Remove-GitHubCodespace -CodespaceName $codespace.name -Confirm:$false
                }
            }
        }

        Context -Name 'When creating a codespace with all possible settings' {
            BeforeAll {
                $newGitHubCodespaceParms = @{
                    # ClientIp = 'TODO ???? - should be instead of rather than in addition to Geo, perhaps add some param validation to the function'
                    # DevContainerPath = 'Will add to test in the future when Get-GitHubDevContainer is implemented and the test repo includes one'
                    DisplayName = 'PowerShellForGitHub pester test'
                    Geo = 'UsWest' # location was deprecated in favor of Geo
                    Machine = 'basicLinux32gb'
                    NoMultipleRepoPermissions = $true # Not sure how to assert this, but this proves it accepts the switch without error
                    IdleRetentionPeriodMinutes = 10
                    TimeoutMinutes = 5
                    # WorkingDirectory = 'TODO ???? - not sure how to handle this'
                }
                $codespace = $repo | New-GitHubCodespace @newGitHubCodespaceParms -Wait
            }

            It 'Should return an object of the correct type' {
                $codespace | Should -BeOfType PSCustomObject
            }

            It 'Should return the correct properties' {
                # $codespace.devcontainer_path | Should -Be
                $codespace.display_name | Should -Be $newGitHubCodespaceParms.DisplayName
                $codespace.idle_timeout_minutes | Should -Be $newGitHubCodespaceParms.TimeoutMinutes
                $codespace.location | Should -Match 'WestUs' # location should align with our requested Geo
                $codespace.machine.name | Should -Be $newGitHubCodespaceParms.Machine
                $codespace.owner.UserName | Should -Be $script:OwnerName
                $codespace.repository.name | Should -Be $repo.name
                $codespace.retention_period_minutes | Should -Be $newGitHubCodespaceParms.IdleRetentionPeriodMinutes
                $codespace.template | Should -BeNullOrEmpty
            }

            AfterAll {
                if ($codespace)
                {
                    Remove-GitHubCodespace -CodespaceName $codespace.name -Confirm:$false
                }
            }
        }

        Context -Name 'When creating a codespace with default settings with Repository Elements' {
            BeforeAll {
                $newGitHubCodespaceParms = @{
                    RepositoryName = $repo.name
                    OwnerName = $repo.owner.login
                }
                $codespace = New-GitHubCodespace @newGitHubCodespaceParms -Wait
            }

            It 'Should return an object of the correct type' {
                $codespace | Should -BeOfType PSCustomObject
            }

            It 'Should return the correct properties' {
                $codespace.display_name | Should -Not -BeNullOrEmpty
                $codespace.repository.name | Should -Be $repo.name
                $codespace.owner.UserName | Should -Be $script:OwnerName
                $codespace.template | Should -BeNullOrEmpty
            }

            AfterAll {
                if ($codespace)
                {
                    Remove-GitHubCodespace -CodespaceName $codespace.name -Confirm:$false
                }
            }
        }
    }
}

Describe 'GitHubCodespaces\Start-GitHubCodespace' {
    BeforeAll {
        # Suppress HTTP 202 warning for codespace creation
        $WarningPreference = 'SilentlyContinue'

        $newGitHubCodespaceParms = @{
            OwnerName = $repo.owner.login
            RepositoryName = $defaultRepositoryName
        }
        $null = New-GitHubCodespace @newGitHubCodespaceParms -Wait
    }

    Context 'When starting a codespace for the authenticated user' {
        BeforeAll {
            $codespace = Get-GitHubCodespace @newGitHubCodespaceParms | Select-Object -First 1
        }

        It 'Should not throw' {
            # Also asserts pipeline input
            { $codespace | Start-GitHubCodespace } | Should -Not -Throw
        }

        It 'Should become Available' {
            # Also asserts Wait and PassThru
            $result = $codespace | Start-GitHubCodespace -Wait -PassThru
            $result.State | Should -Be 'Available'
        }
    }

    AfterAll {
        Get-GitHubCodespace @newGitHubCodespaceParms | Remove-GitHubCodespace -Confirm:$false -Force
    }
}

Describe 'GitHubCodespaces\Stop-GitHubCodespace' {
    BeforeAll {
        # Suppress HTTP 202 warning for codespace creation
        $WarningPreference = 'SilentlyContinue'

        $newGitHubCodespaceParms = @{
            OwnerName = $repo.owner.login
            RepositoryName = $defaultRepositoryName
        }
        $null = New-GitHubCodespace @newGitHubCodespaceParms -Wait
    }

    Context 'When stopping a codespace for the authenticated user' {
        BeforeAll {
            $codespace = Get-GitHubCodespace @newGitHubCodespaceParms | Select-Object -First 1
        }

        It 'Should not throw' {
            # Also asserts pipeline input
            { $codespace | Stop-GitHubCodespace } | Should -Not -Throw
        }

        It 'Should become Shutdown' {
            # Also asserts Wait and PassThru
            $result = $codespace | Stop-GitHubCodespace -Wait -PassThru
            $result.State | Should -Be 'Shutdown'
        }
    }

    AfterAll {
        Get-GitHubCodespace @newGitHubCodespaceParms | Remove-GitHubCodespace -Confirm:$false -Force
    }
}

AfterAll {
    if (Get-Variable -Name repo -ErrorAction SilentlyContinue)
    {
        # Should delete any corresponding codespaces along with it
        $repo | Remove-GitHubRepository -Confirm:$false
    }

    if (Test-Path -Path $script:originalConfigFile -PathType Leaf)
    {
        # Restore the user's configuration to its pre-test state
        Restore-GitHubConfiguration -Path $script:originalConfigFile
        $script:originalConfigFile = $null
    }
}