TestingHelper.psm1


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

Set-Variable -Name TestRunFolderName -Value "TestRunFolder" 

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

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

function Trace-Message {
    [CmdletBinding()]
    param (
        [Parameter(ValueFromPipeline, 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-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
    )

    begin{
        $ret = @{
            # Pass = 0
            # Failed = 0
            # SkippedCount = 0
            # NotImplementedCount = 0
            FailedTests = @()
            FailedTestsErrors = @()
            NotImplementedTests = @()
            SkippedTests = @()
        }

    }

    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
            $null = & $FunctionName -ErrorAction $ErrorShow
            Write-Host "] "  -NoNewline -ForegroundColor DarkCyan 
            Write-Host "PASS"  -ForegroundColor DarkYellow 
            $ret.Pass++
        }
        catch {
    
            Write-Host "x"  -NoNewline -ForegroundColor Red 
            Write-Host "] "  -NoNewline -ForegroundColor DarkCyan 

            if ($_.Exception.Message -eq "SKIP_TEST") {
                Write-Host "Skip"  -ForegroundColor Magenta 
                $ret.SkippedTests += $FunctionName
                
            }elseif ($_.Exception.Message -eq "NOT_IMPLEMENTED") {
                Write-Host "NotImplemented"  -ForegroundColor Red 
                $ret.NotImplementedTests += $FunctionName
                
            } else {
                Write-Host "Failed"  -ForegroundColor Red 
                $ret.FailedTests += $FunctionName
                
                $ret.FailedTestsErrors += @($functionName,$_)
                
                if ($ShowTestErrors) {
                    @($functionName,$_)
                } 
            }
        }
        finally {
            $local | Pop-TestingFolder -Force
        }
    }

    end{

        $Global:FailedTestsErrors = $FailedTestsErrors

        if($ret.FailedTests.count -eq 0)         { $ret.Remove("FailedTests")}         else {$ret.Failed = $ret.FailedTests.Count}
        if($ret.SkippedTests.count -eq 0)        { $ret.Remove("SkippedTests")}        else {$ret.Skipped = $ret.SkippedTests.Count}
        if($ret.NotImplementedTests.count -eq 0) { $ret.Remove("NotImplementedTests")} else {$ret.NotImplemented = $ret.NotImplementedTests.Count}

        $Global:FailedTestsErrors = $ret.FailedTestsErrors

        if($ret.FailedTestsErrors.count -eq 0) { $ret.Remove("FailedTestsErrors")}

        return [PSCustomObject] $ret
    }
}

function Out-SingleResultData($Name,$Value, $Color){
    $testColor = $Value -eq 0 ? "DarkCyan" : $Color

    "{0}" -f $Name | Write-Host  -ForegroundColor $testColor -NoNewline 
    "["     -f $Name | Write-Host  -ForegroundColor DarkCyan -NoNewline 
    $Value            | Write-Host  -ForegroundColor $testColor -NoNewline 
    "] "     -f $Name | Write-Host  -ForegroundColor DarkCyan -NoNewline 
}

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

    process {
        write-host
        "[ {0} ] Running tests functions [ {1} ] " -f $Name,([string]::IsNullOrWhiteSpace($TestName) ? "*" : $TestName) | Write-Host -ForegroundColor Green

        $local = Push-TestingFolder

        try {

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

            $TestingModuleName = Get-TestingModuleName -TargetModule $Name

            $functionsTest = @()

            # Check if specific scoped to specific testing functions
            if ( $TestName) {
                # Filter based on TestFunction names
                $ShowTestErrors = $true
                $functionsTestName = $TestName
            }
            else {
                # No function scope so search for all testing functions in module based on prefix
                $functionsTestName = Get-TestingFunctionPrefix -TestingModuleName ($TestingModuleName )
            } 
            
            # Get list of testing fucntions to run
            $functionsTest += Get-Command -Name $functionsTestName -Module $TestingModuleName -ErrorAction SilentlyContinue
            
            # Run tests and gather result
            $start = Get-Date
            $result = $functionsTest | Start-TestingFunction -ShowTestErrors:$ShowTestErrors
            $time = ($start | New-TimeSpan ).ToString("hh\:mm\:ss\:FFFF")
            
            # Add extra info to result
            $result | Add-Member -NotePropertyName "Name" -NotePropertyValue $Name
            $result | Add-Member -NotePropertyName "TestModule" -NotePropertyValue $TestingModuleName
            $result | Add-Member -NotePropertyName "TestsName" -NotePropertyValue $functionsTestName
            $result | Add-Member -NotePropertyName "Tests" -NotePropertyValue $functionsTest.Length
            $result | Add-Member -NotePropertyName "Time" -NotePropertyValue $time

            # Display single line result
            Write-Host  -ForegroundColor DarkCyan 
            $TestingModuleName | Write-Host  -ForegroundColor Green -NoNewline
            " results - " | Write-Host  -ForegroundColor DarkCyan -NoNewline
            Out-SingleResultData -Name "Pass"           -Value $result.Pass           -Color "Yellow"
            Out-SingleResultData -Name "Failed"         -Value $result.Failed         -Color "Red"
            Out-SingleResultData -Name "Skipped"        -Value $result.Skipped        -Color "Yellow"
            Out-SingleResultData -Name "NotImplemented" -Value $result.NotImplemented -Color "Red"
            Write-Host  -ForegroundColor DarkCyan 

            # Displayy all results strucutre
            $result

            # Save result to global variable
            $global:ResultTestingHelper = $result

            # unload testing module
            Remove-Module -Name $TestingModuleName -Force
        }
        finally {
            $local | Pop-TestingFolder
        }
    }
}

