Invoke-Tests.ps1

#!/usr/bin/env pwsh
#Requires -Version 7.0

<#
.SYNOPSIS
    CI-ready test runner for KlippyCLI module.

.DESCRIPTION
    Runs Pester tests with configurable options for local development
    and CI/CD pipelines. Outputs results in multiple formats.

.PARAMETER TestType
    Type of tests to run: Unit, Integration, Lint, or All.
    Default is Unit.

.PARAMETER OutputPath
    Path for test result files. Default is ./TestResults.

.PARAMETER CodeCoverage
    Enable code coverage reporting.

.PARAMETER CI
    Run in CI mode with NUnit output format.

.EXAMPLE
    ./Invoke-Tests.ps1
    Runs unit tests only.

.EXAMPLE
    ./Invoke-Tests.ps1 -TestType All -CodeCoverage
    Runs all tests with code coverage.

.EXAMPLE
    ./Invoke-Tests.ps1 -TestType Integration
    Runs integration tests (requires KLIPPY_TEST_PRINTER env var).

.EXAMPLE
    ./Invoke-Tests.ps1 -CI
    Runs tests in CI mode with NUnit output.
#>


[CmdletBinding()]
param(
    [Parameter()]
    [ValidateSet('Unit', 'Integration', 'Lint', 'All')]
    [string]$TestType = 'Unit',

    [Parameter()]
    [string]$OutputPath = './TestResults',

    [Parameter()]
    [switch]$CodeCoverage,

    [Parameter()]
    [switch]$CI
)

$ErrorActionPreference = 'Stop'
Set-StrictMode -Version Latest

# Ensure output directory exists
if (-not (Test-Path $OutputPath)) {
    New-Item -Path $OutputPath -ItemType Directory -Force | Out-Null
}

# Check for required modules
$requiredModules = @('Pester')
if ($TestType -eq 'Lint' -or $TestType -eq 'All') {
    $requiredModules += 'PSScriptAnalyzer'
}

foreach ($module in $requiredModules) {
    if (-not (Get-Module -ListAvailable -Name $module)) {
        Write-Host "Installing $module module..." -ForegroundColor Yellow
        Install-Module -Name $module -Force -Scope CurrentUser -SkipPublisherCheck
    }
    Import-Module $module -Force
}

# Build Pester configuration
$pesterConfig = [PesterConfiguration]::Default

# Test paths based on type
$testPaths = switch ($TestType) {
    'Unit' { @('./tests/unit', './tests/KlippyCLI.Tests.ps1') }
    'Integration' { @('./tests/integration') }
    'Lint' { @('./tests/Lint.Tests.ps1') }
    'All' { @('./tests') }
}

$pesterConfig.Run.Path = $testPaths
$pesterConfig.Run.Exit = $CI.IsPresent

# Tags
$includeTags = switch ($TestType) {
    'Unit' { 'Unit' }
    'Integration' { 'Integration' }
    'Lint' { 'Lint' }
    'All' { $null }  # Run all tags
}

if ($includeTags) {
    $pesterConfig.Filter.Tag = @($includeTags)
}

# Output configuration
$pesterConfig.Output.Verbosity = if ($CI) { 'Detailed' } else { 'Normal' }

# Test results
if ($CI) {
    $pesterConfig.TestResult.Enabled = $true
    $pesterConfig.TestResult.OutputPath = Join-Path $OutputPath "TestResults.xml"
    $pesterConfig.TestResult.OutputFormat = 'NUnitXml'
}

# Code coverage
if ($CodeCoverage) {
    $pesterConfig.CodeCoverage.Enabled = $true
    $pesterConfig.CodeCoverage.Path = @('./functions')
    $pesterConfig.CodeCoverage.OutputPath = Join-Path $OutputPath "Coverage.xml"
    $pesterConfig.CodeCoverage.OutputFormat = 'JaCoCo'
}

# Display configuration
Write-Host "`n=== KlippyCLI Test Runner ===" -ForegroundColor Cyan
Write-Host "Test Type: $TestType" -ForegroundColor White
Write-Host "CI Mode: $CI" -ForegroundColor White
Write-Host "Code Coverage: $CodeCoverage" -ForegroundColor White
Write-Host "Output Path: $OutputPath" -ForegroundColor White

if ($TestType -eq 'Integration' -or $TestType -eq 'All') {
    if ($env:KLIPPY_TEST_PRINTER) {
        Write-Host "Test Printer: $env:KLIPPY_TEST_PRINTER" -ForegroundColor Green
    }
    else {
        Write-Host "Warning: KLIPPY_TEST_PRINTER not set - integration tests will be skipped" -ForegroundColor Yellow
    }
}

Write-Host "`nRunning tests...`n" -ForegroundColor Cyan

# Run Pester
$result = Invoke-Pester -Configuration $pesterConfig

# Summary
Write-Host "`n=== Test Summary ===" -ForegroundColor Cyan
if ($result) {
    Write-Host "Passed: $($result.PassedCount)" -ForegroundColor Green
    Write-Host "Failed: $($result.FailedCount)" -ForegroundColor $(if ($result.FailedCount -gt 0) { 'Red' } else { 'Green' })
    Write-Host "Skipped: $($result.SkippedCount)" -ForegroundColor Yellow
    Write-Host "Total: $($result.TotalCount)" -ForegroundColor White

    if ($CodeCoverage -and $result.CodeCoverage) {
        $coverage = [Math]::Round($result.CodeCoverage.CoveragePercent, 2)
        Write-Host "`nCode Coverage: $coverage%" -ForegroundColor $(if ($coverage -lt 50) { 'Red' } elseif ($coverage -lt 80) { 'Yellow' } else { 'Green' })
    }

    # Exit with appropriate code for CI
    if ($CI -and $result.FailedCount -gt 0) {
        exit 1
    }
}

return $result