microsoft/hve-core

Public

mirrored fromhttps://github.com/microsoft/hve-coreAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
copilot/research-single-dynamic-rewrite

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

scripts/tests/security/Test-ActionVersionConsistency.Tests.ps1

564lines · modecode

1#Requires -Modules Pester
2# Copyright (c) Microsoft Corporation.
3# SPDX-License-Identifier: MIT
4
5using module ../../security/Modules/SecurityClasses.psm1
6
7<#
8.SYNOPSIS
9 Pester tests for Test-ActionVersionConsistency.ps1 functions.
10
11.DESCRIPTION
12 Tests version consistency checking functions without executing the main script.
13 Uses HVE_SKIP_MAIN environment variable to prevent main block execution.
14#>
15
16BeforeAll {
17 $scriptPath = Join-Path $PSScriptRoot '../../security/Test-ActionVersionConsistency.ps1'
18 $script:OriginalSkipMain = $env:HVE_SKIP_MAIN
19 $env:HVE_SKIP_MAIN = '1'
20 . $scriptPath
21
22 $mockPath = Join-Path $PSScriptRoot '../Mocks/GitMocks.psm1'
23 Import-Module $mockPath -Force
24
25 Save-CIEnvironment
26
27 $script:FixturesPath = Join-Path $PSScriptRoot '../Fixtures/Workflows'
28}
29
30AfterAll {
31 Restore-CIEnvironment
32 $env:HVE_SKIP_MAIN = $script:OriginalSkipMain
33}
34
35Describe 'Write-ConsistencyLog' -Tag 'Unit' {
36 Context 'Log output' {
37 It 'Does not throw for Info level' {
38 { Write-ConsistencyLog -Message 'Test message' -Level Info } | Should -Not -Throw
39 }
40
41 It 'Does not throw for Warning level' {
42 { Write-ConsistencyLog -Message 'Warning message' -Level Warning } | Should -Not -Throw
43 }
44
45 It 'Does not throw for Error level' {
46 { Write-ConsistencyLog -Message 'Error message' -Level Error } | Should -Not -Throw
47 }
48
49 It 'Does not throw for Success level' {
50 { Write-ConsistencyLog -Message 'Success message' -Level Success } | Should -Not -Throw
51 }
52
53 It 'Defaults to Info level when not specified' {
54 { Write-ConsistencyLog -Message 'Default level test' } | Should -Not -Throw
55 }
56 }
57}
58
59Describe 'Get-ActionVersionViolations' -Tag 'Unit' {
60 Context 'Clean workflow (no violations)' {
61 It 'Returns zero violations for fully commented workflow' {
62 $testPath = Join-Path $TestDrive 'clean-workflow-test'
63 New-Item -ItemType Directory -Path $testPath -Force | Out-Null
64 Copy-Item -Path (Join-Path $script:FixturesPath 'consistent-versions.yml') -Destination $testPath
65
66 $result = Get-ActionVersionViolations -WorkflowPath $testPath
67 $result.Violations | Should -BeNullOrEmpty
68 }
69
70 It 'Returns correct TotalActions count' {
71 $testPath = Join-Path $TestDrive 'consistent-test'
72 New-Item -ItemType Directory -Path $testPath -Force | Out-Null
73 Copy-Item -Path (Join-Path $script:FixturesPath 'consistent-versions.yml') -Destination $testPath
74
75 $result = Get-ActionVersionViolations -WorkflowPath $testPath
76 $result.TotalActions | Should -Be 3
77 }
78
79 It 'Returns empty violations array for pinned-workflow.yml' {
80 $testPath = Join-Path $TestDrive 'pinned-test'
81 New-Item -ItemType Directory -Path $testPath -Force | Out-Null
82 Copy-Item -Path (Join-Path $script:FixturesPath 'pinned-workflow.yml') -Destination $testPath
83
84 $result = Get-ActionVersionViolations -WorkflowPath $testPath
85 $result.Violations.Count | Should -Be 0
86 }
87 }
88
89 Context 'Missing version comment (single)' {
90 It 'Detects SHA without version comment' {
91 $testPath = Join-Path $TestDrive 'missing-single'
92 New-Item -ItemType Directory -Path $testPath -Force | Out-Null
93 Copy-Item -Path (Join-Path $script:FixturesPath 'missing-version-comment.yml') -Destination $testPath
94
95 $result = Get-ActionVersionViolations -WorkflowPath $testPath
96 $missingComments = $result.Violations | Where-Object { $_.ViolationType -eq 'MissingVersionComment' }
97 $missingComments.Count | Should -Be 1
98 }
99
100 It 'Returns correct violation properties for missing comment' {
101 $testPath = Join-Path $TestDrive 'missing-props'
102 New-Item -ItemType Directory -Path $testPath -Force | Out-Null
103 Copy-Item -Path (Join-Path $script:FixturesPath 'missing-version-comment.yml') -Destination $testPath
104
105 $result = Get-ActionVersionViolations -WorkflowPath $testPath
106 $violation = $result.Violations | Where-Object { $_.ViolationType -eq 'MissingVersionComment' } | Select-Object -First 1
107
108 $violation.Type | Should -Be 'github-actions'
109 $violation.Severity | Should -Be 'Medium'
110 $violation.Name | Should -Be 'actions/checkout'
111 $violation.Description | Should -Match 'missing version comment'
112 }
113
114 It 'Includes FullSha in violation Metadata' {
115 $testPath = Join-Path $TestDrive 'missing-meta'
116 New-Item -ItemType Directory -Path $testPath -Force | Out-Null
117 Copy-Item -Path (Join-Path $script:FixturesPath 'missing-version-comment.yml') -Destination $testPath
118
119 $result = Get-ActionVersionViolations -WorkflowPath $testPath
120 $violation = $result.Violations | Where-Object { $_.ViolationType -eq 'MissingVersionComment' } | Select-Object -First 1
121
122 $violation.Metadata.FullSha | Should -Be 'a5ac7e51b41094c92402da3b24376905380afc29'
123 }
124 }
125
126 Context 'Missing version comments (multiple)' {
127 It 'Detects all missing version comments' {
128 $testPath = Join-Path $TestDrive 'missing-multiple'
129 New-Item -ItemType Directory -Path $testPath -Force | Out-Null
130 Copy-Item -Path (Join-Path $script:FixturesPath 'multiple-missing-comments.yml') -Destination $testPath
131
132 $result = Get-ActionVersionViolations -WorkflowPath $testPath
133 $missingComments = $result.Violations | Where-Object { $_.ViolationType -eq 'MissingVersionComment' }
134 $missingComments.Count | Should -Be 3
135 }
136
137 It 'Returns unique line numbers for each violation' {
138 $testPath = Join-Path $TestDrive 'missing-lines'
139 New-Item -ItemType Directory -Path $testPath -Force | Out-Null
140 Copy-Item -Path (Join-Path $script:FixturesPath 'multiple-missing-comments.yml') -Destination $testPath
141
142 $result = Get-ActionVersionViolations -WorkflowPath $testPath
143 $lines = $result.Violations | ForEach-Object { $_.Line } | Sort-Object -Unique
144 $lines.Count | Should -Be $result.Violations.Count
145 }
146 }
147
148 Context 'Version mismatch detection' {
149 It 'Detects version mismatch for same SHA across files' {
150 $testPath = Join-Path $TestDrive 'mismatch-test'
151 New-Item -ItemType Directory -Path $testPath -Force | Out-Null
152 Copy-Item -Path (Join-Path $script:FixturesPath 'version-mismatch-a.yml') -Destination $testPath
153 Copy-Item -Path (Join-Path $script:FixturesPath 'version-mismatch-b.yml') -Destination $testPath
154
155 $result = Get-ActionVersionViolations -WorkflowPath $testPath
156 $mismatches = $result.Violations | Where-Object { $_.ViolationType -eq 'VersionMismatch' }
157 $mismatches.Count | Should -Be 1
158 }
159
160 It 'Returns High severity for version mismatch' {
161 $testPath = Join-Path $TestDrive 'mismatch-severity'
162 New-Item -ItemType Directory -Path $testPath -Force | Out-Null
163 Copy-Item -Path (Join-Path $script:FixturesPath 'version-mismatch-a.yml') -Destination $testPath
164 Copy-Item -Path (Join-Path $script:FixturesPath 'version-mismatch-b.yml') -Destination $testPath
165
166 $result = Get-ActionVersionViolations -WorkflowPath $testPath
167 $mismatch = $result.Violations | Where-Object { $_.ViolationType -eq 'VersionMismatch' } | Select-Object -First 1
168 $mismatch.Severity | Should -Be 'High'
169 }
170
171 It 'Includes conflicting versions in Metadata' {
172 $testPath = Join-Path $TestDrive 'mismatch-meta'
173 New-Item -ItemType Directory -Path $testPath -Force | Out-Null
174 Copy-Item -Path (Join-Path $script:FixturesPath 'version-mismatch-a.yml') -Destination $testPath
175 Copy-Item -Path (Join-Path $script:FixturesPath 'version-mismatch-b.yml') -Destination $testPath
176
177 $result = Get-ActionVersionViolations -WorkflowPath $testPath
178 $mismatch = $result.Violations | Where-Object { $_.ViolationType -eq 'VersionMismatch' } | Select-Object -First 1
179 $mismatch.Metadata.ConflictingVersions | Should -Match 'v4\.1\.0'
180 $mismatch.Metadata.ConflictingVersions | Should -Match 'v4\.1\.6'
181 }
182
183 It 'Includes affected locations in Metadata' {
184 $testPath = Join-Path $TestDrive 'mismatch-locations'
185 New-Item -ItemType Directory -Path $testPath -Force | Out-Null
186 Copy-Item -Path (Join-Path $script:FixturesPath 'version-mismatch-a.yml') -Destination $testPath
187 Copy-Item -Path (Join-Path $script:FixturesPath 'version-mismatch-b.yml') -Destination $testPath
188
189 $result = Get-ActionVersionViolations -WorkflowPath $testPath
190 $mismatch = $result.Violations | Where-Object { $_.ViolationType -eq 'VersionMismatch' } | Select-Object -First 1
191 $mismatch.Metadata.AffectedLocations.Count | Should -Be 2
192 }
193 }
194
195 Context 'Non-existent path handling' {
196 BeforeAll {
197 # Use platform-agnostic path that guaranteed doesn't exist
198 $script:NonExistentPath = Join-Path ([System.IO.Path]::GetTempPath()) "nonexistent-$(New-Guid)"
199 }
200
201 It 'Returns empty violations for non-existent path' {
202 $result = Get-ActionVersionViolations -WorkflowPath $script:NonExistentPath
203 $result.Violations | Should -BeNullOrEmpty
204 }
205
206 It 'Returns zero TotalActions for non-existent path' {
207 $result = Get-ActionVersionViolations -WorkflowPath $script:NonExistentPath
208 $result.TotalActions | Should -Be 0
209 }
210
211 It 'Returns empty ShaVersionMap for non-existent path' {
212 $result = Get-ActionVersionViolations -WorkflowPath $script:NonExistentPath
213 $result.ShaVersionMap.Count | Should -Be 0
214 }
215 }
216
217 Context 'Empty directory handling' {
218 It 'Returns empty violations for empty directory' {
219 $emptyPath = Join-Path $TestDrive 'empty-workflows'
220 New-Item -ItemType Directory -Path $emptyPath -Force | Out-Null
221
222 $result = Get-ActionVersionViolations -WorkflowPath $emptyPath
223 $result.Violations | Should -BeNullOrEmpty
224 }
225
226 It 'Returns zero TotalActions for empty directory' {
227 $emptyPath = Join-Path $TestDrive 'empty-workflows-count'
228 New-Item -ItemType Directory -Path $emptyPath -Force | Out-Null
229
230 $result = Get-ActionVersionViolations -WorkflowPath $emptyPath
231 $result.TotalActions | Should -Be 0
232 }
233 }
234
235 Context 'Mixed violations (both types in one scan)' {
236 It 'Detects both mismatch and missing comment violations' {
237 $testPath = Join-Path $TestDrive 'mixed-violations'
238 New-Item -ItemType Directory -Path $testPath -Force | Out-Null
239 Copy-Item -Path (Join-Path $script:FixturesPath 'version-mismatch-a.yml') -Destination $testPath
240 Copy-Item -Path (Join-Path $script:FixturesPath 'version-mismatch-b.yml') -Destination $testPath
241 Copy-Item -Path (Join-Path $script:FixturesPath 'missing-version-comment.yml') -Destination $testPath
242
243 $result = Get-ActionVersionViolations -WorkflowPath $testPath
244 $mismatches = @($result.Violations | Where-Object { $_.ViolationType -eq 'VersionMismatch' })
245 $missingComments = @($result.Violations | Where-Object { $_.ViolationType -eq 'MissingVersionComment' })
246
247 $mismatches.Count | Should -BeGreaterThan 0
248 $missingComments.Count | Should -BeGreaterThan 0
249 }
250 }
251}
252
253Describe 'Export-ConsistencyReport' -Tag 'Unit' {
254 BeforeEach {
255 $script:TestOutputPath = Join-Path $TestDrive 'report'
256 New-Item -ItemType Directory -Path $script:TestOutputPath -Force | Out-Null
257
258 # Create mock violations
259 $script:MockViolations = @()
260
261 $v1 = [DependencyViolation]::new()
262 $v1.File = 'workflow.yml'
263 $v1.Line = 10
264 $v1.Type = 'github-actions'
265 $v1.Name = 'actions/checkout'
266 $v1.Version = 'a5ac7e5'
267 $v1.Severity = 'Medium'
268 $v1.ViolationType = 'MissingVersionComment'
269 $v1.Description = 'SHA-pinned action missing version comment'
270 $v1.Remediation = 'Add version comment'
271 $v1.Metadata = @{ FullSha = 'a5ac7e51b41094c92402da3b24376905380afc29' }
272 $script:MockViolations += $v1
273
274 $v2 = [DependencyViolation]::new()
275 $v2.File = 'other.yml'
276 $v2.Line = 15
277 $v2.Type = 'github-actions'
278 $v2.Name = 'actions/setup-node'
279 $v2.Version = '60edb5d'
280 $v2.Severity = 'High'
281 $v2.ViolationType = 'VersionMismatch'
282 $v2.Description = 'Same SHA has conflicting version comments'
283 $v2.Remediation = 'Standardize version comment'
284 $v2.Metadata = @{ ConflictingVersions = 'v4.0.0, v4.0.2' }
285 $script:MockViolations += $v2
286 }
287
288 Context 'Table format output' {
289 It 'Does not throw for Table format' {
290 { Export-ConsistencyReport -Violations $script:MockViolations -Format Table -TotalActions 5 } | Should -Not -Throw
291 }
292
293 It 'Writes file when OutputPath specified' {
294 $outputFile = Join-Path $script:TestOutputPath 'report.txt'
295 Export-ConsistencyReport -Violations $script:MockViolations -Format Table -OutputPath $outputFile -TotalActions 5
296 Test-Path $outputFile | Should -BeTrue
297 }
298
299 It 'Reports success message when no violations' {
300 # Capture Host output
301 $output = Export-ConsistencyReport -Violations @() -Format Table -TotalActions 0 6>&1
302 ($output -join ' ') | Should -Match 'No version consistency violations found'
303 }
304 }
305
306 Context 'JSON format with OutputPath' {
307 It 'Generates valid JSON' {
308 $outputFile = Join-Path $script:TestOutputPath 'report.json'
309 Export-ConsistencyReport -Violations $script:MockViolations -Format Json -OutputPath $outputFile -TotalActions 5
310
311 Test-Path $outputFile | Should -BeTrue
312 $content = Get-Content $outputFile -Raw
313 { $content | ConvertFrom-Json } | Should -Not -Throw
314 }
315
316 It 'Includes Timestamp field' {
317 $outputFile = Join-Path $script:TestOutputPath 'report-ts.json'
318 Export-ConsistencyReport -Violations $script:MockViolations -Format Json -OutputPath $outputFile -TotalActions 5
319
320 $rawContent = Get-Content $outputFile -Raw
321 $rawContent | Should -Match '"Timestamp":\s*"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}'
322 }
323
324 It 'Includes TotalActions count' {
325 $outputFile = Join-Path $script:TestOutputPath 'report-total.json'
326 Export-ConsistencyReport -Violations $script:MockViolations -Format Json -OutputPath $outputFile -TotalActions 10
327
328 $content = Get-Content $outputFile -Raw | ConvertFrom-Json
329 $content.TotalActions | Should -Be 10
330 }
331
332 It 'Includes MismatchCount' {
333 $outputFile = Join-Path $script:TestOutputPath 'report-mismatch.json'
334 Export-ConsistencyReport -Violations $script:MockViolations -Format Json -OutputPath $outputFile -TotalActions 5
335
336 $content = Get-Content $outputFile -Raw | ConvertFrom-Json
337 $content.MismatchCount | Should -Be 1
338 }
339
340 It 'Includes MissingComments count' {
341 $outputFile = Join-Path $script:TestOutputPath 'report-missing.json'
342 Export-ConsistencyReport -Violations $script:MockViolations -Format Json -OutputPath $outputFile -TotalActions 5
343
344 $content = Get-Content $outputFile -Raw | ConvertFrom-Json
345 $content.MissingComments | Should -Be 1
346 }
347
348 It 'Includes Violations array' {
349 $outputFile = Join-Path $script:TestOutputPath 'report-violations.json'
350 Export-ConsistencyReport -Violations $script:MockViolations -Format Json -OutputPath $outputFile -TotalActions 5
351
352 $content = Get-Content $outputFile -Raw | ConvertFrom-Json
353 $content.Violations.Count | Should -Be 2
354 }
355 }
356
357 Context 'SARIF format schema validation' {
358 It 'Generates valid SARIF structure' {
359 $outputFile = Join-Path $script:TestOutputPath 'report.sarif'
360 Export-ConsistencyReport -Violations $script:MockViolations -Format Sarif -OutputPath $outputFile -TotalActions 5
361
362 Test-Path $outputFile | Should -BeTrue
363 $content = Get-Content $outputFile -Raw | ConvertFrom-Json
364 $content.version | Should -Be '2.1.0'
365 }
366
367 It 'Includes schema reference' {
368 $outputFile = Join-Path $script:TestOutputPath 'report-schema.sarif'
369 Export-ConsistencyReport -Violations $script:MockViolations -Format Sarif -OutputPath $outputFile -TotalActions 5
370
371 $content = Get-Content $outputFile -Raw | ConvertFrom-Json
372 $content.'$schema' | Should -Match 'sarif-2\.1\.0\.json'
373 }
374
375 It 'Includes tool driver information' {
376 $outputFile = Join-Path $script:TestOutputPath 'report-tool.sarif'
377 Export-ConsistencyReport -Violations $script:MockViolations -Format Sarif -OutputPath $outputFile -TotalActions 5
378
379 $content = Get-Content $outputFile -Raw | ConvertFrom-Json
380 $content.runs[0].tool.driver.name | Should -Be 'action-version-consistency'
381 }
382
383 It 'Maps violations to SARIF results' {
384 $outputFile = Join-Path $script:TestOutputPath 'report-results.sarif'
385 Export-ConsistencyReport -Violations $script:MockViolations -Format Sarif -OutputPath $outputFile -TotalActions 5
386
387 $content = Get-Content $outputFile -Raw | ConvertFrom-Json
388 $content.runs[0].results.Count | Should -Be 2
389 }
390
391 It 'Maps MissingVersionComment to warning level' {
392 $outputFile = Join-Path $script:TestOutputPath 'report-warning.sarif'
393 Export-ConsistencyReport -Violations $script:MockViolations -Format Sarif -OutputPath $outputFile -TotalActions 5
394
395 $content = Get-Content $outputFile -Raw | ConvertFrom-Json
396 $warningResult = $content.runs[0].results | Where-Object { $_.ruleId -eq 'missing-version-comment' }
397 $warningResult.level | Should -Be 'warning'
398 }
399
400 It 'Maps VersionMismatch to error level' {
401 $outputFile = Join-Path $script:TestOutputPath 'report-error.sarif'
402 Export-ConsistencyReport -Violations $script:MockViolations -Format Sarif -OutputPath $outputFile -TotalActions 5
403
404 $content = Get-Content $outputFile -Raw | ConvertFrom-Json
405 $errorResult = $content.runs[0].results | Where-Object { $_.ruleId -eq 'version-mismatch' }
406 $errorResult.level | Should -Be 'error'
407 }
408
409 It 'Includes locations with file and line' {
410 $outputFile = Join-Path $script:TestOutputPath 'report-locations.sarif'
411 Export-ConsistencyReport -Violations $script:MockViolations -Format Sarif -OutputPath $outputFile -TotalActions 5
412
413 $content = Get-Content $outputFile -Raw | ConvertFrom-Json
414 $result = $content.runs[0].results[0]
415 $result.locations[0].physicalLocation.artifactLocation.uri | Should -Not -BeNullOrEmpty
416 $result.locations[0].physicalLocation.region.startLine | Should -BeGreaterThan 0
417 }
418 }
419
420 Context 'Empty violations array' {
421 It 'Handles empty violations for JSON format' {
422 $outputFile = Join-Path $script:TestOutputPath 'empty.json'
423 Export-ConsistencyReport -Violations @() -Format Json -OutputPath $outputFile -TotalActions 0
424
425 $content = Get-Content $outputFile -Raw | ConvertFrom-Json
426 $content.Violations.Count | Should -Be 0
427 $content.MismatchCount | Should -Be 0
428 $content.MissingComments | Should -Be 0
429 }
430
431 It 'Handles empty violations for SARIF format' {
432 $outputFile = Join-Path $script:TestOutputPath 'empty.sarif'
433 Export-ConsistencyReport -Violations @() -Format Sarif -OutputPath $outputFile -TotalActions 0
434
435 $content = Get-Content $outputFile -Raw | ConvertFrom-Json
436 $content.runs[0].results.Count | Should -Be 0
437 }
438 }
439
440 Context 'Multiple violations of each type' {
441 BeforeEach {
442 $script:MultipleViolations = @()
443
444 # Add 3 MissingVersionComment violations
445 for ($i = 1; $i -le 3; $i++) {
446 $v = [DependencyViolation]::new()
447 $v.File = "workflow$i.yml"
448 $v.Line = $i * 10
449 $v.Type = 'github-actions'
450 $v.Name = "actions/action$i"
451 $v.Version = "sha$i"
452 $v.Severity = 'Medium'
453 $v.ViolationType = 'MissingVersionComment'
454 $v.Description = 'Missing comment'
455 $script:MultipleViolations += $v
456 }
457
458 # Add 2 VersionMismatch violations
459 for ($i = 1; $i -le 2; $i++) {
460 $v = [DependencyViolation]::new()
461 $v.File = "mismatch$i.yml"
462 $v.Line = $i * 5
463 $v.Type = 'github-actions'
464 $v.Name = "actions/mismatch$i"
465 $v.Version = "msha$i"
466 $v.Severity = 'High'
467 $v.ViolationType = 'VersionMismatch'
468 $v.Description = 'Version mismatch'
469 $script:MultipleViolations += $v
470 }
471 }
472
473 It 'Counts multiple MissingVersionComment violations correctly' {
474 $outputFile = Join-Path $script:TestOutputPath 'multiple.json'
475 Export-ConsistencyReport -Violations $script:MultipleViolations -Format Json -OutputPath $outputFile -TotalActions 10
476
477 $content = Get-Content $outputFile -Raw | ConvertFrom-Json
478 $content.MissingComments | Should -Be 3
479 }
480
481 It 'Counts multiple VersionMismatch violations correctly' {
482 $outputFile = Join-Path $script:TestOutputPath 'multiple-mismatch.json'
483 Export-ConsistencyReport -Violations $script:MultipleViolations -Format Json -OutputPath $outputFile -TotalActions 10
484
485 $content = Get-Content $outputFile -Raw | ConvertFrom-Json
486 $content.MismatchCount | Should -Be 2
487 }
488 }
489}
490
491Describe 'Main Script Execution' -Tag 'Unit' {
492 BeforeAll {
493 $script:TestScript = (Resolve-Path (Join-Path $PSScriptRoot '../../security/Test-ActionVersionConsistency.ps1')).Path
494 $script:FixturesPath = Join-Path $PSScriptRoot '../Fixtures/Workflows'
495 # Use cross-platform temp directory (accessible from child process, unlike $TestDrive)
496 $tempBase = [System.IO.Path]::GetTempPath()
497 $script:MainTestRoot = Join-Path $tempBase "pester-main-$(Get-Random)"
498 New-Item -ItemType Directory -Path $script:MainTestRoot -Force | Out-Null
499 }
500
501 AfterAll {
502 if ($script:MainTestRoot -and (Test-Path $script:MainTestRoot)) {
503 Remove-Item -Path $script:MainTestRoot -Recurse -Force -ErrorAction SilentlyContinue
504 }
505 }
506
507 Context 'Exit code handling' {
508 BeforeEach {
509 $script:TestWorkspace = Join-Path $script:MainTestRoot "test-$(Get-Random)"
510 New-Item -ItemType Directory -Path $script:TestWorkspace -Force | Out-Null
511 }
512
513 AfterEach {
514 if ($script:TestWorkspace -and (Test-Path $script:TestWorkspace)) {
515 Remove-Item -Path $script:TestWorkspace -Recurse -Force -ErrorAction SilentlyContinue
516 }
517 }
518
519 It 'Returns exit code 0 when no violations and no fail flags' {
520 Copy-Item -Path (Join-Path $script:FixturesPath 'pinned-workflow.yml') -Destination $script:TestWorkspace
521
522 $null = pwsh -NoProfile -Command "& '$script:TestScript' -Path '$script:TestWorkspace' -Format Json" 2>&1
523 $LASTEXITCODE | Should -Be 0
524 }
525
526 It 'Returns exit code 1 when FailOnMismatch and mismatches exist' {
527 Copy-Item -Path (Join-Path $script:FixturesPath 'version-mismatch-a.yml') -Destination $script:TestWorkspace
528 Copy-Item -Path (Join-Path $script:FixturesPath 'version-mismatch-b.yml') -Destination $script:TestWorkspace
529
530 # Clear HVE_SKIP_MAIN so subprocess runs main block (it's inherited from Pester's BeforeAll)
531 $tempScript = Join-Path $script:TestWorkspace 'run-test.ps1'
532 $scriptContent = @"
533`$env:HVE_SKIP_MAIN = ''
534& '$($script:TestScript)' -Path '$($script:TestWorkspace)' -Format Json -FailOnMismatch
535exit `$LASTEXITCODE
536"@
537 Set-Content -Path $tempScript -Value $scriptContent
538 $proc = Start-Process -FilePath 'pwsh' -ArgumentList @('-NoProfile', '-File', $tempScript) -Wait -PassThru -NoNewWindow
539 $proc.ExitCode | Should -Be 1
540 }
541
542 It 'Returns exit code 1 when FailOnMissingComment and missing comments exist' {
543 Copy-Item -Path (Join-Path $script:FixturesPath 'missing-version-comment.yml') -Destination $script:TestWorkspace
544
545 # Clear HVE_SKIP_MAIN so subprocess runs main block (it's inherited from Pester's BeforeAll)
546 $tempScript = Join-Path $script:TestWorkspace 'run-test.ps1'
547 $scriptContent = @"
548`$env:HVE_SKIP_MAIN = ''
549& '$($script:TestScript)' -Path '$($script:TestWorkspace)' -Format Json -FailOnMissingComment
550exit `$LASTEXITCODE
551"@
552 Set-Content -Path $tempScript -Value $scriptContent
553 $proc = Start-Process -FilePath 'pwsh' -ArgumentList @('-NoProfile', '-File', $tempScript) -Wait -PassThru -NoNewWindow
554 $proc.ExitCode | Should -Be 1
555 }
556
557 It 'Returns exit code 0 when violations exist but no fail flags set' {
558 Copy-Item -Path (Join-Path $script:FixturesPath 'missing-version-comment.yml') -Destination $script:TestWorkspace
559
560 $null = pwsh -NoProfile -Command "& '$script:TestScript' -Path '$script:TestWorkspace' -Format Json" 2>&1
561 $LASTEXITCODE | Should -Be 0
562 }
563 }
564}
565