function GetModuleManifest($Path){

    $localPath = $Path | Convert-Path

    $psdpath = Get-ChildItem -Path $localPath -Filter "*.psd1" -ErrorAction SilentlyContinue

    if($psdpath.count -ne 1){
        throw "No psd1 file found in path $localPath"
    }
    
    $manifest = Import-PowerShellDataFile -Path $psdpath.FullName

    $manifest.Path = $localPath
    $manifest.PsdPath = $psdpath.FullName
    $manifest.Name = $manifest.RootModule | Split-Path -leafbase


    return $manifest
}

function GetTestingModuleManifest($path){

    $name = $path | Split-Path -leafbase
    $testingModulename = Get-TestingModuleName -TargetModule $name
    $testingpath = $path | Join-Path -ChildPath $testingModulename

    $ret = GetModuleManifest -Path $testingpath

    return $ret
}


function Test-ModulelocalPSD1 {
    [CmdletBinding()] 
    param (
        [Parameter( Position = 1)] [string] $TestName,
        [Parameter( Position = 2)] [string] $Path = '.',
        [Parameter()] [switch] $ShowTestErrors
    )

    process {

        $manifest = GetModuleManifest -Path ($Path | Convert-Path)
        $testingmodulemanifest = GetTestingModuleManifest -path $manifest.Path
        $versionString = "{0} {1} {2}" -f $manifest.Name, $manifest.ModuleVersion, $manifest.PrivateData.PSData.Prerelease

        write-host
        "[ {0} ] Running tests from functions [ {1} ] " -f $versionString,([string]::IsNullOrWhiteSpace($TestName) ? "*" : $TestName) | Write-Host -ForegroundColor Green

        $local = Push-TestingFolder
  
        try {

            # Import Target Module
            Import-Module -Name $manifest.PsdPath -Force -Scope:Global
            
            # Load Testing Module
            Import-TestingModule -Name $testingmodulemanifest.path -Force

            $TestingModuleName = Get-TestingModuleName -TargetModule $manifest.Name

            $functionsTest = @()

            # Check if specific scoped to specific testing functions
            if ( $TestName) {
                # Filter based on TestFunction names
                $ShowTestErrors = $true
                $functionsTestName = $TestName
            }
            else {
                # No function scope so search for all testing functions in module based on prefix
                $functionsTestName = Get-TestingFunctionPrefix -TestingModuleName ($testingmodulemanifest.Name )
            } 
            
            # Get list of testing fucntions to run
            $functionsTest += Get-Command -Name $functionsTestName -Module $TestingModuleName -ErrorAction SilentlyContinue
            
            # Run tests and gather result
            $start = Get-Date
            $result = $functionsTest | Start-TestingFunction -ShowTestErrors:$ShowTestErrors
            $time = ($start | New-TimeSpan ).ToString("hh\:mm\:ss\:FFFF")
            
            # Add extra info to result
            $result | Add-Member -NotePropertyName "Name" -NotePropertyValue $Name
            $result | Add-Member -NotePropertyName "TestModule" -NotePropertyValue $TestingModuleName
            $result | Add-Member -NotePropertyName "TestsName" -NotePropertyValue $functionsTestName
            $result | Add-Member -NotePropertyName "Tests" -NotePropertyValue $functionsTest.Length
            $result | Add-Member -NotePropertyName "Time" -NotePropertyValue $time

            # Display single line result
            Write-Host  -ForegroundColor DarkCyan 
            $TestingModuleName | Write-Host  -ForegroundColor Green -NoNewline
            " results - " | Write-Host  -ForegroundColor DarkCyan -NoNewline
            Out-SingleResultData -Name "Pass"           -Value $result.Pass           -Color "Yellow"
            Out-SingleResultData -Name "Failed"         -Value $result.Failed         -Color "Red"
            Out-SingleResultData -Name "Skipped"        -Value $result.Skipped        -Color "Yellow"
            Out-SingleResultData -Name "NotImplemented" -Value $result.NotImplemented -Color "Red"
            Write-Host  -ForegroundColor DarkCyan 

            # Displayy all results strucutre
            $result

            # Save result to global variable
            $global:ResultTestingHelper = $result

            # unload testing module
            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,
        [Parameter()][string] $TargetModuleVersion,

        [switch] $Force
    )

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

    if ($TargetModule) {
  
        # check if module is already loaded
        $module = Get-Module -Name $TargetModule -ErrorAction SilentlyContinue
        if (-not $module) {
            "[Import-TestingModule] TargetModule {0} is not loaded" -f $TargetModule | Write-Verbose
            $module = Import-Module -Name $TargetModule -Force -PassThru
        } else {
            "[Import-TestingModule] TargetModule {0} is already loaded" -f $TargetModule | Write-Warning
        }

        #check TargetModuleVersion
        if ($TargetModuleVersion) {
            if ($module.Version -ne $TargetModuleVersion) {
                # Write-Warning -Message "TargetModule [ $TargetModule ] version [ $($module.Version) ] is not equal to TargetModuleVersion [ $TargetModuleVersion ]"
                "[Import-TestingModule] TargetModule {0} version {1} not matches {2}" -f $TargetModule,$module.Version,$TargetModuleVersion | Write-Warning

                return
            }
        }

        $modulePath = $module.Path
    
        $testingModulePathOrName = Join-Path -Path (Split-Path -Path $modulePath -Parent) -ChildPath (Get-TestingModuleName -TargetModule $TargetModule)

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

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

    if ($Manifest) {
        Get-Item -Path $manifestFile | Import-Module -force -Force:$Force -Global
        return
    }
    
    Import-Module -Name $Name -Force:$Force -Global -Passthru:$PassThru
}

# 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
    )

    Assert-IsFalse -Condition ($null -eq $Object) -Comment ("Object is null -" + $Comment)
}

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


    Assert-IsTrue -Condition ($null -eq $Object) -Comment ("Object not null -" + $Comment)
}

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-AreNotEqualPath {
    [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-AreNotEqual -Expected $ex -Presented $pr -Comment ("Path equal - " + $Comment)
}

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

    )

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

    Assert-Contains -Expected $ex -Presented $pr -Comment ("Path not contained - " + $Comment)
}

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

    )

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

    Assert-NotContains -Expected $ex -Presented $pr -Comment ("Path not contained - " + $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-AreEqualContent{
    [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}}

    $hashEx = Get-FileHash -Path $ex
    $hashPr = Get-FileHash -Path $pr

    Assert-AreEqual -Expected $hashEx -Presented $hashPr 
}

