Skip to content

Commit 9b49425

Browse files
tablackburnclaude
andcommitted
feat(tests): SemVer-aware dependency version constraints
Replace the single-shape Manifest.tests.ps1 dependency check with constraint- specific assertions and a SemVer comparison helper module. - tests/ManifestHelpers.psm1: new module exporting Test-VersionConstraint, with SemVer 2.0.0 prerelease ordering, .NET Version normalization, and Equal / GreaterOrEqual / LessOrEqual constraint modes. - tests/Manifest.tests.ps1: differentiate ModuleVersion (minimum), RequiredVersion (exact), and MaximumVersion (maximum) checks; accept both string and hashtable shapes for entries in requirements.psd1; detect duplicate RequiredModules entries; skip with a reason for plain-string dependencies. Preserves the existing BHBuildOutput override that points at Output/<Module>/<Version>/. Validated end-to-end against an initialized SmokeTest module: 30 passed, 0 failed, 2 -Skip'd. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 73810c5 commit 9b49425

2 files changed

Lines changed: 512 additions & 7 deletions

File tree

tests/Manifest.tests.ps1

Lines changed: 133 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,31 @@
2424
'dependencies',
2525
Justification = 'false positive'
2626
)]
27+
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
28+
'PSUseDeclaredVarsMoreThanAssignments',
29+
'dependencyName',
30+
Justification = 'false positive'
31+
)]
32+
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
33+
'PSUseDeclaredVarsMoreThanAssignments',
34+
'dependencyRawData',
35+
Justification = 'false positive'
36+
)]
37+
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
38+
'PSUseDeclaredVarsMoreThanAssignments',
39+
'manifestRawData',
40+
Justification = 'false positive'
41+
)]
42+
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
43+
'PSUseDeclaredVarsMoreThanAssignments',
44+
'requirementsVersionSkipReason',
45+
Justification = 'false positive'
46+
)]
47+
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
48+
'PSUseDeclaredVarsMoreThanAssignments',
49+
'requirementsVersion',
50+
Justification = 'false positive'
51+
)]
2752
param()
2853

2954
BeforeDiscovery {
@@ -85,7 +110,19 @@ BeforeAll {
85110
ErrorAction = 'Stop'
86111
WarningAction = 'SilentlyContinue'
87112
}
113+
$importDataFileParameters = @{
114+
Path = $moduleManifestPath
115+
ErrorAction = 'Stop'
116+
WarningAction = 'SilentlyContinue'
117+
}
88118
$manifestData = Test-ModuleManifest @testModuleManifestParameters
119+
$manifestRawData = Import-PowerShellDataFile @importDataFileParameters
120+
121+
# Import ManifestHelpers.psm1 for SemVer helper functions
122+
Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath 'ManifestHelpers.psm1') -Verbose:$false -Force
123+
124+
$requirementsPath = Join-Path -Path $env:BHProjectPath -ChildPath 'requirements.psd1'
125+
$requirements = Import-PowerShellDataFile -Path $requirementsPath -ErrorAction Stop
89126

90127
# Parse the version from the changelog
91128
$changelogPath = Join-Path -Path $Env:BHProjectPath -ChildPath 'CHANGELOG.md'
@@ -143,19 +180,108 @@ Describe 'Module manifest' {
143180
}
144181

