Tests/GitHubContents.tests.ps1

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

<#
.Synopsis
   Tests for GitHubContents.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
$moduleRootPath = Split-Path -Path $PSScriptRoot -Parent
. (Join-Path -Path $moduleRootPath -ChildPath 'Tests\Common.ps1')

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

    # Need two separate blocks to set constants because we need to reference a constant from the first block in this block.
    @{
        htmlOutputStart = '<div id="file" class="md" data-path="README.md">'
        rawOutput = "# $repoGuid"
    }.GetEnumerator() | ForEach-Object {
        Set-Variable -Force -Scope Script -Option ReadOnly -Visibility Private -Name $_.Key -Value $_.Value
    }

    Describe 'Getting file and folder content' {
        BeforeAll {
            # AutoInit will create a readme with the GUID of the repo name
            $repo = New-GitHubRepository -RepositoryName ($repoGuid) -AutoInit
        }

        AfterAll {
            Remove-GitHubRepository -Uri $repo.svn_url -Confirm:$false
        }

        Context 'For getting folder contents with parameters' {
            $folderOutput = Get-GitHubContent -OwnerName $script:ownerName -RepositoryName $repo.name

            It "Should have the expected name" {
                $folderOutput.name | Should -BeNullOrEmpty
            }

            It "Should have the expected path" {
                $folderOutput.path | Should -BeNullOrEmpty
            }

            It "Should have the expected type" {
                $folderOutput.type | Should -Be "dir"
            }

            It "Should have the expected entries" {
                $folderOutput.entries.length | Should -Be 1
            }

            It "Should have the expected entry data" {
                $folderOutput.entries[0].name | Should -Be $readmeFileName
                $folderOutput.entries[0].path | Should -Be $readmeFileName
            }

            It "Should have the expected type and additional properties" {
                $folderOutput.PSObject.TypeNames[0] | Should -Be 'GitHub.Content'
                $folderOutput.RepositoryUrl | Should -Be $repo.RepositoryUrl
            }
        }

        Context 'For getting folder contents via URL' {
            $folderOutput = Get-GitHubContent -Uri "https://github.com/$($script:ownerName)/$($repo.name)"

            It "Should have the expected name" {
                $folderOutput.name | Should -BeNullOrEmpty
            }

            It "Should have the expected path" {
                $folderOutput.path | Should -BeNullOrEmpty
            }

            It "Should have the expected type" {
                $folderOutput.type | Should -Be "dir"
            }

            It "Should have the expected entries" {
                $folderOutput.entries.length | Should -Be 1
            }

            It "Should have the expected entry data" {
                $folderOutput.entries[0].name | Should -Be $readmeFileName
                $folderOutput.entries[0].path | Should -Be $readmeFileName
            }

            It "Should have the expected type" {
                $folderOutput.PSObject.TypeNames[0] | Should -Be 'GitHub.Content'
                $folderOutput.RepositoryUrl | Should -Be $repo.RepositoryUrl
            }
        }

        Context 'For getting folder contents with the repo on the pipeline' {
            $folderOutput = $repo | Get-GitHubContent

            It "Should have the expected name" {
                $folderOutput.name | Should -BeNullOrEmpty
            }

            It "Should have the expected path" {
                $folderOutput.path | Should -BeNullOrEmpty
            }

            It "Should have the expected type" {
                $folderOutput.type | Should -Be "dir"
            }

            It "Should have the expected entries" {
                $folderOutput.entries.length | Should -Be 1
            }

            It "Should have the expected entry data" {
                $folderOutput.entries[0].name | Should -Be $readmeFileName
                $folderOutput.entries[0].path | Should -Be $readmeFileName
            }

            It "Should have the expected type" {
                $folderOutput.PSObject.TypeNames[0] | Should -Be 'GitHub.Content'
                $folderOutput.RepositoryUrl | Should -Be $repo.RepositoryUrl
            }
        }

        Context 'For getting raw (byte) file contents' {
            $readmeFileBytes = Get-GitHubContent -OwnerName $script:ownerName -RepositoryName $repo.name -Path $readmeFileName -MediaType Raw
            $readmeFileString = [System.Text.Encoding]::UTF8.GetString($readmeFileBytes)

            It "Should have the expected content" {
                $readmeFileString | Should -Be $rawOutput
            }

            It "Should have the expected type" {
                $readmeFileString.PSObject.TypeNames[0] | Should -Not -Be 'GitHub.Content'
                $readmeFileString.RepositoryUrl | Should -BeNullOrEmpty
            }
        }

        Context 'For getting raw (string) file contents' {
            $readmeFileString = Get-GitHubContent -OwnerName $script:ownerName -RepositoryName $repo.name -Path $readmeFileName -MediaType Raw -ResultAsString

            It "Should have the expected content" {
                $readmeFileString | Should -Be $rawOutput
            }

            It "Should have the expected type" {
                $readmeFileString.PSObject.TypeNames[0] | Should -Not -Be 'GitHub.Content'
                $readmeFileString.RepositoryUrl | Should -BeNullOrEmpty
            }
        }

        Context 'For getting html (byte) file contents' {
            $readmeFileBytes = Get-GitHubContent -OwnerName $script:ownerName -RepositoryName $repo.name -Path $readmeFileName -MediaType Html
            $readmeFileString = [System.Text.Encoding]::UTF8.GetString($readmeFileBytes)

            # Replace newlines with empty for comparison purposes
            $readmeNoBreaks = $readmeFileString.Replace("`n", "").Replace("`r", "")
            It "Should have the expected content" {
                # GitHub changes the syntax for this file too frequently, so we'll just do some
                # partial matches to make sure we're getting HTML output for the right repo.
                $readmeNoBreaks.StartsWith($htmlOutputStart) | Should -BeTrue
                $readmeNoBreaks.IndexOf($repoGuid) | Should -BeGreaterOrEqual 0
            }

            It "Should have the expected type" {
                $readmeNoBreaks.PSObject.TypeNames[0] | Should -Not -Be 'GitHub.Content'
                $readmeNoBreaks.RepositoryUrl | Should -BeNullOrEmpty
            }
        }

        Context 'For getting html (string) file contents' {
            $readmeFileString = Get-GitHubContent -OwnerName $script:ownerName -RepositoryName $repo.name -Path $readmeFileName -MediaType Html -ResultAsString

            # Replace newlines with empty for comparison purposes
            $readmeNoBreaks = $readmeFileString.Replace("`n", "").Replace("`r", "")
            It "Should have the expected content" {
                # GitHub changes the syntax for this file too frequently, so we'll just do some
                # partial matches to make sure we're getting HTML output for the right repo.
                $readmeNoBreaks.StartsWith($htmlOutputStart) | Should -BeTrue
                $readmeNoBreaks.IndexOf($repoGuid) | Should -BeGreaterOrEqual 0
            }

            It "Should have the expected type" {
                $readmeFileString.PSObject.TypeNames[0] | Should -Not -Be 'GitHub.Content'
                $readmeFileString.RepositoryUrl | Should -BeNullOrEmpty
            }
        }

        Context 'For getting object (default) file result' {
            $readmeFileObject = Get-GitHubContent -OwnerName $script:ownerName -RepositoryName $repo.name -Path $readmeFileName

            It "Should have the expected name" {
                $readmeFileObject.name | Should -Be $readmeFileName
            }

            It "Should have the expected path" {
                $readmeFileObject.path | Should -Be $readmeFileName
            }

            It "Should have the expected type" {
                $readmeFileObject.type | Should -Be "file"
            }

            It "Should have the expected encoding" {
                $readmeFileObject.encoding | Should -Be "base64"
            }

            It "Should have the expected content" {
                # Convert from base64
                $readmeFileString = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($readmeFileObject.content))
                $readmeFileString | Should -Be $rawOutput
            }

            It "Should have the expected type" {
                $readmeFileObject.PSObject.TypeNames[0] | Should -Be 'GitHub.Content'
                $readmeFileObject.RepositoryUrl | Should -Be $repo.RepositoryUrl
            }
        }

        Context 'For getting object file result as string' {
            $readmeFileObject = Get-GitHubContent -OwnerName $script:ownerName -RepositoryName $repo.name -Path $readmeFileName -MediaType Object -ResultAsString

            It "Should have the expected name" {
                $readmeFileObject.name | Should -Be $readmeFileName
            }
            It "Should have the expected path" {
                $readmeFileObject.path | Should -Be $readmeFileName
            }
            It "Should have the expected type" {
                $readmeFileObject.type | Should -Be "file"
            }
            It "Should have the expected encoding" {
                $readmeFileObject.encoding | Should -Be "base64"
            }

            It "Should have the expected content" {
                $readmeFileObject.contentAsString | Should -Be $rawOutput
            }

            It "Should have the expected type" {
                $readmeFileObject.PSObject.TypeNames[0] | Should -Be 'GitHub.Content'
                $readmeFileObject.RepositoryUrl | Should -Be $repo.RepositoryUrl
            }
        }
    }

    Describe 'GitHubContents/Set-GitHubContent' {
        BeforeAll {
            $repoName = [Guid]::NewGuid().Guid
            $repo = New-GitHubRepository -RepositoryName $repoName -AutoInit
        }

        Context 'When setting new file content' {
            BeforeAll {
                $filePath = 'notes'
                $fileName = 'hello.txt'
                $commitMessage = 'Commit Message'
                $content = 'This is the content for test.txt'
                $branchName = 'master'
                $committerName = 'John Doe'
                $committerEmail = 'john.doe@testdomain.com'
                $authorName = 'Jane Doe'
                $authorEmail = 'jane.doe@testdomain.com'

                $setGitHubContentParms = @{
                    Path = "$filePath/$fileName"
                    CommitMessage = $commitMessage
                    Branch = $branchName
                    Content = $content
                    Uri = $repo.svn_url
                    CommitterName = $committerName
                    CommitterEmail = $committerEmail
                    authorName = $authorName
                    authorEmail = $authorEmail
                }

                $result = Set-GitHubContent @setGitHubContentParms -PassThru
            }

            It 'Should have the expected type and additional properties' {
                $result.PSObject.TypeNames[0] | Should -Be 'GitHub.Content'
                $result.content.name | Should -Be $fileName
                $result.content.path | Should -Be "$filePath/$fileName"
                $result.content.url | Should -Be ("https://api.github.com/repos/$($script:ownerName)" +
                    "/$repoName/contents/$filePath/$($fileName)?ref=$BranchName")
                $result.commit.author.name | Should -Be $authorName
                $result.commit.author.email | Should -Be $authorEmail
                $result.commit.committer.name | Should -Be $committerName
                $result.commit.committer.email | Should -Be $committerEmail
                $result.commit.message | Should -Be $commitMessage
            }

            It 'Should have written the correct content' {
                $getGitHubContentParms = @{
                    Path = "$filePath/$fileName"
                    Uri = $repo.svn_url
                    MediaType = 'Raw'
                    ResultAsString = $true
                }

                $writtenContent = Get-GitHubContent @getGitHubContentParms

                $content | Should -Be $writtenContent
            }

            It 'Should support pipeline input' {
                $getGitHubContentParms = @{
                    Path = "$filePath/$fileName"
                    Uri = $repo.svn_url
                }

                $writtenContent = Get-GitHubContent @getGitHubContentParms

                $setGitHubContentParms = @{
                    CommitMessage = $commitMessage
                    Content = $content
                    CommitterName = $committerName
                    CommitterEmail = $committerEmail
                    authorName = $authorName
                    authorEmail = $authorEmail
                }

                { $writtenContent | Set-GitHubContent @setGitHubContentParms -WhatIf } | Should -Not -Throw
            }
        }

        Context 'When overwriting file content' {
            BeforeAll {
                $filePath = 'notes'
                $fileName = 'hello.txt'
                $commitMessage = 'Commit Message 2'
                $content = 'This is the new content for test.txt'
                $branchName = 'master'
                $committerName = 'John Doe'
                $committerEmail = 'john.doe@testdomain.com'
                $authorName = 'Jane Doe'
                $authorEmail = 'jane.doe@testdomain.com'

                $setGitHubContentParms = @{
                    Path = "$filePath/$fileName"
                    CommitMessage = $commitMessage
                    BranchName = $branchName
                    Content = $content
                    Uri = $repo.svn_url
                    CommitterName = $committerName
                    CommitterEmail = $committerEmail
                    authorName = $authorName
                    authorEmail = $authorEmail
                }

                $result = Set-GitHubContent @setGitHubContentParms -PassThru
            }

            It 'Should have the expected type and additional properties' {
                $result.PSObject.TypeNames[0] | Should -Be 'GitHub.Content'
                $result.content.name | Should -Be $fileName
                $result.content.path | Should -Be "$filePath/$fileName"
                $result.content.url | Should -Be ("https://api.github.com/repos/$($script:ownerName)" +
                    "/$repoName/contents/$filePath/$($fileName)?ref=$BranchName")
                $result.commit.author.name | Should -Be $authorName
                $result.commit.author.email | Should -Be $authorEmail
                $result.commit.committer.name | Should -Be $committerName
                $result.commit.committer.email | Should -Be $committerEmail
                $result.commit.message | Should -Be $commitMessage
            }

            It 'Should have written the correct content' {
                $getGitHubContentParms = @{
                    Path = "$filePath/$fileName"
                    Uri = $repo.svn_url
                    MediaType = 'Raw'
                    ResultAsString = $true
                }

                $writtenContent = Get-GitHubContent @getGitHubContentParms

                $content | Should -Be $writtenContent
            }
        }

        Context 'When Specifying only one Committer parameter' {
            $setGitHubContentParms = @{
                Path = "$filePath/$fileName"
                CommitMessage = $commitMessage
                BranchName = $branchName
                Content = $content
                Uri = $repo.svn_url
                CommitterName = $committerName
            }

            It 'Shoud throw the correct exception' {
                $errorMessage = 'Both CommiterName and CommitterEmail need to be specified.'
                { Set-GitHubContent @setGitHubContentParms } | Should -Throw $errorMessage
            }
        }

        Context 'When Specifying only one Author parameter' {
            $setGitHubContentParms = @{
                Path = "$filePath/$fileName"
                Uri = $repo.svn_url
                CommitMessage = $commitMessage
                BranchName = $branchName
                Content = $content
                AuthorName = $authorName
            }

            It 'Shoud throw the correct exception' {
                $errorMessage = 'Both AuthorName and AuthorEmail need to be specified.'
                { Set-GitHubContent @setGitHubContentParms } | Should -Throw $errorMessage
            }
        }

        Context 'When Invoke-GHRestMethod returns an unexpected error' {
            It 'Should throw' {
                $setGitHubContentParms = @{
                    Path = "$filePath/$fileName"
                    OwnerName = $script:ownerName
                    RepositoryName = 'IncorrectRepositoryName'
                    BranchName = $branchName
                    CommitMessage = $commitMessage
                    Content = $content
                }

                { Set-GitHubContent @setGitHubContentParms } | Should -Throw
            }
        }

        AfterAll {
            Remove-GitHubRepository -Uri $repo.svn_url -Force
        }
    }
}
finally
{
    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
    }
}