Test-BeforePublish.ps1

<#
.SYNOPSIS
Comprehensive pre-publish testing script for iFacto.AICodeReview module
 
.DESCRIPTION
This script performs thorough testing before publishing to PowerShell Gallery:
1. Unit tests with Pester
2. Module import/export verification
3. Function availability checks
4. Configuration validation
5. Documentation completeness
6. Example usage testing
7. Performance checks
 
Use this script to ensure everything works before publishing.
 
.PARAMETER SkipFunctionalTests
Skip functional tests that require API keys
 
.PARAMETER Provider
Test with specific AI provider (github, anthropic, openai, azure)
 
.PARAMETER Verbose
Show detailed test output
 
.EXAMPLE
.\Test-BeforePublish.ps1
 
.EXAMPLE
.\Test-BeforePublish.ps1 -SkipFunctionalTests
 
.EXAMPLE
# Test with GitHub Models (requires GITHUB_TOKEN)
$env:GITHUB_TOKEN = "your-token"
.\Test-BeforePublish.ps1 -Provider github
 
.NOTES
This is a comprehensive test that goes beyond Pester unit tests.
It validates the entire module is ready for production use.
#>

[CmdletBinding()]
param(
    [Parameter()]
    [switch]$SkipFunctionalTests,
    
    [Parameter()]
    [string]$Provider
)

$ErrorActionPreference = 'Stop'
$script:TestsPassed = 0
$script:TestsFailed = 0
$script:TestsSkipped = 0

# Module details
$moduleRoot = $PSScriptRoot
$moduleName = 'iFacto.AICodeReview'
$manifestPath = Join-Path $moduleRoot "$moduleName.psd1"

function Write-TestResult {
    param(
        [string]$TestName,
        [bool]$Passed,
        [string]$Message = '',
        [bool]$Skipped = $false
    )
    
    if ($Skipped) {
        Write-Host " ⊘ $TestName" -ForegroundColor Gray
        if ($Message) { Write-Host " $Message" -ForegroundColor Gray }
        $script:TestsSkipped++
    }
    elseif ($Passed) {
        Write-Host " ✓ $TestName" -ForegroundColor Green
        if ($Message) { Write-Host " $Message" -ForegroundColor Gray }
        $script:TestsPassed++
    }
    else {
        Write-Host " ✗ $TestName" -ForegroundColor Red
        if ($Message) { Write-Host " $Message" -ForegroundColor Red }
        $script:TestsFailed++
    }
}

Write-Host ""
Write-Host "========================================" -ForegroundColor Cyan
Write-Host " Pre-Publish Test Suite" -ForegroundColor Cyan
Write-Host "========================================" -ForegroundColor Cyan
Write-Host ""
Write-Host "Module: $moduleName" -ForegroundColor White
Write-Host "Path: $moduleRoot" -ForegroundColor Gray
Write-Host ""

# =============================================================================
# TEST 1: PESTER UNIT TESTS
# =============================================================================

Write-Host "Test Suite 1: Pester Unit Tests" -ForegroundColor Yellow
Write-Host "--------------------------------" -ForegroundColor Yellow

try {
    $pesterModule = Get-Module -ListAvailable -Name Pester | Sort-Object Version -Descending | Select-Object -First 1
    if (-not $pesterModule) {
        Write-TestResult "Pester installed" $false "Pester not found. Install with: Install-Module Pester -Force -MinimumVersion 5.0"
    }
    elseif ($pesterModule.Version.Major -lt 5) {
        Write-TestResult "Pester version" $false "Version $($pesterModule.Version) found, 5.x required"
    }
    else {
        Write-TestResult "Pester $($pesterModule.Version)" $true
        
        Import-Module Pester -MinimumVersion 5.0 -ErrorAction Stop
        
        $testPath = Join-Path $moduleRoot 'Tests\Unit\BasicFunctions.Tests.ps1'
        if (Test-Path $testPath) {
            $config = New-PesterConfiguration
            $config.Run.Path = $testPath
            $config.Output.Verbosity = 'Minimal'
            $config.Run.PassThru = $true
            
            $result = Invoke-Pester -Configuration $config
            
            Write-TestResult "Unit tests passed ($($result.PassedCount)/$($result.TotalCount))" ($result.FailedCount -eq 0) "Duration: $($result.Duration.TotalSeconds)s"
        }
        else {
            Write-TestResult "Unit tests found" $false "Test file not found: $testPath"
        }
    }
}
catch {
    Write-TestResult "Pester execution" $false $_.Exception.Message
}

