TestingHelper.psm1


Write-Host "Loading TestingHelper ..." -ForegroundColor DarkCyan

Set-Variable -Name TestRunFolderName -Value "TestRunFolder" 
Set-Variable -Name TEST_FUNCTION_NAME_PATTERN -Value "Test-*" 

function Get-TestingModuleName {
    [CmdletBinding()]
    param (
        [parameter(Mandatory)] [string] $TargetModule
    )
    
    return ($TargetModule + "Test") 
}

function Get-TestingFunctionPrefix_Deprecated ([string] $TestingModuleName) { return ($TestingModuleName + "_*") }

function Trace-Message {
    [CmdletBinding()]
    param (
        [Parameter(Position = 1)]
        [string]
        $Message
    )

    Write-Verbose -Message $Message
}

function Test-Assert {
    [CmdletBinding()]
    [Alias("Assert")]
    param (
        [Parameter(Mandatory)] [bool] $Condition,
        [Parameter()][bool] $Expected = $true,
        [Parameter()][string]$Comment = "No Comment"
    )
    
    Write-Verbose -Message "Assert -Condition $Condition -Expected $Expected - $Comment"
    if ($Condition -ne $Expected) {
        throw "Assertion - Found [ $Condition ] Expected [ $Expected ] - $Comment"
    }
    else {
        #Write-Host "." -NoNewline -ForegroundColor DarkMagenta
        Write-AssertionDot -Color DarkMagenta
    }
}

function Write-AssertionDot {
    [CmdletBinding()]
    param ( 
        [Parameter()] [string] $Color
    )
    Write-Host "." -NoNewline -ForegroundColor $Color
}

function Write-AssertionSectionEnd{
    Write-AssertionDot -Color Yellow
}

function Start-TestingFunction {
    [CmdletBinding()]
    param (
        [parameter(Mandatory, ValueFromPipeline,ParameterSetName="FuncInfo")][System.Management.Automation.FunctionInfo] $FunctionInfo,
        [Parameter(Mandatory, ParameterSetName="FuncName")] [string] $FunctionName,
        [Parameter()] [switch] $ShowTestErrors
    )

    Process {

        if ($ShowTestErrors) {
            $ErrorShow = 'Continue'
        }
        else {
            $ErrorShow = 'SilentlyContinue'
        }

        if ($FunctionInfo) {
            $FunctionName = $FunctionInfo.Name
        }
        Write-Verbose -Message "Running [ $FunctionName ]"
    
        $local = Push-TestingFolder -Path $FunctionName
    
        try {
            Write-Host "$FunctionName ... [" -NoNewline -ForegroundColor DarkCyan
            & $FunctionName -ErrorAction $ErrorShow
            Write-Host "] "  -NoNewline -ForegroundColor DarkCyan 
            Write-Host "PASS"  -ForegroundColor DarkYellow 
        }
        catch {
    
            if ($_.Exception.Message -eq "SKIP_TEST") {
                Write-Host "] "  -NoNewline -ForegroundColor DarkCyan 
                Write-Host "Skip"  -ForegroundColor Magenta 
                
            }elseif ($_.Exception.Message -eq "NOT_IMPLEMENTED") {
                Write-Host "] "  -NoNewline -ForegroundColor DarkCyan 
                Write-Host "NotImplemented"  -ForegroundColor Red 
                
            } else {
                Write-Host "x"  -NoNewline -ForegroundColor Red 
                Write-Host "] "  -NoNewline -ForegroundColor DarkCyan 
                Write-Host "Failed"  -ForegroundColor Red 
                
                if ($ShowTestErrors) {
                    $_
                }
            }
        }
        finally {
            $local | Pop-TestingFolder -Force
        }
    }
}

function Test-Module {
    [CmdletBinding()] 
    param (
        [Parameter(Mandatory, ValueFromPipelineByPropertyName,Position = 0)] [string] $Name,
        [Parameter( Position = 1)] [string] $TestName,
        [Parameter()] [switch] $ShowTestErrors
    )

    process {

        Write-Verbose "Running tests for Module [ $Name ] functions [ $TestName ] "

        $local = Push-TestingFolder

        try {

            Remove-Module -Name "$Name*"
            
            Import-TestingModule -TargetModule $Name -Force

            $TestingModuleName = Get-TestingModuleName -TargetModule $Name

            $functionsTest = @()

            #Use standar testing fucntions prfix
            if ( $TestName) {
                # Filter based on TestFunction names
                $ShowTestErrors = $true
                $functionsTest += Get-Command -Name $TestName -Module $TestingModuleName 
            }
            else {
                # Legacy
                $TestName = Get-TestingFunctionPrefix_Deprecated -TestingModuleName ($TestingModuleName )
                $functionsTest += Get-Command -Name $TestName -Module $TestingModuleName 
                
                # New function name Test-*
                $functionsTest += Get-Command -Name $TEST_FUNCTION_NAME_PATTERN -Module $TestingModuleName 
            } 
            
            $functionsTest | Start-TestingFunction -ShowTestErrors:$ShowTestErrors

            Remove-Module -Name $TestingModuleName -Force

        }
        finally {
            $local | Pop-TestingFolder
        }
    }
}