function Assert-AreNotEqualContent{
    [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}}

    $hashEx = Get-FileHash -Path $ex
    $hashPr = Get-FileHash -Path $pr

    Assert-AreNotEqual -Expected $hashEx -Presented $hashPr  
}

function Assert-ItemExist {
    param(
        [string] $Path
    )
    Assert-IsNotNull -Object $Path -Comment "[Assert-ItemExist] Path is empty"
    Assert-IsTrue -Condition ($Path | Test-Path)
}

function Assert-ItemNotExist {
    param(
        [string] $Path
        )
        
    Assert-IsNotNull -Object $Path -Comment "[Assert-ItemNotExist] Path is empty"
    Assert-IsFalse -Condition ($Path | Test-Path)
}

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

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

    )

    if (!$Presented) {
        Assert-IsTrue -Condition ($Expected -eq 0) -Comment ("Presented is null expected [{0}]- {1}" -f $Expected, $Comment)
    } else {
        Assert-IsTrue -Condition ($Presented.Count -eq $Expected) -Comment ("Count Expected [{0}] and Presented [{1}] - {2}" -f $Expected,$Presented.Count, $Comment)

    }
}

function Assert-CountTimes {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory)] [int] $Expected,
        [Parameter(Mandatory)] [string] $Pattern,
        [Parameter()] [string[]] $Presented,
        [Parameter()] [string] $Comment
    )

        if (!$Presented) {
        Assert-IsTrue -Condition ($Expected -eq 0) -Comment ("Presented is null expected [{0}]- {1}" -f $Expected, $Comment)
    } else {
        $iterations = $Presented | Where-Object {$_ -eq $pattern}
        Assert-IsTrue -Condition ($iterations.Count -eq $Expected) -Comment ("Count Expected [{0}] and Presented [{1}] - {2}" -f $Expected,$iterations.Count, $Comment)
    }
}

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

    Test-Assert -Condition (!([string]::IsNullOrEmpty($Expected)) -and ($Presented.Contains($Expected))) -Comment  ("[Assert-Contains] Expected[{0}] present on {1}" -f $Expected, $Presented)

}

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

    Assert -Condition ([string]::IsNullOrEmpty($Expected)) -Expected $false -Comment "[Assert-Contains] Expected can not be empty"

    Assert-IsTrue -Condition (!($Presented.Contains($Expected))) -Comment  ("[Assert-Contains] Expected[{0}] present on {1}" -f $Expected, $Presented)
}

