Tests/00-PowershellModule.Tests.ps1

param (
    #Specify an alternate location for the Powershell Module. This is useful when testing a build in another directory
    [string]$ModulePath = (Get-Location)
)

#if we are in the "Tests" directory and there is a PSD file below this one, change to the module directory so relative paths work correctly.
$currentDir = Get-Location
if (
    (Test-Path $currentDir -PathType Container) -and
    $currentDir -match 'Tests$' -and
    (Get-Item (join-path ".." "*.psd1") | where name -notmatch '\.(depend|requirements)\.psd1$')
) {
    $ModulePath = (split-path $modulepath)
}

#If an alternate module root was specified, set that to our running directory.
if ($ModulePath -ne (get-location).path) {Push-Location $ModulePath}

#Find the module manifest. Get-ChildItem's last item is the one closest to the root directory. #FIXME: Do this in a safer manner
try {
    $moduleManifestFile = Get-ChildItem -File -Recurse *.psd1 -ErrorAction Stop | where name -notmatch '(depend|requirements)\.psd1$'| Select-Object -last 1
    $SCRIPT:moduleDirectory = $moduleManifestFile.directory
} catch {
    throw "Did not detect any module manifests in $ModulePath. Did you run 'Invoke-Build Build' first?"
}
Describe 'Powershell Module' {
    $ModuleName = $ModuleManifestFile.basename
    Context ($ModuleName) {
        It 'Has a valid Module Manifest' {
            if ($PSEdition -eq 'Core' -or $PSVersionTable.PSVersion -ge [Version]"5.1") {
                $Script:Manifest = Test-ModuleManifest $ModuleManifestFile
            } else {
                #Copy the Module Manifest to a temp file for testing. This fixes a bug where
                #Test-ModuleManifest caches the first result, thus not catching changes if subsequent tests are run
                $TempModuleManifestPath = [System.IO.Path]::GetTempFileName() + '.psd1'
                copy-item $ModuleManifestPath $TempModuleManifestPath
                $Script:Manifest = Test-ModuleManifest $TempModuleManifestPath
                remove-item $TempModuleManifestPath -verbose:$false
            }
        }

        It 'Has a valid root module' {
            Test-Path $Manifest.RootModule -Type Leaf | Should Be $true
        }


        It 'Has a valid folder structure (ModuleName\Manifest or ModuleName\Version\Manifest)' {
            $moduleDirectoryErrorMessage = "Module directory structure doesn't match either $ModuleName or $moduleName\$($Manifest.Version)"
            $ModuleManifestDirectory = $ModuleManifestFile.directory
            switch ($ModuleManifestDirectory.basename) {
                $ModuleName {$true}
                $Manifest.Version.toString() {
                    if ($ModuleManifestDirectory.parent -match $ModuleName) {$true} else {throw $moduleDirectoryErrorMessage}
                }
                default {throw $moduleDirectoryErrorMessage}
            }
        }

        It 'Has a valid Description' {
            $Manifest.Description | Should Not BeNullOrEmpty
        }

        It 'Has a valid GUID' {
            [Guid]$Manifest.Guid | Should BeOfType 'System.GUID'
        }

        It 'Has a valid Copyright' {
            $Manifest.Copyright | Should Not BeNullOrEmpty
        }

        It 'Exports all public functions' {
            $FunctionFiles = Get-ChildItem Public -Filter *.ps1
            $FunctionNames = $FunctionFiles.basename | ForEach-Object {$_ -replace '-', "-$($Manifest.Prefix)"}
            $ExFunctions = $Manifest.ExportedFunctions.Values.Name
            if ($ExFunctions -eq '*') {write-warning "Manifest has * for functions. You should individually specify your public functions prior to deployment for better discoverability"}
            if ($functionNames) {
                foreach ($FunctionName in $FunctionNames) {
                    $ExFunctions -contains $FunctionName | Should Be $true
                }
            }
        }

        It 'Has at least 1 exported command' {
            $Script:Manifest.exportedcommands.count | Should BeGreaterThan 0
        }
        It 'Can be imported as a module successfully' {
            #Make sure an existing module isn't present
            Remove-Module $moduleManifestFile.basename -ErrorAction SilentlyContinue
            $SCRIPT:BuildOutputModule = Import-Module $moduleManifestFile -PassThru -verbose:$false -erroraction stop
            $BuildOutputModule.Name | Should Be $ModuleName
            $BuildOutputModule | Should BeOfType System.Management.Automation.PSModuleInfo
        }
        It 'Can be removed as a module' {
            $BuildOutputModule | Remove-Module -erroraction stop | Should BeNullOrEmpty
        }

    }
}
Describe 'Powershell Gallery Readiness (PSScriptAnalyzer)' {
    $results = Invoke-ScriptAnalyzer -Path $ModuleManifestFile.directory -Recurse -Setting PSGallery -Severity Error
    It 'PSScriptAnalyzer returns zero errors (warnings OK) using the Powershell Gallery ruleset' {
        if ($results) {write-warning ($results | Format-Table -autosize | out-string)}
        $results.Count | Should Be 0
    }
}


#Return to where we started
Pop-Location