Write-Host ""

# =============================================================================
# TEST 2: MODULE STRUCTURE
# =============================================================================

Write-Host "Test Suite 2: Module Structure" -ForegroundColor Yellow
Write-Host "-------------------------------" -ForegroundColor Yellow

$requiredFolders = @('Public', 'Private', 'Rules', 'Tests', 'Build', 'Examples')
foreach ($folder in $requiredFolders) {
    $folderPath = Join-Path $moduleRoot $folder
    Write-TestResult "Folder: $folder" (Test-Path $folderPath)
}

$requiredFiles = @(
    'iFacto.AICodeReview.psd1',
    'iFacto.AICodeReview.psm1',
    'README.md',
    'LICENSE',
    'CHANGELOG.md',
    'Rules\model-config.json',
    'Rules\core-rules.md'
)
foreach ($file in $requiredFiles) {
    $filePath = Join-Path $moduleRoot $file
    Write-TestResult "File: $file" (Test-Path $filePath)
}

Write-Host ""

# =============================================================================
# TEST 3: MODULE MANIFEST
# =============================================================================

Write-Host "Test Suite 3: Module Manifest" -ForegroundColor Yellow
Write-Host "------------------------------" -ForegroundColor Yellow

try {
    $manifest = Test-ModuleManifest -Path $manifestPath -ErrorAction Stop
    Write-TestResult "Manifest valid" $true
    Write-TestResult "Version: $($manifest.Version)" $true
    Write-TestResult "GUID present" ($null -ne $manifest.Guid -and $manifest.Guid -ne '00000000-0000-0000-0000-000000000000')
    Write-TestResult "Author set" (-not [string]::IsNullOrWhiteSpace($manifest.Author))
    Write-TestResult "Description set" (-not [string]::IsNullOrWhiteSpace($manifest.Description))
    Write-TestResult "PowerShell version" ($manifest.PowerShellVersion -ge [version]'7.0')
    Write-TestResult "Functions exported" ($manifest.ExportedFunctions.Count -eq 4) "$($manifest.ExportedFunctions.Count) functions"
}
catch {
    Write-TestResult "Manifest validation" $false $_.Exception.Message
}

Write-Host ""

# =============================================================================
# TEST 4: MODULE IMPORT
# =============================================================================

Write-Host "Test Suite 4: Module Import/Export" -ForegroundColor Yellow
Write-Host "-----------------------------------" -ForegroundColor Yellow

try {
    Remove-Module $moduleName -Force -ErrorAction SilentlyContinue
    Import-Module $manifestPath -Force -ErrorAction Stop
    Write-TestResult "Module imports" $true
    
    $commands = Get-Command -Module $moduleName
    Write-TestResult "Commands available" ($commands.Count -eq 4) "$($commands.Count) commands"
    
    $expectedCommands = @('Invoke-AICodeReview', 'Get-ChangedALFiles', 'ConvertTo-ADOLogFormat', 'Test-AICodeReview')
    foreach ($cmd in $expectedCommands) {
        $found = Get-Command -Name $cmd -Module $moduleName -ErrorAction SilentlyContinue
        Write-TestResult "Command: $cmd" ($null -ne $found)
    }
    
    # Check help
    $help = Get-Help Invoke-AICodeReview -ErrorAction SilentlyContinue
    Write-TestResult "Help available" ($null -ne $help -and $help.Synopsis)
}
catch {
    Write-TestResult "Module import" $false $_.Exception.Message
}