function Import-TestingModule {
    [CmdletBinding()] 
    param (
        [Parameter(Mandatory, ParameterSetName = "TestingModule")][string] $Name,
        [Parameter(Mandatory, ParameterSetName = "TargetModule" )][string] $TargetModule,
        [switch] $Force
    )

    if ($Name) {
        $moduleName = $Name
    }

    if ($TargetModule) {
  
        Import-TargetModule -Name $TargetModule -Force

        $modulePath = (Get-Module -Name $TargetModule).Path
    
        $moduleName = Join-Path -Path (Split-Path -Path $modulePath -Parent) -ChildPath (Get-TestingModuleName -TargetModule $TargetModule)

        if (-not (Test-Path -Path $moduleName)) {
            Write-Warning -Message "TestingModule for module [ $TargetModule ] not found at [ $moduleName ]"
            return
        }
    }
    
    Import-Module -Name $moduleName -Force:$Force -Global
}

function Import-TargetModule {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory)][string] $Name,
        [switch] $Force
    )

    Import-Module -Name $Name -Force:$Force -Global
}

function Start-TestModule {
    [CmdletBinding()] 
    param (
        [Parameter(Mandatory, Position = 0)][string] $TestModuleName,
        [Parameter()][string] $Prefix,
        [Parameter()][string] $ModuleName
    )

    if ($ModuleName) {
        Import-Module -Name $ModuleName
    }

    Write-Host "Running Test Module [ $TestModuleName ] ..." -ForegroundColor DarkYellow
    Import-TestingModule -Name $TestModuleName

    if ($Prefix) {
        Write-Host "Filtering functions by Prefix [ $Prefix ] ..." -ForegroundColor DarkYellow
        $functions = Get-Command -Module $TestModuleName -Name $Prefix*
    }
    else { 
        $functions = Get-Command -Module $TestModuleName 
    }

    $functions | ForEach-Object {
        Start-TestingFunction -FunctionName $_.Name
    }
}

function Assert-NotImplemented {

    throw "NOT_IMPLEMENTED"
}

function Assert-SkipTest{
    throw "SKIP_TEST"
}

function Assert-IsTrue {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory, Position = 0)] [bool] $Condition,
        [Parameter()][string] $Comment
    )
    Assert -Condition $Condition -Expected $true -Comment:$Comment
}

function Assert-IsFalse {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory, Position = 0)] [bool] $Condition,
        [Parameter()][string] $Comment
    )
    Assert -Condition $Condition -Expected $false -Comment:$Comment
}

function Assert-IsNotNull {
    [CmdletBinding()]
    param (
        $Object,
        $Comment
    )
    if ($Object) {
        $isNull = $false
    }
    else {
        $isNull = $true
    }
    Assert-IsFalse -Condition $isNull -Comment ("Object is null -" + $Comment)
}

function Assert-IsNull {
    [CmdletBinding()]
    param (
        [parameter(Position=0,ValueFromPipeline)] $Object
    )

    if ($Object) {
        $isNull = $false
    }
    else {
        $isNull = $true
    }
    Assert-IsTrue -Condition $isNull -Comment "IsNull"
}
function Assert-AreEqual {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory)] [object] $Expected,
        [Parameter(Mandatory)] [object] $Presented,
        [Parameter()] [string] $Comment

    )

    Assert-IsTrue -Condition ($Expected -eq $Presented) -Comment ("Object are not Equal : Expected [ $Expected ] and presented [ $Presented] - " + $Comment)
}

function Assert-AreEqualSecureString {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory)] [string] $Expected,
        [Parameter(Mandatory)] [securestring] $Presented,
        [Parameter()] [string] $Comment

    )

    $pss = $Presented | ConvertFrom-SecureString -AsPlainText

    Assert-AreEqual -Expected $Expected -Presented $pss -Comment ("SecureString - " + $Comment)
}

function Assert-AreEqualPath {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory)] [object] $Expected,
        [Parameter(Mandatory)] [object] $Presented,
        [Parameter()] [string] $Comment

    )

    $ex = &{ if ($Expected  | Test-Path) { $Expected  | Convert-Path} else {$Expected} }
    $pr = &{ if ($Presented | Test-Path) { $Presented | Convert-Path} else {$Presented}}

    Assert-AreEqual -Expected $ex -Presented $pr -Comment ("Path not equal - " + $Comment)
}

function Assert-AreNotEqual {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory)] [object] $Expected,
        [Parameter(Mandatory)] [object] $Presented,
        [Parameter()] [string] $Comment
    )

    Assert-IsFalse -Condition ($Expected -eq $Presented) -Comment ("Object are Equal : Expecte [ $Expected ] and presented [ $Presented] - " + $Comment)

}

