Tests/VSAModule.Quality.Tests.ps1
|
BeforeAll { $ModuleRoot = Split-Path -Path (Split-Path -Parent $PSCommandPath) $ModulePath = Join-Path -Path $ModuleRoot -ChildPath 'VSAModule.psd1' $PublicPath = Join-Path -Path $ModuleRoot -ChildPath 'public' $PrivatePath = Join-Path -Path $ModuleRoot -ChildPath 'private' } Describe "VSAModule Code Quality" { Context "Directory Structure" { It "Module has PSM1 file" { Test-Path (Join-Path $ModuleRoot 'VSAModule.psm1') | Should -Be $true } It "Module has PSD1 file" { Test-Path (Join-Path $ModuleRoot 'VSAModule.psd1') | Should -Be $true } It "Module has public functions directory" { Test-Path $PublicPath | Should -Be $true } It "Module has private functions directory" { Test-Path $PrivatePath | Should -Be $true } It "Module has Tests directory" { Test-Path (Join-Path $ModuleRoot 'Tests') | Should -Be $true } It "Module has README" { Test-Path (Join-Path $ModuleRoot 'README.md') | Should -Be $true } It "Module has LICENSE" { Test-Path (Join-Path $ModuleRoot 'LICENSE.txt') | Should -Be $true } } Context "Function File Naming" { It "Public functions follow Verb-VSANoun naming" { $PublicFunctions = Get-ChildItem -Path $PublicPath -Filter "*.ps1" -ErrorAction SilentlyContinue foreach ($func in $PublicFunctions) { $func.BaseName -match "^[A-Z][a-z]+-VSA[A-Za-z0-9]+$" | Should -Be $true } } It "Private function files follow Verb-Noun naming" { # Only files that actually define a function are held to Verb-Noun; data-only files # (e.g. VSAEndpointMaps.ps1, which holds the dispatch/id hashtables) are exempt. $PrivateFunctions = Get-ChildItem -Path $PrivatePath -Filter "*.ps1" -ErrorAction SilentlyContinue | Where-Object { (Get-Content -LiteralPath $_.FullName -Raw) -match '(?im)^\s*function\s+' } foreach ($func in $PrivateFunctions) { $func.BaseName -match "^[A-Z][a-z]+-[A-Z][A-Za-z0-9]+$" | Should -Be $true } } It "Public function names start with approved verb" { $PublicFunctions = Get-ChildItem -Path $PublicPath -Filter "*.ps1" -ErrorAction SilentlyContinue # Validate against PowerShell's actual approved-verb set (Get-Verb) rather than a # hand-maintained subset that silently drifts as new (approved) verbs are adopted. $ValidVerbs = (Get-Verb).Verb foreach ($func in $PublicFunctions) { $verb = $func.BaseName.Split('-')[0] $ValidVerbs -contains $verb | Should -Be $true -Because $func.BaseName } } } Context "Function Content Quality" { It "Public functions are not empty" { $PublicFunctions = Get-ChildItem -Path $PublicPath -Filter "*.ps1" -ErrorAction SilentlyContinue | Select-Object -First 5 foreach ($func in $PublicFunctions) { (Get-Content -Path $func.FullName -Raw).Trim().Length | Should -BeGreaterThan 0 } } It "Private functions are not empty" { $PrivateFunctions = Get-ChildItem -Path $PrivatePath -Filter "*.ps1" -ErrorAction SilentlyContinue foreach ($func in $PrivateFunctions) { (Get-Content -Path $func.FullName -Raw).Trim().Length | Should -BeGreaterThan 0 } } It "Functions have function declaration" { $PublicFunctions = Get-ChildItem -Path $PublicPath -Filter "*.ps1" -ErrorAction SilentlyContinue | Select-Object -First 5 foreach ($func in $PublicFunctions) { (Get-Content -Path $func.FullName -Raw) -match "function\s+\w+-\w+" | Should -Be $true } } } Context "Root Module Content" { It "Root module exists and has content" { $RootModule = Get-Content -Path (Join-Path $ModuleRoot 'VSAModule.psm1') -Raw $RootModule.Length | Should -BeGreaterThan 0 } It "Root module defines classes" { $RootModule = Get-Content -Path (Join-Path $ModuleRoot 'VSAModule.psm1') -Raw $RootModule -match "class\s+\w+" | Should -Be $true } It "Root module has VSAConnection class" { $RootModule = Get-Content -Path (Join-Path $ModuleRoot 'VSAModule.psm1') -Raw $RootModule -match "class\s+VSAConnection" | Should -Be $true } It "Module defines URI mappings (in the dot-sourced maps file)" { # The URI/id maps were extracted from the .psm1 into private/VSAEndpointMaps.ps1 for # readability; they are dot-sourced into module scope by the loader. $Maps = Get-Content -Path (Join-Path $ModuleRoot 'private/VSAEndpointMaps.ps1') -Raw $Maps -match '\$(?:script:)?URISuffix.*=\s*@\{' | Should -Be $true } It "Root module creates aliases dynamically" { $RootModule = Get-Content -Path (Join-Path $ModuleRoot 'VSAModule.psm1') -Raw $RootModule -match "New-Alias" | Should -Be $true } } Context "Module Syntax Validation" { It "PSM1 file is valid PowerShell" { { [System.Management.Automation.PSParser]::Tokenize((Get-Content -Path (Join-Path $ModuleRoot 'VSAModule.psm1') -Raw), [ref]$null) } | Should -Not -Throw } It "PSD1 file is valid PowerShell data" { { Import-PowerShellDataFile -Path (Join-Path $ModuleRoot 'VSAModule.psd1') -ErrorAction Stop } | Should -Not -Throw } It "Public functions are valid PowerShell" { Get-ChildItem -Path $PublicPath -Filter "*.ps1" -ErrorAction SilentlyContinue | ForEach-Object { { [System.Management.Automation.PSParser]::Tokenize((Get-Content -Path $_.FullName -Raw), [ref]$null) } | Should -Not -Throw } } It "Private functions are valid PowerShell" { Get-ChildItem -Path $PrivatePath -Filter "*.ps1" -ErrorAction SilentlyContinue | ForEach-Object { { [System.Management.Automation.PSParser]::Tokenize((Get-Content -Path $_.FullName -Raw), [ref]$null) } | Should -Not -Throw } } } Context "Documentation Standards" { It "New-VSAConnection has comment-based help" { $Content = Get-Content -Path (Join-Path $ModuleRoot 'VSAModule.psm1') -Raw $Content -match "<#" | Should -Be $true } It "Help includes SYNOPSIS" { $Content = Get-Content -Path (Join-Path $ModuleRoot 'VSAModule.psm1') -Raw $Content -match "\.SYNOPSIS" | Should -Be $true } It "Help includes DESCRIPTION" { $Content = Get-Content -Path (Join-Path $ModuleRoot 'VSAModule.psm1') -Raw $Content -match "\.DESCRIPTION" | Should -Be $true } It "Help includes PARAMETER documentation" { $Content = Get-Content -Path (Join-Path $ModuleRoot 'VSAModule.psm1') -Raw $Content -match "\.PARAMETER" | Should -Be $true } It "Help includes EXAMPLE" { $Content = Get-Content -Path (Join-Path $ModuleRoot 'VSAModule.psm1') -Raw $Content -match "\.EXAMPLE" | Should -Be $true } } Context "Best Practices" { BeforeEach { Import-Module $ModulePath -Force } It "Module does not export wildcards" { $ManifestData = Import-PowerShellDataFile -Path $ModulePath $ManifestData.FunctionsToExport -contains "*" | Should -Be $false $ManifestData.AliasesToExport -contains "*" | Should -Be $false } It "Private functions not exposed in global scope" { Get-Item -Path Function:\Get-RequestData -ErrorAction SilentlyContinue | Should -BeNullOrEmpty Get-Item -Path Function:\Invoke-VSARestMethod -ErrorAction SilentlyContinue | Should -BeNullOrEmpty } It "Module version follows semantic versioning" { $ManifestData = Import-PowerShellDataFile -Path $ModulePath $ManifestData.ModuleVersion -match '^\d+\.\d+\.\d+' | Should -Be $true } } Context "Organization Consistency" { It "Test files follow naming convention" { Get-ChildItem -Path (Join-Path $ModuleRoot 'Tests') -Filter "*Tests.ps1" -ErrorAction SilentlyContinue | Should -Not -BeNullOrEmpty } } } Describe "The module version has a single source of truth: the manifest" { # The version used to be restated by hand in the psm1 banner and in New-VSAConnection's .NOTES, # and 9 functions carried their own private ".NOTES Version" (seven said 1.0.0, one 1.1.0, one # 1.2.0) that tracked nothing and had already gone stale. A per-function version is meaningless # in a module that ships as one unit, and every hand-maintained copy is a future lie. # ModuleVersion in the .psd1 is the only place the version may live. It "declares ModuleVersion in the manifest" { (Import-PowerShellDataFile "$ModuleRoot/VSAModule.psd1").ModuleVersion | Should -Match '^\d+\.\d+\.\d+$' } It "does not restate a version anywhere in shipped code" { $offenders = @() $files = @(Get-ChildItem "$ModuleRoot/public", "$ModuleRoot/private" -Filter *.ps1 -File) + @(Get-Item "$ModuleRoot/VSAModule.psm1") foreach ($f in $files) { $hits = Select-String -Path $f.FullName -Pattern '(?m)^\s*Version\s+\d+\.\d+\.\d+\s*$' foreach ($h in $hits) { $offenders += "$($f.Name):$($h.LineNumber)" } } $offenders | Should -BeNullOrEmpty -Because 'the version belongs only in the .psd1 ModuleVersion; hand-maintained copies rot' } It "exposes the manifest version at runtime, which is how callers should read it" { Import-Module "$ModuleRoot/VSAModule.psd1" -Force $expected = (Import-PowerShellDataFile "$ModuleRoot/VSAModule.psd1").ModuleVersion (Get-Module VSAModule).Version.ToString() | Should -Be $expected } } Describe "ShouldProcess discipline is complete and uniform across the write surface" { BeforeAll { Import-Module "$ModuleRoot/VSAModule.psd1" -Force # Read-only despite their verbs: Test-VSASSL performs a GET diagnostic. $script:ReadOnlyExceptions = @('Test-VSASSL') } It "every mutating public function declares SupportsShouldProcess" { $offenders = @() foreach ($f in Get-ChildItem "$ModuleRoot/public" -Filter *.ps1) { if ($f.BaseName -like 'Get-*' -or $f.BaseName -in $script:ReadOnlyExceptions) { continue } if ((Get-Content $f.FullName -Raw) -notmatch 'SupportsShouldProcess') { $offenders += $f.BaseName } } $offenders | Should -BeNullOrEmpty -Because 'a mutating cmdlet without -WhatIf support is a safety gap; Copy-VSAOrgStructure/Copy-VSAMGStructure were the last two (fixed)' } It "the Copy-* orchestrators gate creation with a direct ShouldProcess call" { # They compose public cmdlets rather than the write chokepoint, so the central -Caller # pattern does not cover them; they must call ShouldProcess themselves. The gate wraps the # whole create-and-verify block: under -WhatIf the 60s read-back wait must not spin. foreach ($n in @('Copy-VSAOrgStructure', 'Copy-VSAMGStructure')) { $src = Get-Content "$ModuleRoot/public/$n.ps1" -Raw $src | Should -Match '\$PSCmdlet\.ShouldProcess\(' -Because "$n mass-creates on a destination VSA" } } It "every delegating write cmdlet carries the justified PSShouldProcess suppression" { # The IDE analyzer cannot see that ShouldProcess is invoked centrally by # Invoke-VSAWriteRequest via -Caller; the justification attribute makes the tooling agree # with the architecture and documents the pattern in place. A new write cmdlet that # delegates must carry it too. $offenders = @() foreach ($f in Get-ChildItem "$ModuleRoot/public" -Filter *.ps1) { $src = Get-Content $f.FullName -Raw if ($src -notmatch 'SupportsShouldProcess') { continue } if ($src -match '\$PSCmdlet\.ShouldProcess\(') { continue } # calls it directly if ($src -notmatch "SuppressMessageAttribute\('PSShouldProcess'") { $offenders += $f.BaseName } } $offenders | Should -BeNullOrEmpty } } |