Write-Host ""

# =============================================================================
# TEST 5: CONFIGURATION VALIDATION
# =============================================================================

Write-Host "Test Suite 5: Configuration" -ForegroundColor Yellow
Write-Host "----------------------------" -ForegroundColor Yellow

$configPath = Join-Path $moduleRoot 'Rules\model-config.json'
try {
    $config = Get-Content $configPath | ConvertFrom-Json
    Write-TestResult "Config file valid JSON" $true
    Write-TestResult "Default provider set" (-not [string]::IsNullOrWhiteSpace($config.default.provider))
    
    $providerCount = @($config.providers.PSObject.Properties).Count
    Write-TestResult "Providers configured" ($providerCount -ge 4) "$providerCount providers"
    
    # Validate provider structure (simplified - just check count and structure)
    $validProviders = 0
    foreach ($providerName in $config.providers.PSObject.Properties.Name) {
        if ($config.providers.$providerName.type -and 
            $config.providers.$providerName.endpoint -and 
            $config.providers.$providerName.api_key_variable) {
            $validProviders++
        }
    }
    Write-TestResult "Valid provider configs" ($validProviders -ge 4) "$validProviders/$providerCount valid"
}
catch {
    Write-TestResult "Configuration" $false $_.Exception.Message
}

Write-Host ""

# =============================================================================
# TEST 6: RULES VALIDATION
# =============================================================================

Write-Host "Test Suite 6: Review Rules" -ForegroundColor Yellow
Write-Host "---------------------------" -ForegroundColor Yellow

$rulesPath = Join-Path $moduleRoot 'Rules\core-rules.md'
try {
    $rules = Get-Content $rulesPath -Raw
    Write-TestResult "Rules file exists" $true
    Write-TestResult "Rules not empty" ($rules.Length -gt 1000) "$($rules.Length) characters"
    Write-TestResult "Contains rule IDs" ($rules -match 'AL-\w+-\d+')
    Write-TestResult "Contains examples" ($rules -match '```al')
    Write-TestResult "Contains severity levels" ($rules -match '\*\*Severity:\*\*')
}
catch {
    Write-TestResult "Rules validation" $false $_.Exception.Message
}

Write-Host ""

# =============================================================================
# TEST 7: DOCUMENTATION
# =============================================================================

Write-Host "Test Suite 7: Documentation" -ForegroundColor Yellow
Write-Host "----------------------------" -ForegroundColor Yellow

$readmePath = Join-Path $moduleRoot 'README.md'
try {
    $readme = Get-Content $readmePath -Raw
    Write-TestResult "README exists" $true
    Write-TestResult "README has content" ($readme.Length -gt 5000) "$($readme.Length) characters"
    Write-TestResult "README has install instructions" ($readme -match 'Install-Module')
    Write-TestResult "README has examples" (($readme -match '## Example') -or ($readme -match '```powershell'))
}
catch {
    Write-TestResult "README validation" $false $_.Exception.Message
}

$changelogPath = Join-Path $moduleRoot 'CHANGELOG.md'
try {
    $changelog = Get-Content $changelogPath -Raw -ErrorAction SilentlyContinue
    if ($changelog) {
        Write-TestResult "CHANGELOG exists" $true
        Write-TestResult "CHANGELOG has version" ($changelog -match '\[?\d+\.\d+\.\d+\]?')
    }
    else {
        Write-TestResult "CHANGELOG exists" $false
    }
}
catch {
    Write-TestResult "CHANGELOG" $false $_.Exception.Message
}

Write-Host ""

# =============================================================================
# TEST 8: FUNCTIONAL TESTS (OPTIONAL)
# =============================================================================

