Skip to content

Commit 97003e6

Browse files
authored
Bug/260 setting max pester version below 6 (#261)
* fix(#260): enforce supported Pester version range and improve error handling - `Invoke-NovaTest`, `Test-NovaBuild`, and `% nova test` now only use Pester versions from `5.7.1` to `5.10.0`, failing with a clear error for unsupported `Pester 6.x`. - Updated documentation to reflect the new Pester version requirements. * fix(#260): improve Pester version handling in Nova test workflows - Reuse already loaded supported Pester 5.x version before switching - Ensure early failure with clear error when unsupported Pester 6.x is present * fix(#260): enhance Nova module validation commands and logging - Introduced new functions for command generation and execution - Improved error handling for validation command failures - Updated logging to capture command execution details * fix(#260): improve run.ps1 script content handling in tests - resolve script path and load content once for efficiency - update test assertions to use loaded script content * fix(#260): improve run.ps1 script content handling in tests - resolve script path and load content once for efficiency - update test assertions to use loaded script content * fix(#260): enhance output handling in integration tests - replace output joining with Get-NovaPublicCommandIntegrationOutputText for better formatting - normalize whitespace in output assertions for consistency * fix(#260): add tests for Get-NovaPesterVersionText and Test-NovaPesterModuleVersionSupported - implement default value handling in Get-NovaPesterVersionText - validate null candidate version in Test-NovaPesterModuleVersionSupported
1 parent 5a5a56a commit 97003e6

16 files changed

Lines changed: 770 additions & 130 deletions

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
1515

1616
### Fixed
1717

18+
- `Invoke-NovaTest`, `Test-NovaBuild`, and `% nova test` now resolve and import a supported installed `Pester` version from `5.7.1` through `5.10.0` instead of using an unsupported `Pester 6.x` installation automatically.
19+
- Nova test workflows now fail early with a clear dependency error when only unsupported `Pester 6.x` versions are available.
20+
- Nova test workflows now reuse an already loaded supported `Pester 5.x` version in the current PowerShell session before selecting a different installed version.
21+
- Repository CI no longer trips the `Pester` assembly loader by importing a gallery-installed `NovaModuleTools` module, building the local module, and then switching to another supported `Pester` version in the same session.
22+
1823
### Security
1924

2025
## [3.3.0] - 2026-06-16

README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -298,8 +298,9 @@ Notes:
298298
- if `project.json` sets `Pester.CodeCoverage.CoveragePercentTarget`, `Invoke-NovaTest` fails when the measured coverage percentage is lower than that configured target
299299
- this repository currently enables coverage with a `99` percent target; the template and packaged example
300300
`project.json` files ship the same JaCoCo configuration shape with `Enabled=false` and a `90` percent opt-in target
301-
- make sure `Pester 5.7.1` is available before running `Invoke-NovaTest` or `Test-NovaBuild`
302-
- the published `NovaModuleTools` manifest also declares `Pester 5.7.1`, so installed end-user workflows can still resolve that dependency automatically
301+
- make sure a supported `Pester` version from `5.7.1` through `5.10.0` is available before running `Invoke-NovaTest` or `Test-NovaBuild`
302+
- Nova resolves and imports a compatible installed `Pester 5.x` version for the managed test workflow and does not run tests with `Pester 6.x`
303+
- the published `NovaModuleTools` manifest declares `Pester` with `ModuleVersion = 5.7.1` and `MaximumVersion = 5.10.0`, so installed end-user workflows can still resolve that supported dependency range automatically
303304

304305
### Create a package artifact
305306

@@ -640,7 +641,7 @@ Responsibilities currently covered by the release pipeline include:
640641
- publishing to PowerShell Gallery
641642
- preparing the next prerelease version on `develop`
642643

643-
The workflow now uses `KeepAChangelog` for changelog release moves, creates annotated git tags named directly from the release version, and bootstraps the local PSResourceGet repository store before calling `Publish-NovaModule`. The shared CI installer also installs `Pester 5.7.1` explicitly before it installs prerelease gallery modules so test workflows do not rely on transitive manifest dependency resolution.
644+
The workflow now uses `KeepAChangelog` for changelog release moves, creates annotated git tags named directly from the release version, and bootstraps the local PSResourceGet repository store before calling `Publish-NovaModule`. The shared CI installer also installs `Pester 5.7.1` explicitly before it installs prerelease gallery modules so test workflows stay on Nova's supported `Pester 5.x` range and do not rely on transitive manifest dependency resolution.
644645

645646
### Where NovaModuleTools cmdlets fit
646647

RELEASE_NOTE.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ This file summarizes the release notes for NovaModuleTools. **UNRELEASED** chang
1414

1515
### Fixed
1616

17+
- `Invoke-NovaTest`, `Test-NovaBuild`, and `% nova test` now stay on Nova's supported `Pester 5.x` range and stop with a clear error instead of trying to run with an installed `Pester 6.x` version.
18+
1719
### Security
1820

1921
## [3.3.0] - 2026-06-16

docs/NovaModuleTools/en-US/Invoke-NovaTest.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ PS> Invoke-NovaTest [[-TagFilter] <string[]>] [[-ExcludeTagFilter] <string[]>]
3232

3333
`Invoke-NovaTest` reads the Pester configuration from `project.json`, discovers the repository unit-test files, and runs the managed Nova test workflow without rebuilding the project first.
3434

35+
Nova resolves a supported installed `Pester` version from `5.7.1` through `5.10.0` for the managed test workflow and stops with a clear dependency error when only unsupported `Pester 6.x` versions are available.
36+
3537
The unit-test workflow writes NUnit XML to `artifacts/UnitTestResults.xml`.
3638

3739
When `Pester.CodeCoverage.Enabled` is `true`, Nova also writes JaCoCo coverage to `artifacts/coverage.xml` and fails the run when the measured percentage is lower than `Pester.CodeCoverage.CoveragePercentTarget`.

docs/NovaModuleTools/en-US/Test-NovaBuild.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,13 @@ PS> Test-NovaBuild [[-TagFilter] <string[]>] [[-ExcludeTagFilter] <string[]>]
3131

3232
`Test-NovaBuild` reads the Pester configuration from `project.json`, discovers the build-validation integration tests for the current project, and runs the managed Nova test workflow against the built-module validation surface.
3333

34+
Nova resolves a supported installed `Pester` version from `5.7.1` through `5.10.0` for the managed test workflow and stops with a clear dependency error when only unsupported `Pester 6.x` versions are available.
35+
3436
This build-validation flow writes NUnit XML to `artifacts/TestResults.xml`.
3537

3638
Unlike `Invoke-NovaTest`, this command does not enforce source-coverage targets. Use `Invoke-NovaTest` for the unit-test and code-coverage workflow, and use `Test-NovaBuild` when you need build-validation integration coverage that reflects the built module path.
3739

38-
If the current project does not contain any `*.Integration.Tests.ps1` files, `Test-NovaBuild` stops with a Nova-native error that explains the expected naming and reminds you to use `Invoke-NovaTest` for unit tests.
40+
If the current project does not contain any `*.Integration.Tests.ps1` files, `Test-NovaBuild` writes an actionable warning that explains the expected naming and reminds you to use `Invoke-NovaTest` for unit tests.
3941

4042
`-OverrideWarning` lets the nested build-validation flow continue even if the `src/public` layout guard reports zero or multiple top-level functions in a public file.
4143

@@ -271,5 +273,5 @@ Returns the Pester result object from the managed build-validation run.
271273

272274
## RELATED LINKS
273275

274-
[Invoke-NovaTest](Invoke-NovaTest.md)
275-
[Invoke-NovaBuild](Invoke-NovaBuild.md)
276+
- [Invoke-NovaTest](./Invoke-NovaTest.md)
277+
- [Invoke-NovaBuild](./Invoke-NovaBuild.md)

scripts/build/ci/Invoke-NovaModuleToolsCI.ps1

Lines changed: 42 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,29 +16,58 @@ function Copy-NovaModuleToolsArtifactIfPresent {
1616
}
1717
}
1818

19+
function ConvertTo-NovaSingleQuotedPowerShellLiteral {
20+
param(
21+
[Parameter(Mandatory)][string]$Value
22+
)
23+
24+
return "'$($Value.Replace("'", "''") )'"
25+
}
26+
27+
function Get-NovaModuleToolsValidationCommand {
28+
param(
29+
[Parameter(Mandatory)][string]$BuiltModulePath,
30+
[Parameter(Mandatory)][string]$CommandName,
31+
[string[]]$ExcludeTag = @()
32+
)
33+
34+
$commandLine = $CommandName
35+
if (@($ExcludeTag).Count -gt 0) {
36+
$excludeTagLiteral = @($ExcludeTag | ForEach-Object {ConvertTo-NovaSingleQuotedPowerShellLiteral -Value ([string]$_)}) -join ', '
37+
$commandLine += " -ExcludeTagFilter @($excludeTagLiteral)"
38+
}
39+
40+
return "Import-Module $( ConvertTo-NovaSingleQuotedPowerShellLiteral -Value $BuiltModulePath ) -Force -ErrorAction Stop; $commandLine"
41+
}
42+
43+
function Invoke-NovaModuleToolsFreshValidationCommand {
44+
param(
45+
[Parameter(Mandatory)][string]$Command
46+
)
47+
48+
& pwsh -NoLogo -NoProfile -Command $Command
49+
if ($LASTEXITCODE -ne 0) {
50+
throw "Validation command failed: $Command"
51+
}
52+
}
53+
1954
$repoRoot = (Resolve-Path -LiteralPath (Join-Path $PSScriptRoot '..' '..' '..')).Path
2055
Set-Location $repoRoot
2156
New-Item -ItemType Directory -Path $OutputDirectory -Force | Out-Null
2257

2358
Import-Module NovaModuleTools -ErrorAction Stop
2459

25-
Invoke-NovaBuild
26-
27-
$projectInfo = Get-NovaProjectInfo
28-
$builtModulePath = $projectInfo.OutputModuleDir
29-
Remove-Module $projectInfo.ProjectName -ErrorAction SilentlyContinue
30-
Import-Module $builtModulePath -Force
3160
$projectInfo = Get-NovaProjectInfo
61+
Invoke-NovaBuild
62+
$builtModulePath = Join-Path $projectInfo.OutputModuleDir "$( $projectInfo.ProjectName ).psd1"
3263

3364
$novaModuleToolsTestFailed = $false
3465
try {
35-
if (@($ExcludeTag).Count -gt 0) {
36-
Invoke-NovaTest -ExcludeTagFilter $ExcludeTag
37-
Test-NovaBuild -ExcludeTagFilter $ExcludeTag
38-
} else {
39-
Invoke-NovaTest
40-
Test-NovaBuild
41-
}
66+
$unitTestCommand = Get-NovaModuleToolsValidationCommand -BuiltModulePath $builtModulePath -CommandName 'Invoke-NovaTest' -ExcludeTag $ExcludeTag
67+
Invoke-NovaModuleToolsFreshValidationCommand -Command $unitTestCommand
68+
69+
$buildValidationCommand = Get-NovaModuleToolsValidationCommand -BuiltModulePath $builtModulePath -CommandName 'Test-NovaBuild' -ExcludeTag $ExcludeTag
70+
Invoke-NovaModuleToolsFreshValidationCommand -Command $buildValidationCommand
4271
} catch {
4372
$novaModuleToolsTestFailed = $true
4473
Write-Warning "Nova test workflow failed: $( $_.Exception.Message )"

src/private/quality/GetNovaTestWorkflowContext.ps1

Lines changed: 184 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,26 +12,205 @@ function Get-NovaTestWorkflowOperation {
1212
}
1313

1414
function Assert-NovaPesterAvailable {
15+
[CmdletBinding()]
16+
param(
17+
[AllowNull()][pscustomobject]$ProjectInfo
18+
)
19+
20+
$moduleSpecification = Get-NovaSupportedPesterModuleSpecification -ProjectInfo $ProjectInfo
21+
Import-NovaSupportedPesterModule -ModuleSpecification $moduleSpecification
22+
return $moduleSpecification
23+
}
24+
25+
function Get-NovaSupportedPesterModuleSpecification {
26+
[CmdletBinding()]
27+
param(
28+
[AllowNull()][pscustomobject]$ProjectInfo
29+
)
30+
31+
$moduleRequirement = Get-NovaPesterModuleRequirement -ProjectInfo $ProjectInfo
32+
$availableModule = @(Get-LoadedNovaPesterModule -ModuleRequirement $moduleRequirement)
33+
if ($availableModule.Count -eq 0) {
34+
$availableModule = @(Get-AvailableNovaPesterModule -ModuleRequirement $moduleRequirement)
35+
}
36+
37+
if ($availableModule.Count -eq 0) {
38+
Stop-NovaOperation -Message (Get-NovaPesterDependencyMessage -ModuleRequirement $moduleRequirement) -ErrorId 'Nova.Dependency.PesterDependencyMissing' -Category ResourceUnavailable -TargetObject 'Pester'
39+
}
40+
41+
$selectedVersion = [version]$availableModule[0].Version
42+
return [pscustomobject]@{
43+
Name = 'Pester'
44+
MinimumVersion = $moduleRequirement.MinimumVersion
45+
MaximumVersion = $moduleRequirement.MaximumVersion
46+
SelectedVersion = $selectedVersion
47+
FullyQualifiedName = @{
48+
ModuleName = 'Pester'
49+
RequiredVersion = [string]$selectedVersion
50+
}
51+
}
52+
}
53+
54+
function Get-NovaSupportedPesterRange {
1555
[CmdletBinding()]
1656
param()
1757

18-
if (-not (Get-Module -Name Pester -ListAvailable)) {
19-
Stop-NovaOperation -Message 'The module Pester must be installed to run Nova tests. Install Pester 5.7.1 and try again.' -ErrorId 'Nova.Dependency.PesterDependencyMissing' -Category ResourceUnavailable -TargetObject 'Pester'
58+
return [pscustomobject]@{
59+
Name = 'Pester'
60+
MinimumVersion = [version]'5.7.1'
61+
MaximumVersion = [version]'5.10.0'
2062
}
2163
}
2264

65+
function Get-NovaPesterModuleRequirement {
66+
[CmdletBinding()]
67+
param(
68+
[AllowNull()][pscustomobject]$ProjectInfo
69+
)
70+
71+
$supportedRequirement = Get-NovaSupportedPesterRange
72+
$manifestSettings = Get-NovaPesterSettingValue -InputObject $ProjectInfo -Name 'Manifest'
73+
$requiredModules = @(Get-NovaPesterSettingValue -InputObject $manifestSettings -Name 'RequiredModules')
74+
$pesterModule = $requiredModules |
75+
Where-Object {(Get-NovaPesterSettingValue -InputObject $_ -Name 'ModuleName') -eq 'Pester'} |
76+
Select-Object -First 1
77+
78+
if ($null -ne $pesterModule) {
79+
$manifestRequirement = [pscustomobject]@{
80+
Name = 'Pester'
81+
MinimumVersion = [version](Get-NovaPesterVersionText -InputObject $pesterModule -Name 'ModuleVersion' -DefaultValue '5.7.1')
82+
MaximumVersion = [version](Get-NovaPesterVersionText -InputObject $pesterModule -Name 'MaximumVersion' -DefaultValue '5.10.0')
83+
}
84+
Assert-NovaPesterModuleRequirementSupported -ModuleRequirement $manifestRequirement -SupportedRequirement $supportedRequirement
85+
}
86+
87+
return $supportedRequirement
88+
}
89+
90+
function Assert-NovaPesterModuleRequirementSupported {
91+
[CmdletBinding()]
92+
param(
93+
[Parameter(Mandatory)][pscustomobject]$ModuleRequirement,
94+
[Parameter(Mandatory)][pscustomobject]$SupportedRequirement
95+
)
96+
97+
if ($ModuleRequirement.MinimumVersion -eq $SupportedRequirement.MinimumVersion -and $ModuleRequirement.MaximumVersion -eq $SupportedRequirement.MaximumVersion) {
98+
return
99+
}
100+
101+
$supportedVersionText = Get-NovaPesterVersionRangeText -ModuleRequirement $SupportedRequirement
102+
$declaredVersionText = Get-NovaPesterVersionRangeText -ModuleRequirement $ModuleRequirement
103+
Stop-NovaOperation -Message "Nova tests support Pester only from $supportedVersionText. project.json declares Pester from $declaredVersionText. Update project.json to match Nova's supported range and try again." -ErrorId 'Nova.Dependency.UnsupportedPesterVersionRequirement' -Category InvalidData -TargetObject 'project.json'
104+
}
105+
106+
function Get-NovaPesterVersionText {
107+
[CmdletBinding()]
108+
param(
109+
[AllowNull()][object]$InputObject,
110+
[Parameter(Mandatory)][string]$Name,
111+
[Parameter(Mandatory)][string]$DefaultValue
112+
)
113+
114+
$value = [string](Get-NovaPesterSettingValue -InputObject $InputObject -Name $Name)
115+
if ( [string]::IsNullOrWhiteSpace($value)) {
116+
return $DefaultValue
117+
}
118+
119+
return $value
120+
}
121+
122+
function Get-NovaPesterVersionRangeText {
123+
[CmdletBinding()]
124+
param(
125+
[Parameter(Mandatory)][pscustomobject]$ModuleRequirement
126+
)
127+
128+
return "$( $ModuleRequirement.MinimumVersion ) through $( $ModuleRequirement.MaximumVersion )"
129+
}
130+
131+
function Get-AvailableNovaPesterModule {
132+
[CmdletBinding()]
133+
param(
134+
[Parameter(Mandatory)][pscustomobject]$ModuleRequirement
135+
)
136+
137+
return @(
138+
Get-Module -Name Pester -ListAvailable |
139+
Where-Object {Test-NovaPesterModuleVersionSupported -Version $_.Version -ModuleRequirement $ModuleRequirement} |
140+
Sort-Object Version -Descending
141+
)
142+
}
143+
144+
function Get-LoadedNovaPesterModule {
145+
[CmdletBinding()]
146+
param(
147+
[Parameter(Mandatory)][pscustomobject]$ModuleRequirement
148+
)
149+
150+
return @(
151+
Get-Module -Name Pester |
152+
Where-Object {Test-NovaPesterModuleVersionSupported -Version $_.Version -ModuleRequirement $ModuleRequirement} |
153+
Sort-Object Version -Descending
154+
)
155+
}
156+
157+
function Test-NovaPesterModuleVersionSupported {
158+
[CmdletBinding()]
159+
param(
160+
[AllowNull()][object]$Version,
161+
[Parameter(Mandatory)][pscustomobject]$ModuleRequirement
162+
)
163+
164+
if ($null -eq $Version) {
165+
return $false
166+
}
167+
168+
$resolvedVersion = [version]$Version
169+
return $resolvedVersion -ge $ModuleRequirement.MinimumVersion -and $resolvedVersion -le $ModuleRequirement.MaximumVersion
170+
}
171+
172+
function Get-NovaPesterDependencyMessage {
173+
[CmdletBinding()]
174+
param(
175+
[Parameter(Mandatory)][pscustomobject]$ModuleRequirement
176+
)
177+
178+
$supportedVersionText = Get-NovaPesterVersionRangeText -ModuleRequirement $ModuleRequirement
179+
$installedVersion = @(
180+
Get-Module -Name Pester -ListAvailable |
181+
Sort-Object Version -Descending |
182+
ForEach-Object {[string]$_.Version} |
183+
Select-Object -Unique
184+
)
185+
if ($installedVersion.Count -eq 0) {
186+
return "The module Pester must be installed to run Nova tests. Install a supported Pester version from $supportedVersionText and try again."
187+
}
188+
189+
return "Nova tests require Pester from $supportedVersionText. Installed versions: $( $installedVersion -join ', ' ). Install a supported Pester 5.x version and try again."
190+
}
191+
192+
function Import-NovaSupportedPesterModule {
193+
[CmdletBinding()]
194+
param(
195+
[Parameter(Mandatory)][pscustomobject]$ModuleSpecification
196+
)
197+
198+
Import-Module -FullyQualifiedName $ModuleSpecification.FullyQualifiedName -Force -ErrorAction Stop | Out-Null
199+
}
200+
23201
function Get-NovaTestWorkflowContext {
24202
[CmdletBinding()]
25203
param(
26204
[Parameter(Mandatory)][hashtable]$TestOption,
27205
[Parameter(Mandatory)][hashtable]$BoundParameters
28206
)
29207

30-
Test-ProjectSchema | Out-Null
31-
Assert-NovaPesterAvailable
32208
$projectInfo = Get-NovaProjectInfo
209+
Test-ProjectSchema | Out-Null
210+
$pesterModuleSpecification = Assert-NovaPesterAvailable -ProjectInfo $projectInfo
33211
$workflowProfile = Get-NovaTestWorkflowProfile -TestOption $TestOption
34212
$pesterConfig = New-PesterConfiguration -Hashtable $projectInfo.Pester
213+
Add-Member -InputObject $pesterConfig -MemberType NoteProperty -Name PesterModuleSpecification -Value $pesterModuleSpecification -Force
35214
$coverageConfiguration = Get-NovaPesterCoverageConfigurationState -ProjectInfo $projectInfo -CoverageEnabled:$workflowProfile.CoverageEnabled
36215
$pesterConfig.CodeCoverage.Enabled = $coverageConfiguration.Enabled
37216
$pesterConfig.CodeCoverage.Path = $coverageConfiguration.Path
@@ -62,6 +241,7 @@ function Get-NovaTestWorkflowContext {
62241
CommandName = $workflowProfile.CommandName
63242
OverrideWarningRequested = $BoundParameters.ContainsKey('OverrideWarning') -and [bool]$BoundParameters.OverrideWarning
64243
ProjectInfo = $projectInfo
244+
PesterModuleSpecification = $pesterModuleSpecification
65245
PesterSettings = Get-NovaTestWorkflowPesterConfiguration -ProjectPesterSettings $projectInfo.Pester -CoverageEnabled:$workflowProfile.CoverageEnabled
66246
PesterConfig = $pesterConfig
67247
TestResultPath = $testResultPath

0 commit comments

Comments
 (0)