248 lines
9.9 KiB
PowerShell
248 lines
9.9 KiB
PowerShell
# Tests\RunAllTests.ps1
|
|
# Enterprise Admin Studio - Comprehensive Test Runner Script
|
|
# Dual-Compatible with both Windows PowerShell 5.1 and PowerShell 7 (Core)
|
|
# Supports standard Pester v5+ and standalone runner fallback for environments without Pester v5.
|
|
|
|
[CmdletBinding()]
|
|
param(
|
|
[string[]]$Path = @(),
|
|
[string]$Tag = "",
|
|
[string]$Filter = "",
|
|
[string]$Output = "Detailed",
|
|
[switch]$Live,
|
|
[switch]$CI
|
|
)
|
|
|
|
$ErrorActionPreference = 'Stop'
|
|
$ScriptDir = if ($PSScriptRoot) { $PSScriptRoot } else { Split-Path -Parent $MyInvocation.MyCommand.Path }
|
|
$WorkspaceRoot = Split-Path -Parent $ScriptDir
|
|
|
|
Write-Host "================================================================================" -ForegroundColor Cyan
|
|
Write-Host " Enterprise Admin Studio - Comprehensive Test Suite Runner" -ForegroundColor Cyan
|
|
Write-Host " PowerShell Version : $($PSVersionTable.PSVersion)" -ForegroundColor Cyan
|
|
Write-Host " OS Platform : $(if ($PSVersionTable.OS) { $PSVersionTable.OS } else { [System.Environment]::OSVersion.VersionString })" -ForegroundColor Cyan
|
|
Write-Host " Working Directory : $WorkspaceRoot" -ForegroundColor Cyan
|
|
Write-Host " Mode : $(if ($Live) { 'Live External Infrastructure' } else { '100% Mock / Standalone Reproducible' })" -ForegroundColor Cyan
|
|
Write-Host "================================================================================" -ForegroundColor Cyan
|
|
|
|
# Determine test files to execute
|
|
$testFiles = if ($Path -and $Path.Count -gt 0) {
|
|
@($Path | ForEach-Object {
|
|
if ([System.IO.Path]::IsPathRooted($_)) { $_ } else { Join-Path $ScriptDir $_ }
|
|
})
|
|
} else {
|
|
@(
|
|
(Join-Path $ScriptDir "ConfigService.Tests.ps1"),
|
|
(Join-Path $ScriptDir "UserResolverService.Tests.ps1"),
|
|
(Join-Path $ScriptDir "SqlService.Tests.ps1"),
|
|
(Join-Path $ScriptDir "ADService.Tests.ps1"),
|
|
(Join-Path $ScriptDir "NetAppService.Tests.ps1"),
|
|
(Join-Path $ScriptDir "BatchPipeline.Tests.ps1")
|
|
)
|
|
}
|
|
|
|
# Verify test files exist
|
|
$missingFiles = @($testFiles | Where-Object { -not (Test-Path $_) })
|
|
if ($missingFiles.Count -gt 0) {
|
|
Write-Error "Missing test files:`n$($missingFiles -join "`n")"
|
|
exit 1
|
|
}
|
|
|
|
# Try importing Pester 5
|
|
$pesterModule = Get-Module -Name Pester
|
|
if (-not $pesterModule -or $pesterModule.Version.Major -lt 5) {
|
|
$pester5 = Get-Module -ListAvailable -Name Pester | Where-Object { $_.Version.Major -ge 5 } | Select-Object -First 1
|
|
if ($pester5) {
|
|
Import-Module $pester5.Path -Force -ErrorAction SilentlyContinue
|
|
$pesterModule = Get-Module -Name Pester
|
|
} else {
|
|
$userModulePaths = @(
|
|
"$([System.Environment]::GetFolderPath('MyDocuments'))\PowerShell\Modules\Pester\5.5.0\Pester.psd1",
|
|
"$([System.Environment]::GetFolderPath('MyDocuments'))\WindowsPowerShell\Modules\Pester\5.5.0\Pester.psd1"
|
|
)
|
|
foreach ($mp in $userModulePaths) {
|
|
if (Test-Path $mp) {
|
|
Import-Module $mp -Force -ErrorAction SilentlyContinue
|
|
$pesterModule = Get-Module -Name Pester
|
|
if ($pesterModule) { break }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
$usePester5 = ($pesterModule -and $pesterModule.Version.Major -ge 5)
|
|
$startTime = [System.Diagnostics.Stopwatch]::StartNew()
|
|
|
|
if ($usePester5) {
|
|
Write-Host "Running via Pester v$($pesterModule.Version)...`n" -ForegroundColor Green
|
|
$pesterConfig = [PesterConfiguration]::Default
|
|
$pesterConfig.Run.Path = $testFiles
|
|
$pesterConfig.Run.PassThru = $true
|
|
$pesterConfig.Output.Verbosity = $Output
|
|
|
|
if (-not [string]::IsNullOrWhiteSpace($Tag)) {
|
|
$pesterConfig.Filter.Tag = @($Tag -split ",")
|
|
} elseif ($Live) {
|
|
$pesterConfig.Filter.Tag = @("Live")
|
|
}
|
|
|
|
if (-not [string]::IsNullOrWhiteSpace($Filter)) {
|
|
$pesterConfig.Filter.FullName = "*$Filter*"
|
|
}
|
|
|
|
$testResult = Invoke-Pester -Configuration $pesterConfig
|
|
$startTime.Stop()
|
|
|
|
$passedCount = if ($testResult.PassedCount) { $testResult.PassedCount } else { 0 }
|
|
$failedCount = if ($testResult.FailedCount) { $testResult.FailedCount } else { 0 }
|
|
$skippedCount = if ($testResult.SkippedCount) { $testResult.SkippedCount } else { 0 }
|
|
$totalCount = if ($testResult.TotalCount) { $testResult.TotalCount } else { ($passedCount + $failedCount + $skippedCount) }
|
|
} else {
|
|
Write-Host "Pester v5 not detected. Executing with embedded Standalone Test Engine...`n" -ForegroundColor Yellow
|
|
|
|
$script:StandalonePassed = 0
|
|
$script:StandaloneFailed = 0
|
|
$script:StandaloneSkipped = 0
|
|
$script:FailedDetails = [System.Collections.Generic.List[string]]::new()
|
|
$script:ActiveBeforeEach = $null
|
|
$script:ActiveAfterEach = $null
|
|
|
|
function script:Describe {
|
|
param([string]$Name, [string[]]$Tag = @(), [scriptblock]$Fixture)
|
|
Write-Host "Describing $Name" -ForegroundColor Cyan
|
|
& $Fixture
|
|
}
|
|
|
|
function script:Context {
|
|
param([string]$Name, [string[]]$Tag = @(), [scriptblock]$Fixture)
|
|
if ((-not $Live) -and ($Tag -contains "Live")) {
|
|
Write-Host " Skipping Context: $Name (Live tag disabled)" -ForegroundColor DarkGray
|
|
$script:StandaloneSkipped++
|
|
return
|
|
}
|
|
Write-Host " Context $Name" -ForegroundColor Yellow
|
|
& $Fixture
|
|
}
|
|
|
|
function script:It {
|
|
param([string]$Name, [string[]]$Tag = @(), [scriptblock]$Test)
|
|
if ((-not $Live) -and ($Tag -contains "Live")) {
|
|
$script:StandaloneSkipped++
|
|
return
|
|
}
|
|
if ($Filter -and ($Name -notmatch $Filter)) {
|
|
return
|
|
}
|
|
try {
|
|
if ($script:ActiveBeforeEach) { & $script:ActiveBeforeEach }
|
|
& $Test
|
|
if ($script:ActiveAfterEach) { & $script:ActiveAfterEach }
|
|
$script:StandalonePassed++
|
|
Write-Host " [+] $Name" -ForegroundColor Green
|
|
} catch {
|
|
$script:StandaloneFailed++
|
|
$err = "$Name failed: $($_.Exception.Message)"
|
|
$script:FailedDetails.Add($err)
|
|
Write-Host " [-] $err" -ForegroundColor Red
|
|
}
|
|
}
|
|
|
|
function script:BeforeAll { param([scriptblock]$Script) & $Script }
|
|
function script:AfterAll { param([scriptblock]$Script) & $Script }
|
|
function script:BeforeEach { param([scriptblock]$Script) $script:ActiveBeforeEach = $Script }
|
|
function script:AfterEach { param([scriptblock]$Script) $script:ActiveAfterEach = $Script }
|
|
|
|
function script:Mock {
|
|
param([string]$CommandName, [scriptblock]$MockWith)
|
|
Set-Item "function:global:$CommandName" -Value $MockWith
|
|
}
|
|
|
|
filter script:Should {
|
|
param(
|
|
[string]$Operator,
|
|
[object]$Expected,
|
|
[switch]$Not,
|
|
[switch]$BeTrue,
|
|
[switch]$BeFalse,
|
|
[switch]$BeNullOrEmpty
|
|
)
|
|
$val = $_
|
|
$op = if ($Operator) { $Operator.TrimStart('-') } else { "" }
|
|
|
|
if ($op -eq 'Not') {
|
|
$Not = $true
|
|
# subsequent argument
|
|
}
|
|
|
|
$pass = $false
|
|
if ($BeTrue -or $op -eq 'BeTrue') { $pass = ($val -eq $true) }
|
|
elseif ($BeFalse -or $op -eq 'BeFalse') { $pass = ($val -eq $false) }
|
|
elseif ($BeNullOrEmpty -or $op -eq 'BeNullOrEmpty') {
|
|
$pass = ($null -eq $val -or ($val -is [string] -and [string]::IsNullOrEmpty($val)) -or ($val.Count -eq 0))
|
|
}
|
|
elseif ($op -eq 'Be') { $pass = ($val -eq $Expected) }
|
|
elseif ($op -eq 'Match') { $pass = ($val -match $Expected) }
|
|
elseif ($op -eq 'Contain') { $pass = ($val -contains $Expected) }
|
|
elseif ($op -eq 'BeGreaterThan') { $pass = ($val -gt $Expected) }
|
|
elseif ($op -eq 'BeGreaterOrEqual') { $pass = ($val -ge $Expected) }
|
|
elseif ($op -eq 'Throw') {
|
|
$threw = $false
|
|
if ($val -is [scriptblock]) {
|
|
try { & $val } catch { $threw = $true; if ($Expected) { $pass = ($_.Exception.Message -like $Expected) } else { $pass = $true } }
|
|
}
|
|
$pass = $threw
|
|
}
|
|
|
|
if ($Not) { $pass = -not $pass }
|
|
if (-not $pass) {
|
|
throw "Assertion failed. Actual: '$val', Operator: '$op', Expected: '$Expected', Not: $Not"
|
|
}
|
|
}
|
|
|
|
foreach ($tf in $testFiles) {
|
|
Write-Host "Running tests from '$tf'" -ForegroundColor Gray
|
|
. $tf
|
|
}
|
|
|
|
$startTime.Stop()
|
|
$passedCount = $script:StandalonePassed
|
|
$failedCount = $script:StandaloneFailed
|
|
$skippedCount = $script:StandaloneSkipped
|
|
$totalCount = $passedCount + $failedCount + $skippedCount
|
|
}
|
|
|
|
# Generate Test Summary
|
|
Write-Host "`n================================================================================" -ForegroundColor Cyan
|
|
Write-Host " TEST EXECUTION SUMMARY" -ForegroundColor Cyan
|
|
Write-Host "================================================================================" -ForegroundColor Cyan
|
|
|
|
$summaryTable = [PSCustomObject]@{
|
|
TotalSuites = $testFiles.Count
|
|
TotalTests = $totalCount
|
|
Passed = $passedCount
|
|
Failed = $failedCount
|
|
Skipped = $skippedCount
|
|
DurationSec = [Math]::Round($startTime.Elapsed.TotalSeconds, 2)
|
|
SuccessRate = if ($totalCount -gt 0) { "$([Math]::Round(($passedCount / $totalCount) * 100, 1))%" } else { "0%" }
|
|
}
|
|
|
|
$summaryTable | Format-Table -AutoSize
|
|
|
|
if ($failedCount -gt 0) {
|
|
Write-Host "`n[FAIL] Test suite failed with $failedCount errors." -ForegroundColor Red
|
|
if ($testResult -and $testResult.Failed) {
|
|
Write-Host "`nFailed Test Details:" -ForegroundColor Red
|
|
foreach ($f in $testResult.Failed) {
|
|
Write-Host " - $($f.ExpandedPath): $($f.ErrorRecord.Exception.Message)" -ForegroundColor Red
|
|
}
|
|
} elseif ($script:FailedDetails) {
|
|
foreach ($fd in $script:FailedDetails) {
|
|
Write-Host " - $fd" -ForegroundColor Red
|
|
}
|
|
}
|
|
if ($CI) { exit 1 } else { return $false }
|
|
} else {
|
|
Write-Host "`n[PASS] All $passedCount tests passed successfully without error!" -ForegroundColor Green
|
|
if ($CI) { exit 0 } else { return $true }
|
|
}
|