Write-Host "Test Suite 8: Functional Tests" -ForegroundColor Yellow
Write-Host "-------------------------------" -ForegroundColor Yellow

if ($SkipFunctionalTests) {
    Write-TestResult "Functional tests" $false "Skipped by user" $true
}
else {
    # Test Get-ChangedALFiles
    try {
        $changedFiles = Get-ChangedALFiles -BaseBranch "HEAD~1" -ErrorAction Stop
        Write-TestResult "Get-ChangedALFiles executes" $true
    }
    catch {
        if ($_.Exception.Message -like "*not a git repository*") {
            Write-TestResult "Get-ChangedALFiles" $false "Requires git repository" $true
        }
        else {
            Write-TestResult "Get-ChangedALFiles" $false $_.Exception.Message
        }
    }
    
    # Test ConvertTo-ADOLogFormat
    try {
        $testViolations = @(
            @{file='test.al'; line=10; severity='warning'; message='Test message'; category='Test'}
        )
        $result = ConvertTo-ADOLogFormat -Violations $testViolations -SeverityFailBuild 'error' -ErrorAction Stop
        Write-TestResult "ConvertTo-ADOLogFormat executes" ($result -eq 0)
    }
    catch {
        Write-TestResult "ConvertTo-ADOLogFormat" $false $_.Exception.Message
    }
    
    # Test AI provider (if API key available)
    if ($Provider) {
        $config = Get-Content $configPath | ConvertFrom-Json
        $providerConfig = $config.providers.$Provider
        $apiKeyVar = $providerConfig.api_key_variable
        $apiKey = [Environment]::GetEnvironmentVariable($apiKeyVar)
        
        if ($apiKey) {
            try {
                Write-Host " ⏳ Testing $Provider provider (may take 30-60s)..." -ForegroundColor Yellow
                
                $testContext = @(
                    @{
                        FilePath = "test.al"
                        FullDiff = "table 50000 TestTable`n{`n fields`n {`n field(1; customer_no; Text[20]) { }`n }`n}"
                    }
                )
                
                $testRules = "Test rule: Field names should use PascalCase"
                
                $violations = Invoke-AICodeReview `
                    -ReviewContext $testContext `
                    -Rules $testRules `
                    -Provider $Provider `
                    -MaxTokens 500 `
                    -ErrorAction Stop
                
                Write-TestResult "$Provider provider works" $true "Returned $($violations.Count) violations"
            }
            catch {
                Write-TestResult "$Provider provider" $false $_.Exception.Message
            }
        }
        else {
            Write-TestResult "$Provider provider" $false "API key not found: $apiKeyVar" $true
        }
    }
    else {
        Write-TestResult "AI provider test" $false "Use -Provider to test specific provider" $true
    }
}

Write-Host ""

# =============================================================================
# TEST SUMMARY
# =============================================================================

Write-Host "========================================" -ForegroundColor Cyan
Write-Host "Test Summary" -ForegroundColor Cyan
Write-Host "========================================" -ForegroundColor Cyan
Write-Host ""
Write-Host "Passed: $script:TestsPassed" -ForegroundColor Green
Write-Host "Failed: $script:TestsFailed" -ForegroundColor $(if ($script:TestsFailed -eq 0) { "Green" } else { "Red" })
Write-Host "Skipped: $script:TestsSkipped" -ForegroundColor Gray
Write-Host ""

if ($script:TestsFailed -eq 0) {
    Write-Host "✓ All tests passed - Module is ready to publish!" -ForegroundColor Green
    Write-Host ""
    Write-Host "Next steps:" -ForegroundColor Yellow
    Write-Host "1. Review CHANGELOG.md and update with latest changes" -ForegroundColor White
    Write-Host "2. Run: .\Publish-ToGallery.ps1" -ForegroundColor White
    Write-Host ""
    exit 0
}
else {
    Write-Host "✗ Some tests failed - Fix issues before publishing" -ForegroundColor Red
    Write-Host ""
    exit 1
}