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 } |