145182
Context 'Module Dependency' -ForEach $dependencies {
146-
# This ensures we keep our dependant modules in sync between the manifest file and the requirements
183+
# This ensures we keep our dependent modules in sync between the manifest file and the requirements
147184
# script used to bootstrap and test.
148185
BeforeAll {
149-
$requirementsPath = Join-Path -Path $Env:BHProjectPath -Child 'requirements.psd1'
150-
$requirements = Import-PowerShellDataFile $requirementsPath
186+
$dependencyName = $_.Name
187+
$dependencyRawData = $manifestRawData.RequiredModules | Where-Object {
188+
$_ -eq $dependencyName -or $_.ModuleName -eq $dependencyName
189+
}
190+
# Ensure exactly one match - duplicates should fail, not silently skip
191+
if (@($dependencyRawData).Count -gt 1) {
192+
throw "Duplicate RequiredModules entry found for '$dependencyName'"
193+
}
194+
# Handle plain-string module references (not hashtables with version info)
195+
if ($dependencyRawData -isnot [hashtable]) {
196+
$dependencyRawData = $null
197+
}
198+
199+
# Extract version from requirements.psd1 (shared logic for all version constraint tests)
200+
$requirementsVersionSkipReason = $null
201+
$requirementsVersion = $null
202+
203+
if (-not $requirements.ContainsKey($dependencyName)) {
204+
$requirementsVersionSkipReason = 'dependency not found in requirements.psd1'
205+
} elseif ($requirements.Item($dependencyName) -is [string]) {
206+
# Plain string format: 'ModuleName' = '1.2.3'
207+
$requirementsVersion = $requirements.Item($dependencyName)
208+
} elseif ($requirements.Item($dependencyName) -is [hashtable] -and $requirements.Item($dependencyName).ContainsKey('Version')) {
209+
# Hashtable format: 'ModuleName' = @{ Version = '1.2.3' }
210+
$requirementsVersion = $requirements.Item($dependencyName).Version
211+
} else {
212+
# Invalid format
213+
$requirementsVersionSkipReason = "requirements.psd1 entry for '$dependencyName' must be a string or hashtable with a Version key"
214+
}
215+
}
216+
217+
It '<_.Name> exists in requirements.psd1' {
218+
$requirements.ContainsKey($dependencyName) | Should -BeTrue
219+
}
220+
221+
It '<_.Name> uses at least one version key' {
222+
if ($null -eq $dependencyRawData) {
223+
Set-ItResult -Skipped -Because 'Plain-string module reference without version constraints'
224+
}
225+
226+
# Valid dependency version keys
227+
$validDependencyKeys = @(
228+
'ModuleVersion' # Specifies a minimum acceptable version of the module
229+
'RequiredVersion' # Specifies an exact, required version of the module
230+
'MaximumVersion' # Specifies a maximum acceptable version of the module
231+
)
232+
$dependencyKeysUsed = $dependencyRawData.Keys | Where-Object { $_ -in $validDependencyKeys }
233+
$dependencyKeysUsed.Count | Should -BeGreaterThan 0
234+
}
235+
236+
It '<_.Name> has a matching required version in requirements.psd1' {
237+
if ($null -eq $dependencyRawData -or -not $dependencyRawData.ContainsKey('RequiredVersion')) {
238+
Set-ItResult -Skipped -Because 'No RequiredVersion specified in the manifest'
239+
}
240+
241+
if ($requirementsVersionSkipReason) {
242+
Set-ItResult -Skipped -Because $requirementsVersionSkipReason
243+
}
244+
245+
$constraintParameters = @{
246+
ManifestVersion = $dependencyRawData.RequiredVersion
247+
RequirementsVersion = $requirementsVersion
248+
Constraint = 'Equal'
249+
}
250+
Test-VersionConstraint @constraintParameters | Should -BeTrue
151251
}
152252

153-
It '<_.Name> exists in Requirements.psd1' {
154-
$requirements.ContainsKey($_.Name) | Should -BeTrue
253+
It '<_.Name> has a maximum version greater than or equal to requirements.psd1' {
254+
if ($null -eq $dependencyRawData -or -not $dependencyRawData.ContainsKey('MaximumVersion')) {
255+
Set-ItResult -Skipped -Because 'No MaximumVersion specified in the manifest'
256+
}
257+
258+
if ($requirementsVersionSkipReason) {
259+
Set-ItResult -Skipped -Because $requirementsVersionSkipReason
260+
}
261+
262+
$constraintParameters = @{
263+
ManifestVersion = $dependencyRawData.MaximumVersion
264+
RequirementsVersion = $requirementsVersion
265+
Constraint = 'GreaterOrEqual'
266+
}
267+
Test-VersionConstraint @constraintParameters | Should -BeTrue
155268
}
156269

157-
It '<_.Name> has matching version in the Requirements.psd1' {
158-
[Version]$requirements.Item($_.Name).Version | Should -Be $_.Version
270+
It '<_.Name> has a minimum version at or below requirements.psd1' {
271+
if ($null -eq $dependencyRawData -or -not $dependencyRawData.ContainsKey('ModuleVersion')) {
272+
Set-ItResult -Skipped -Because 'No ModuleVersion specified in the manifest'
273+
}
274+
275+
if ($requirementsVersionSkipReason) {
276+
Set-ItResult -Skipped -Because $requirementsVersionSkipReason
277+
}
278+
279+
$constraintParameters = @{
280+
ManifestVersion = $dependencyRawData.ModuleVersion
281+
RequirementsVersion = $requirementsVersion
282+
Constraint = 'LessOrEqual'
283+
}
284+
Test-VersionConstraint @constraintParameters | Should -BeTrue
159285
}
160286
}
161287
}

0 commit comments

Comments
 (0)