function Assert-ContainedXOR{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory,ValueFromPipeline)] [string] $Expected,
        [Parameter(Mandatory)] [string[]] $PresentedA,
        [Parameter(Mandatory)] [string[]] $PresentedB,
        [Parameter()] [string] $Comment
    )

    process {
        $ga = $PresentedA.contains($Expected)
        $gb = $PresentedB.contains($Expected)
        
        Assert-IsTrue -Condition ( $ga -xor $gb) -Comment ("Assert-ContainedXOR [{0}]" -f ($Expected))
    }
}

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 Assert-FileContains{
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)][Object] $Path,
        [Parameter(Mandatory)][Object] $Pattern,
        [Parameter()] [string] $Comment

    )

    $SEL = Select-String -Path $Path -Pattern $Pattern

    Assert-IsTrue -Condition ($null -ne $SEL) -Comment ("Files contains - " + $Comment)
}

function Assert-StringIsNotNullOrEmpty {
    [CmdletBinding()]
    param (
        [parameter(Position=0,ValueFromPipeline)][string] $Presented,
        [Parameter()] [string] $Comment
    )

    Assert-IsFalse -Condition ([string]::IsNullOrEmpty($Presented))-Comment ("String not null or empty -" + $Comment)
}

function Assert-StringIsNullOrEmpty {
    [CmdletBinding()]
    param (
        [parameter(Position=0,ValueFromPipeline)][string] $Presented,
        [Parameter()] [string] $Comment
    )

    Assert-IsTrue -Condition ([string]::IsNullOrEmpty($Presented))-Comment ("String null or empty -" + $Comment)
}

function Assert-CollectionIsNotNullOrEmpty {
    [CmdletBinding()]
    param (
        [parameter(Position=0,ValueFromPipeline)][object] $Presented,
        [Parameter()] [string] $Comment
    )

    Test-Assert -Condition (($null -ne $presented) -and ($presented.Count -gt 0)) -Comment:$Comment
}

