Public/Tests/New-PesterCustomTests.Tests.ps1

BeforeAll {
    $env:TCS_CONFIG_ROOT = Join-Path -Path $TestDrive -ChildPath 'config'
    $env:TCS_SKIP_UPDATE_CHECK = '1'
    $env:TCS_TELEMETRY_OPTOUT = '1'
    $ModuleRoot = Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent
    Import-Module -Name (Join-Path -Path $ModuleRoot -ChildPath 'tcs.utils.psd1') -Force
}

AfterAll {
    Remove-Module -Name tcs.utils -Force -ErrorAction SilentlyContinue
}

Describe 'New-PesterCustomTests' {
    BeforeAll {
        function Get-ParseErrorCount {
            param([string]$Path)
            $tokens = $null
            $errors = $null
            $null = [System.Management.Automation.Language.Parser]::ParseFile($Path, [ref]$tokens, [ref]$errors)
            @($errors).Count
        }
    }

    BeforeEach {
        # A generated module with one public and one private function
        $Target = Join-Path -Path $TestDrive -ChildPath ([guid]::NewGuid().ToString('N'))
        $null = New-Item -Path $Target -ItemType Directory
        New-ModuleCustomTemplate -Names 'gen.tests' -Path $Target
        $Generated = Join-Path -Path $Target -ChildPath 'gen.tests'
        $PublicFolder = Join-Path -Path $Generated -ChildPath 'Public'
        $PrivateFolder = Join-Path -Path $Generated -ChildPath 'Private'
        New-FunctionTemplate -Name 'Get-Widget' -Path $PublicFolder
        New-FunctionTemplate -Name 'Get-Helper' -Path $PrivateFolder -ExcludeTelemetryCollection
        $Psd1 = Join-Path -Path $Generated -ChildPath 'gen.tests.psd1'
        (Get-Content -Path $Psd1 -Raw).Replace('FunctionsToExport = @()', "FunctionsToExport = @('Get-Widget')") | Set-Content -Path $Psd1 -NoNewline
    }

    It 'Creates a test per function in the default Tests folder' {
        New-PesterCustomTests -Source $PublicFolder
        $testFile = Join-Path -Path $PublicFolder -ChildPath 'Tests/Get-Widget.Tests.ps1'
        Test-Path -Path $testFile | Should -BeTrue
        Get-ParseErrorCount -Path $testFile | Should -Be 0
    }

    It 'Uses the module-import header and no per-file PSScriptAnalyzer block' {
        New-PesterCustomTests -Source $PublicFolder
        $content = Get-Content -Path (Join-Path -Path $PublicFolder -ChildPath 'Tests/Get-Widget.Tests.ps1') -Raw
        $content | Should -Match ([regex]::Escape('Import-Module -Name (Join-Path -Path $ModuleRoot -ChildPath "$ModuleName.psd1") -Force'))
        $content | Should -Match ([regex]::Escape("`$env:TCS_TELEMETRY_OPTOUT = '1'"))
        $content | Should -Match "Describe 'Get-Widget'"
        $content | Should -Not -Match 'PSSA'
        $content | Should -Not -Match 'PSScriptAnalyzer'
        $content | Should -Not -Match 'Invoke-Expression'
        $content | Should -Not -Match '###TEMPLATE'
    }

    It 'Does not create a PSScriptAnalyzerSettings.psd1 file' {
        New-PesterCustomTests -Source $PublicFolder
        Get-ChildItem -Path $Generated -Recurse -Filter 'PSScriptAnalyzerSettings.psd1' | Should -BeNullOrEmpty
    }

    It 'Does not generate tests for existing test files' {
        New-PesterCustomTests -Source $PublicFolder
        New-PesterCustomTests -Source (Join-Path -Path $PublicFolder -ChildPath 'Tests') -WarningAction SilentlyContinue
        Get-ChildItem -Path $Generated -Recurse -Filter '*.Tests.Tests.ps1' | Should -BeNullOrEmpty
    }

    It 'Uses a private-function test for files in a Private folder' {
        New-PesterCustomTests -Source (Join-Path -Path $PrivateFolder -ChildPath 'Get-Helper.ps1')
        $content = Get-Content -Path (Join-Path -Path $PrivateFolder -ChildPath 'Tests/Get-Helper.Tests.ps1') -Raw
        $content | Should -Match 'InModuleScope'
    }

    It 'Writes to a custom destination and accepts piped files' {
        $destination = Join-Path -Path $Target -ChildPath 'custom-tests'
        Get-ChildItem -Path $PublicFolder -Filter '*.ps1' | New-PesterCustomTests -Destination $destination
        Test-Path -Path (Join-Path -Path $destination -ChildPath 'Get-Widget.Tests.ps1') | Should -BeTrue
    }

    It 'Keeps existing test files unless -Force is used' {
        $testFile = Join-Path -Path $PublicFolder -ChildPath 'Tests/Get-Widget.Tests.ps1'
        Set-Content -Path $testFile -Value '# my tests'
        New-PesterCustomTests -Source $PublicFolder -WarningVariable pesterWarnings -WarningAction SilentlyContinue
        (Get-Content -Path $testFile -Raw).Trim() | Should -Be '# my tests'
        $pesterWarnings | Should -Not -BeNullOrEmpty
        New-PesterCustomTests -Source $PublicFolder -Force
        Get-Content -Path $testFile -Raw | Should -Match "Describe 'Get-Widget'"
    }

    It 'Writes nothing to the pipeline' {
        New-PesterCustomTests -Source $PublicFolder | Should -BeNullOrEmpty
    }

    It 'Writes an error for a file that is not a .ps1' {
        $textFile = Join-Path -Path $Target -ChildPath 'notes.txt'
        Set-Content -Path $textFile -Value 'x'
        New-PesterCustomTests -Source $textFile -ErrorVariable pesterErrors -ErrorAction SilentlyContinue
        $pesterErrors | Should -Not -BeNullOrEmpty
    }

    It 'Creates nothing with -WhatIf' {
        New-PesterCustomTests -Source $PublicFolder -WhatIf
        Test-Path -Path (Join-Path -Path $PublicFolder -ChildPath 'Tests/Get-Widget.Tests.ps1') | Should -BeFalse
    }

    It 'Generates tests that pass against the generated module' {
        New-PesterCustomTests -Source $PublicFolder
        New-PesterCustomTests -Source $PrivateFolder
        # Run in a separate process with the same PowerShell engine and Pester version
        $engine = (Get-Process -Id $PID).Path
        $pesterVersion = (Get-Module -Name Pester).Version.ToString()
        $command = "`$ProgressPreference = 'SilentlyContinue'; Import-Module Pester -RequiredVersion $pesterVersion; " +
        "`$r = Invoke-Pester -Path '$Generated' -PassThru -Output None; '{0}/{1}' -f `$r.PassedCount, `$r.FailedCount"
        $result = & $engine -NoProfile -NonInteractive -Command $command
        $result | Select-Object -Last 1 | Should -Be '3/0'
    }
}