function Assert-ItemExist {
    param(
        [string] $Path
    )
    try {
        Assert-IsTrue -Condition ($Path | Test-Path)
    }
    catch {
        throw "Item does not exist [ $Path ]"
    }
}

function Assert-ItemNotExist {
    param(
        [string] $Path
    )
    try {
        Assert-IsFalse -Condition ($Path | Test-Path)
    }
    catch {
        throw "Item does not exist [ $Path ]"
    }
}

function Assert-IsGuid{
    param(
        [string] $Presented
    )
    try {
        Assert-IsNotNull -Object (New-Object -TypeName System.Guid -ArgumentList $Presented)
    }
    catch {
        throw "String is not a valid Guid"
    }
}

function Assert-Count {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory)] [int] $Expected,
        [Parameter(Mandatory)] [object] $Presented
    )
    Assert-IsTrue -Condition ($Presented.Count -eq $Expected) -Comment ("Count Expected [{0}] and Presneted [{1}]" -f $Expected,$Presented.Length)
}

function Assert-CountObjects {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory)] [int] $Expected,
        [Parameter(Mandatory)] [object] $Presented
    )

    $presentedCount = $Presented.Count
    Assert-IsTrue -Condition ($presentedCount -eq $Expected) -Comment ("Count Expected [{0}] and Presneted [{1}]" -f $Expected,$presentedCount)
}

function Assert-FilesAreEqual{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory)] [object] $Expected,
        [Parameter(Mandatory)] [object] $Presented,
        [Parameter()] [string] $Comment
    )

    $ex = $Expected | Get-FileHash
    $pr = $Presented | Get-FileHash

    Assert-AreEqual -Expected $ex.Hash -Presented $pr.Hash -Comment ("Files not equal - " + $Comment)
}
function Assert-FilesAreNotEqual{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory)] [object] $Expected,
        [Parameter(Mandatory)] [object] $Presented,
        [Parameter()] [string] $Comment
    )

    $ex = $Expected | Get-FileHash
    $pr = $Presented | Get-FileHash

    Assert-AreNotEqual -Expected $ex.Hash -Presented $pr.Hash -Comment ("Files equal - " + $Comment)
}

function Remove-TestingFolder {
    param(
        [Parameter(Mandatory, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)][string] $Path,
        [switch] $Force
    )

    if (-not ($Path | Test-Path)) {
        return
    }

    #Recursive call
    $ChildTestFolder = Join-Path -Path $Path -ChildPath $TestRunFolderName
    if (Test-Path -Path $ChildTestFolder) {
        Remove-TestingFolder -Path $ChildTestFolder
    }

    # So far only remove content for TestRunFolder named folders
    if (($Path | Split-Path -Leaf) -ne $TestRunFolderName) {
        if (-not $Force) {
            return
        }
    }

    if (Test-Path -Path $Path) {
        $local = Get-Item -Path $Path
        $local | Get-ChildItem -File | Remove-Item -Force
        $local | Get-ChildItem -Directory |  Remove-TestingFolder -Force
        $local | Remove-Item -Force -Recurse
    }
}

function Push-TestingFolder {
    [CmdletBinding()]
    param (
        [Parameter( ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)][string] $Path
    )

    $originalPath = Resolve-Path .

    if ($Path) {
        $testFolderName = $Path
    }
    else {
        $testFolderName = Join-Path -Path (GetRooTestingFolderPath) -ChildPath  $TestRunFolderName
    }
    New-TestingFolder $testFolderName
    $TestRunPath = Resolve-Path -Path $testFolderName

    if (Test-Path -Path $TestRunPath) { Remove-Testingfolder -Path $TestRunPath }

    New-Item -Path $TestRunPath  -ItemType "directory" -Force | Out-Null

    Set-Location -Path $TestRunPath

    return $originalPath
}

function Pop-TestingFolder {
    [CmdletBinding()]
    param (
        [Parameter( ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)][string] $Path,
        [switch] $Force
    )

    $local = Get-Location | Resolve-Path
    $localLeaf = $local | Split-Path -Leaf

    Set-Location -Path $Path

    if (($localLeaf -eq $TestRunFolderName) -or $Force) {
        Remove-TestingFolder -Path $local
    }
}

function New-TestingFolder {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory)] [string] $Path
    )

    # Need to consolidate as mkdir behaves diferent on PC or Mac
    $result = New-Item -ItemType Directory -Path $Path 

    Write-Verbose -Message "Created Diretory [ $result ] "
}

function GetRooTestingFolderPath{
    $rd = Get-Date -Format yyMMdd
    $path = Join-Path -Path "Temp:" -ChildPath ("Posh_Testing_" + $rd)
    return $path
}