function Assert-CollectionIsNullOrEmpty {
    [CmdletBinding()]
    param (
        [parameter(Position=0,ValueFromPipeline)][object] $Presented,
        [Parameter()] [string] $Comment
    )

    Test-Assert -Condition (($null -eq $presented) -or ($presented.Count -eq 0)) -Comment:$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(ValueFromPipeline)] [string] $Path,
        [Parameter()] [string] $Name,
        [switch] $PassThru
    )

    if ($Path -and !$Name) {
        $finalPath = $Path
    } else {
        if ([string]::IsNullOrWhiteSpace($Name))    { $Name    = (New-Guid).ToString()}
        if ([string]::IsNullOrWhiteSpace($Path))    { $Path    = '.' }

        $finalPath = $Path | Join-Path -ChildPath $Name
    }
    
    # if ($Path -and $Name) {
    # $finalPath = $Path | Join-Path -ChildPath $Name
    # }
    
    # if (!$Path -and $Name) {
    # $finalPath = '.' | Join-Path -ChildPath $Name
    # }
    
    # if (!$Path -and !$Name) {
    # $finalPath = '.' | Join-Path -ChildPath (New-Guid).ToString()
    # }


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

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

    if ($PassThru) {
        return $result
    }
}

function New-TestingFile {
    param(
        [Parameter(ValueFromPipeline)][string]$Path,
        [Parameter()][string]$Name,
        [Parameter()][string]$Content,
        [switch] $Hidden,
        [switch] $PassThru
    )

    if ([string]::IsNullOrWhiteSpace($Name))    { $Name    = ("{0}.txt" -f (New-Guid).ToString()) }
    if ([string]::IsNullOrWhiteSpace($Path))    { $Path    = '.' }
    if ([string]::IsNullOrWhiteSpace($Content)) { $Content = "random content" }

    $file = New-Item -ItemType File -Path $Path -Name $Name -Value $Content -Force

    if ($Hidden) {
        $file.Attributes = $file.Attributes -bxor [System.IO.FileAttributes]::Hidden
    }

    if ($PassThru) {
        return $file
    }
}

function Remove-TestingFile {
    param(
        [Parameter(ValueFromPipeline)][string]$Path,
        [Parameter()][string]$Name,
        [Parameter()][string]$Content,
        [switch] $Hidden
    )
    
    if ([string]::IsNullOrWhiteSpace($Path))    { $Path    = '.' }
    
    $target = ([string]::IsNullOrWhiteSpace($Name)) ? $Path : ($Path | Join-Path -ChildPath $Name)

    Assert-ItemExist -Path $target

    (Get-Item -Force -Path $target).Attributes = 0

    Remove-Item -Path $target

    Assert-itemNotExist -Path $target
} 

# Return the path of the root testing folder
function GetRooTestingFolderPath{
    $random = (New-Guid).ToString().Substring(0,6) # add a random number on each run to avoid colisions
    $rd = Get-Date -Format yyMMdd
    $path = Join-Path -Path "Temp:" -ChildPath ("Posh_Testing_{0}_{1}" -f $rd,$random)
    return $path
}

#Modules
function New-Module {
<#
.Synopsis
   Created a Powershell module with BiT21 format.
#>

    [CmdletBinding()]
    [OutputType([System.IO.FileInfo])]
    Param
    (
        # Param1 help description
        [Parameter(Mandatory)][string]$Name,
        [Parameter(Mandatory)][string]$Description,
        [Parameter()][string]$Path,
        [Parameter()][switch]$AvoidModuleFile,
        [Parameter()][switch]$AvoidTestFile,
        [Parameter()][String]$AppendToModuleFile
    )    

    $AUTHOR = 'rulasg'
    $ModuleName = $Name

    if (!$Path) {
        $Path = '.'
    }

    $modulePath = Join-Path -Path $Path -ChildPath $Name

    if(Test-Path($modulePath)){
        throw "Folder already exists"
    } else {
       $null = New-Item -ItemType Directory -Name $modulePath
    }

    # Manifest
    $filename = "$ModuleName.psd1"
    
    New-ModuleManifest `
        -Path (Join-Path -Path $modulePath -ChildPath $filename) `
        -RootModule "$ModuleName.psm1" `
        -Author        $AUTHOR `
        -ModuleVersion '0.1' `
        -Description   $Description `
        #-CompanyName "rulasg" `
        #-Copyright "(c) 2021 rulasg. All rights reserved." `
        # -RequiredModules 'BaseSDK' `
        # -DefaultCommandPrefix $ModuleName

    # Module File
    if (-Not $AvoidModuleFile)
    {
        NewModulefile -Path $modulePath -ModuleName $ModuleName -Author $AUTHOR -Description $Description -Append $AppendToModuleFile
    }    

    # Testing module
    if (-Not $AvoidTestFile)
    {
        New-TestingModule -Path $modulePath -ModuleName $ModuleName
        New-TestingVsCodeLaunchJson -Path $modulePath -ModuleName $ModuleName
    }

    return $modulePath
}

