Tests/VSAModule.Structure.Tests.ps1
|
$ModuleRoot = Split-Path -Path (Split-Path -Parent $PSScriptRoot) $ModulePath = Join-Path -Path $ModuleRoot -ChildPath 'VSAModule.psd1' $PublicPath = Join-Path -Path $ModuleRoot -ChildPath 'public' BeforeAll { $script:ModuleRoot = Split-Path -Path (Split-Path -Parent $PSCommandPath) $script:ModulePath = Join-Path -Path $script:ModuleRoot -ChildPath 'VSAModule.psd1' $script:PublicPath = Join-Path -Path $script:ModuleRoot -ChildPath 'public' Import-Module $script:ModulePath -Force $script:Manifest = Import-PowerShellDataFile -Path $script:ModulePath $script:PublicFiles = Get-ChildItem -Path $script:PublicPath -Filter '*.ps1' -File } Describe "VSAModule Structure Regression" { Context "Manifest functions all exist" { It "Every name in FunctionsToExport resolves to a command in the module" { $missing = @() foreach ($name in $script:Manifest.FunctionsToExport) { if (-not (Get-Command -Name $name -Module VSAModule -ErrorAction SilentlyContinue)) { $missing += $name } } $missing -join ', ' | Should -BeNullOrEmpty } } Context "Every public file is exported" { It "The function defined by each public/*.ps1 is in FunctionsToExport" { $notExported = @() foreach ($file in $script:PublicFiles) { if ($script:Manifest.FunctionsToExport -notcontains $file.BaseName) { $notExported += $file.BaseName } } $notExported -join ', ' | Should -BeNullOrEmpty } } Context "File name equals defined function" { It "The first 'function <Name>' in each public file equals the file base name" { $mismatched = @() foreach ($file in $script:PublicFiles) { $content = Get-Content -Path $file.FullName -Raw $m = [regex]::Match($content, '(?im)^\s*function\s+([A-Za-z0-9\-]+)') $definedName = if ($m.Success) { $m.Groups[1].Value } else { '<none>' } if ($definedName -ne $file.BaseName) { $mismatched += "$($file.Name): defines '$definedName'" } } $mismatched -join '; ' | Should -BeNullOrEmpty } } Context "Aliases reconcile" { It "The set of aliases created by module code equals AliasesToExport" { $created = (Get-Module VSAModule).ExportedAliases.Keys | Sort-Object $declared = $script:Manifest.AliasesToExport | Sort-Object ($created -join ', ') | Should -Be ($declared -join ', ') } } Context "Genuinely internal functions are NOT exported" { # F-20: Get-VSAItem/Get-VSAItemById/Remove-VSAItem were removed from this list -- they MUST # be exported so their ~89 aliases resolve on a normal import. The transport-layer helpers # below have no aliases pointing at them and correctly stay private. It "<Name> is not a command in the VSAModule" -ForEach @( @{ Name = 'Get-RequestData' } @{ Name = 'Invoke-VSARestMethod' } @{ Name = 'Update-VSAConnection' } ) { Get-Command -Name $Name -Module VSAModule -ErrorAction SilentlyContinue | Should -BeNullOrEmpty } } Context "No mandatory VSAConnection" { It "The VSAConnection parameter is not mandatory on any public function" { $mandatory = @() foreach ($name in $script:Manifest.FunctionsToExport) { $cmd = Get-Command -Name $name -Module VSAModule -ErrorAction SilentlyContinue if ($null -eq $cmd) { continue } $param = $cmd.Parameters['VSAConnection'] if ($null -eq $param) { continue } foreach ($set in $param.Attributes | Where-Object { $_ -is [System.Management.Automation.ParameterAttribute] }) { if ($set.Mandatory) { $mandatory += $name } } } $mandatory -join ', ' | Should -BeNullOrEmpty } } Context "Clean import" { It "Import-Module writes zero warnings" { Import-Module $script:ModulePath -Force -WarningVariable w -WarningAction SilentlyContinue | Out-Null $w.Count | Should -Be 0 } } Context "Encoding (F-59 / T-8.2)" { It "Every .ps1 file containing a non-ASCII byte begins with a UTF-8 BOM" { $badFiles = @() $allPs1 = Get-ChildItem -Path $script:ModuleRoot -Filter '*.ps1' -Recurse -File | Where-Object { $_.FullName -notmatch '[\\/](Tests|Tools)[\\/]' } foreach ($file in $allPs1) { $bytes = [System.IO.File]::ReadAllBytes($file.FullName) $hasBom = $bytes.Length -ge 3 -and $bytes[0] -eq 0xEF -and $bytes[1] -eq 0xBB -and $bytes[2] -eq 0xBF $body = if ($hasBom) { $bytes[3..($bytes.Length - 1)] } else { $bytes } $hasNonAscii = $body | Where-Object { $_ -gt 0x7F } | Select-Object -First 1 if ($hasNonAscii -and -not $hasBom) { $badFiles += $file.FullName.Substring($script:ModuleRoot.Length + 1) } } $badFiles -join ', ' | Should -BeNullOrEmpty } } } |