function NewModulefile($Path, $ModuleName, $Author, $Description, $Append){
    $myString = 
@'
<#
.Synopsis
_XMODULE_
 
.Description
_DESCRIPTION_
 
.Notes
NAME : _XMODULE_.psm1*
AUTHOR: _AUTHOR_
 
CREATED: _CREATED_TIME_
#>
 
Write-Host "Loading _XMODULE_ ..." -ForegroundColor DarkCyan
'@

    $myString = $myString.Replace('_XMODULE_',$ModuleName)
    $myString = $myString.Replace('_DESCRIPTION_',$Description)
    $myString = $myString.Replace('_AUTHOR_',$AUTHOR)
    $myString = $myString.Replace('_CREATED_TIME_',(Get-Date).ToShortDateString());

    if ($Append) {
        $myString+=$Append
    }

    $myString |  Out-File -FilePath (Join-Path -Path $Path -ChildPath "$ModuleName.psm1")
} 

function New-TestingModule($Path, $ModuleName){

    $testingModuleName = $ModuleName + "Test"

    $testScript = 
@'
[CmdletBinding()]
param ()
 
$ModuleName = "_XMODULE_"
 
Import-Module -Name TestingHelper -Force
 
Test-Module -Name $ModuleName
'@


    $testingModulePs1 = "$TestingModuleName.ps1"
    
    $testScript = $testScript.Replace('_XMODULE_',$ModuleName)
    $testScript = $testScript.Replace('_CREATED_TIME_',(Get-Date).ToShortDateString());
    
    $testScript |  Out-File -FilePath (Join-Path -Path $Path -ChildPath $testingModulePs1)

    $toAppend =
@'
 
 
function _MODULE_TESTING__Sample(){
    Assert-IsTrue -Condition $true
}
 
Export-ModuleMember -Function _MODULE_TESTING__*
'@


    $toAppend = $toAppend.Replace('_MODULE_TESTING_',$testingModuleName)

    $null = New-Module -Path $Path -Name $testingModuleName -Description "Testing module for $ModuleName" -AvoidTestFile -AppendToModuleFile $toAppend
}   

function New-TestingVsCodeLaunchJson($Path, $ModuleName){
    $testScript = 
@'
    {
        // Use IntelliSense to learn about possible attributes.
        // Hover to view descriptions of existing attributes.
        // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
        "version": "0.2.0",
        "configurations": [
            {
                "name": "PowerShell: _XMODULE_.ps1",
                "type": "PowerShell",
                "request": "launch",
                "script": "${workspaceFolder}/_XMODULE_Test.ps1",
                "cwd": "${workspaceFolder}"
            }
        ]
    }
'@


    $testScript = $testScript.Replace('_XMODULE_',$ModuleName)

    New-Item `
        -ItemType File `
        -Path (Join-Path -Path $Path -ChildPath '.vscode' -AdditionalChildPath 'launch.json') `
        -Value $testScript `
        -Force `
        | Out-Null
}

# TODO : Reduce the number of functions exported
# Export-ModuleMember -Function Assert-*
# Export-ModuleMember -Function New-Testing*
# Export-ModuleMember -Function Test-Module
# Export-ModuleMember -Function Pop-TestingFolder