microsoft/hve-core

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
8b197250063fc1629244f661f78baf9022cebbb0

Branches

Tags

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

Clone

HTTPS

Download ZIP

scripts/tests/lib/CIHelpers.Tests.ps1

819lines · modecode

1# Copyright (c) Microsoft Corporation.
2# SPDX-License-Identifier: MIT
3
4#Requires -Modules Pester
5# CIHelpers.Tests.ps1
6#
7# Purpose: Unit tests for CIHelpers.psm1 module
8# Author: HVE Core Team
9
10BeforeAll {
11 $modulePath = Join-Path $PSScriptRoot '../../lib/Modules/CIHelpers.psm1'
12 Import-Module $modulePath -Force
13
14 $mockPath = Join-Path $PSScriptRoot '../Mocks/GitMocks.psm1'
15 Import-Module $mockPath -Force
16}
17
18Describe 'Get-CIPlatform' -Tag 'Unit' {
19 BeforeAll {
20 Save-CIEnvironment
21 }
22
23 AfterAll {
24 Restore-CIEnvironment
25 }
26
27 Context 'In GitHub Actions environment' {
28 BeforeEach {
29 Clear-MockCIEnvironment
30 $env:GITHUB_ACTIONS = 'true'
31 }
32
33 It 'Returns github' {
34 Get-CIPlatform | Should -Be 'github'
35 }
36 }
37
38 Context 'In Azure DevOps environment with TF_BUILD' {
39 BeforeEach {
40 Clear-MockCIEnvironment
41 $env:TF_BUILD = 'True'
42 }
43
44 It 'Returns azdo' {
45 Get-CIPlatform | Should -Be 'azdo'
46 }
47 }
48
49 Context 'In Azure DevOps environment with AZURE_PIPELINES' {
50 BeforeEach {
51 Clear-MockCIEnvironment
52 $env:AZURE_PIPELINES = 'True'
53 }
54
55 It 'Returns azdo' {
56 Get-CIPlatform | Should -Be 'azdo'
57 }
58 }
59
60 Context 'In local environment' {
61 BeforeEach {
62 Clear-MockCIEnvironment
63 }
64
65 It 'Returns local' {
66 Get-CIPlatform | Should -Be 'local'
67 }
68 }
69
70 Context 'GitHub takes priority over Azure DevOps' {
71 BeforeEach {
72 Clear-MockCIEnvironment
73 $env:GITHUB_ACTIONS = 'true'
74 $env:TF_BUILD = 'True'
75 }
76
77 It 'Returns github when both are set' {
78 Get-CIPlatform | Should -Be 'github'
79 }
80 }
81}
82
83Describe 'Test-CIEnvironment' -Tag 'Unit' {
84 BeforeAll {
85 Save-CIEnvironment
86 }
87
88 AfterAll {
89 Restore-CIEnvironment
90 }
91
92 Context 'In GitHub Actions environment' {
93 BeforeEach {
94 Clear-MockCIEnvironment
95 $env:GITHUB_ACTIONS = 'true'
96 }
97
98 It 'Returns true' {
99 Test-CIEnvironment | Should -BeTrue
100 }
101 }
102
103 Context 'In Azure DevOps environment' {
104 BeforeEach {
105 Clear-MockCIEnvironment
106 $env:TF_BUILD = 'True'
107 }
108
109 It 'Returns true' {
110 Test-CIEnvironment | Should -BeTrue
111 }
112 }
113
114 Context 'In local environment' {
115 BeforeEach {
116 Clear-MockCIEnvironment
117 }
118
119 It 'Returns false' {
120 Test-CIEnvironment | Should -BeFalse
121 }
122 }
123}
124
125Describe 'Set-CIOutput' -Tag 'Unit' {
126 BeforeAll {
127 Save-CIEnvironment
128 }
129
130 AfterAll {
131 Restore-CIEnvironment
132 }
133
134 Context 'In GitHub Actions environment' {
135 BeforeEach {
136 $script:mockFiles = Initialize-MockCIEnvironment
137 }
138
139 AfterEach {
140 Remove-MockCIFiles -MockFiles $script:mockFiles
141 }
142
143 It 'Writes output to GITHUB_OUTPUT file' {
144 Set-CIOutput -Name 'test-key' -Value 'test-value'
145 $content = Get-Content -Path $env:GITHUB_OUTPUT -Raw
146 $content | Should -Match 'test-key=test-value'
147 }
148
149 It 'Appends multiple outputs' {
150 Set-CIOutput -Name 'key1' -Value 'value1'
151 Set-CIOutput -Name 'key2' -Value 'value2'
152 $content = Get-Content -Path $env:GITHUB_OUTPUT -Raw
153 $content | Should -Match 'key1=value1'
154 $content | Should -Match 'key2=value2'
155 }
156 }
157
158 Context 'In Azure DevOps environment' {
159 BeforeEach {
160 Clear-MockCIEnvironment
161 $env:TF_BUILD = 'True'
162 }
163
164 It 'Outputs task.setvariable format' {
165 $output = Set-CIOutput -Name 'test-key' -Value 'test-value'
166 $output | Should -Be '##vso[task.setvariable variable=test-key]test-value'
167 }
168
169 It 'Includes isOutput flag when specified' {
170 $output = Set-CIOutput -Name 'test-key' -Value 'test-value' -IsOutput
171 $output | Should -Be '##vso[task.setvariable variable=test-key;isOutput=true]test-value'
172 }
173 }
174
175 Context 'In local environment' {
176 BeforeEach {
177 Clear-MockCIEnvironment
178 }
179
180 It 'Does not produce console output' {
181 $output = Set-CIOutput -Name 'test-key' -Value 'test-value'
182 $output | Should -BeNullOrEmpty
183 }
184 }
185
186 Context 'GitHub with missing GITHUB_OUTPUT' {
187 BeforeEach {
188 Clear-MockCIEnvironment
189 $env:GITHUB_ACTIONS = 'true'
190 }
191
192 It 'Handles missing GITHUB_OUTPUT gracefully' {
193 { Set-CIOutput -Name 'test-key' -Value 'test-value' } | Should -Not -Throw
194 }
195 }
196
197 Context 'Workflow command injection prevention (Azure DevOps)' {
198 BeforeEach {
199 Clear-MockCIEnvironment
200 $env:TF_BUILD = 'True'
201 }
202
203 It 'Escapes newlines in value to prevent command injection' {
204 $maliciousValue = "value`n##vso[task.setvariable variable=pwned]true"
205 $output = Set-CIOutput -Name 'test-key' -Value $maliciousValue
206 $output | Should -Not -Match '##vso\[task\.setvariable variable=pwned\]'
207 $output | Should -Match '%AZP0A'
208 }
209
210 It 'Escapes semicolons in variable name to prevent property injection' {
211 $maliciousName = 'test;isOutput=true'
212 $output = Set-CIOutput -Name $maliciousName -Value 'value'
213 $output | Should -Match '%AZP3B'
214 }
215 }
216}
217
218Describe 'Set-CIEnv' -Tag 'Unit' {
219 BeforeAll {
220 Save-CIEnvironment
221 }
222
223 AfterAll {
224 Restore-CIEnvironment
225 }
226
227 Context 'In GitHub Actions environment' {
228 BeforeEach {
229 $script:mockFiles = Initialize-MockCIEnvironment
230 }
231
232 AfterEach {
233 Remove-MockCIFiles -MockFiles $script:mockFiles
234 }
235
236 It 'Writes environment variable to GITHUB_ENV file' {
237 Set-CIEnv -Name 'TEST_VAR' -Value 'test-value'
238 $content = Get-Content -Path $env:GITHUB_ENV -Raw
239 $content | Should -Match 'TEST_VAR<<EOF_[a-f0-9]+'
240 $content | Should -Match 'test-value'
241 }
242
243 It 'Preserves newlines in environment variable value using delimiter format' {
244 Set-CIEnv -Name 'TEST_VAR' -Value "line1`nline2"
245 $content = Get-Content -Path $env:GITHUB_ENV -Raw
246 $content | Should -Match 'line1'
247 $content | Should -Match 'line2'
248 $content | Should -Not -Match '%0A'
249 }
250
251 It 'Rejects invalid variable names' {
252 { Set-CIEnv -Name 'invalid-name' -Value 'test' } | Should -Throw -ExpectedMessage '*Invalid GitHub Actions environment variable name*'
253 { Set-CIEnv -Name '123start' -Value 'test' } | Should -Throw
254 }
255 }
256
257 Context 'In Azure DevOps environment' {
258 BeforeEach {
259 Clear-MockCIEnvironment
260 $env:TF_BUILD = 'True'
261 }
262
263 It 'Outputs task.setvariable format' {
264 $output = Set-CIEnv -Name 'test_var' -Value 'test-value'
265 $output | Should -Be '##vso[task.setvariable variable=test_var]test-value'
266 }
267
268 It 'Escapes semicolons in variable name to prevent property injection' {
269 $output = Set-CIEnv -Name 'test;isOutput=true' -Value 'value'
270 $output | Should -Match '%AZP3B'
271 }
272 }
273
274 Context 'In local environment' {
275 BeforeEach {
276 Clear-MockCIEnvironment
277 }
278
279 It 'Does not produce console output' {
280 $output = Set-CIEnv -Name 'test_var' -Value 'test-value'
281 $output | Should -BeNullOrEmpty
282 }
283 }
284
285 Context 'GitHub with missing GITHUB_ENV' {
286 BeforeEach {
287 Clear-MockCIEnvironment
288 $env:GITHUB_ACTIONS = 'true'
289 }
290
291 It 'Handles missing GITHUB_ENV gracefully' {
292 { Set-CIEnv -Name 'test_var' -Value 'test-value' } | Should -Not -Throw
293 }
294 }
295}
296
297Describe 'Write-CIStepSummary' -Tag 'Unit' {
298 BeforeAll {
299 Save-CIEnvironment
300 }
301
302 AfterAll {
303 Restore-CIEnvironment
304 }
305
306 Context 'In GitHub Actions environment with Content' {
307 BeforeEach {
308 $script:mockFiles = Initialize-MockCIEnvironment
309 }
310
311 AfterEach {
312 Remove-MockCIFiles -MockFiles $script:mockFiles
313 }
314
315 It 'Writes content to GITHUB_STEP_SUMMARY file' {
316 Write-CIStepSummary -Content '## Test Summary'
317 $content = Get-Content -Path $env:GITHUB_STEP_SUMMARY -Raw
318 $content | Should -Match '## Test Summary'
319 }
320 }
321
322 Context 'In GitHub Actions environment with Path' {
323 BeforeEach {
324 $script:mockFiles = Initialize-MockCIEnvironment
325 $script:tempSummaryFile = Join-Path ([System.IO.Path]::GetTempPath()) 'test-summary.md'
326 '## Summary from file' | Set-Content -Path $script:tempSummaryFile
327 }
328
329 AfterEach {
330 Remove-MockCIFiles -MockFiles $script:mockFiles
331 Remove-Item -Path $script:tempSummaryFile -Force -ErrorAction SilentlyContinue
332 }
333
334 It 'Reads content from file path' {
335 Write-CIStepSummary -Path $script:tempSummaryFile
336 $content = Get-Content -Path $env:GITHUB_STEP_SUMMARY -Raw
337 $content | Should -Match '## Summary from file'
338 }
339 }
340
341 Context 'In Azure DevOps environment' {
342 BeforeEach {
343 Clear-MockCIEnvironment
344 $env:TF_BUILD = 'True'
345 }
346
347 It 'Outputs section header and content' {
348 $output = Write-CIStepSummary -Content '## Test Summary'
349 $output[0] | Should -Be '##[section]Step Summary'
350 $output[1] | Should -Be '## Test Summary'
351 }
352 }
353
354 Context 'In local environment' {
355 BeforeEach {
356 Clear-MockCIEnvironment
357 }
358
359 It 'Does not produce console output' {
360 $output = Write-CIStepSummary -Content '## Test Summary'
361 $output | Should -BeNullOrEmpty
362 }
363 }
364}
365
366Describe 'Write-CIAnnotation' -Tag 'Unit' {
367 BeforeAll {
368 Save-CIEnvironment
369 }
370
371 AfterAll {
372 Restore-CIEnvironment
373 }
374
375 Context 'In GitHub Actions environment' {
376 BeforeEach {
377 $script:mockFiles = Initialize-MockCIEnvironment
378 }
379
380 AfterEach {
381 Remove-MockCIFiles -MockFiles $script:mockFiles
382 }
383
384 It 'Outputs warning annotation' {
385 $output = Write-CIAnnotation -Message 'Test warning' -Level Warning
386 $output | Should -Be '::warning::Test warning'
387 }
388
389 It 'Outputs error annotation' {
390 $output = Write-CIAnnotation -Message 'Test error' -Level Error
391 $output | Should -Be '::error::Test error'
392 }
393
394 It 'Outputs notice annotation' {
395 $output = Write-CIAnnotation -Message 'Test notice' -Level Notice
396 $output | Should -Be '::notice::Test notice'
397 }
398
399 It 'Includes file in annotation' {
400 $output = Write-CIAnnotation -Message 'Test' -Level Warning -File 'src/test.ps1'
401 $output | Should -Be '::warning file=src/test.ps1::Test'
402 }
403
404 It 'Normalizes backslashes to forward slashes' {
405 $output = Write-CIAnnotation -Message 'Test' -Level Warning -File 'src\path\test.ps1'
406 $output | Should -Be '::warning file=src/path/test.ps1::Test'
407 }
408
409 It 'Includes line number in annotation' {
410 $output = Write-CIAnnotation -Message 'Test' -Level Warning -File 'test.ps1' -Line 42
411 $output | Should -Be '::warning file=test.ps1,line=42::Test'
412 }
413
414 It 'Includes column number in annotation' {
415 $output = Write-CIAnnotation -Message 'Test' -Level Warning -File 'test.ps1' -Line 42 -Column 10
416 $output | Should -Be '::warning file=test.ps1,line=42,col=10::Test'
417 }
418
419 It 'Defaults to Warning level' {
420 $output = Write-CIAnnotation -Message 'Test message'
421 $output | Should -Be '::warning::Test message'
422 }
423 }
424
425 Context 'In Azure DevOps environment' {
426 BeforeEach {
427 Clear-MockCIEnvironment
428 $env:TF_BUILD = 'True'
429 }
430
431 It 'Outputs task.logissue for warning' {
432 $output = Write-CIAnnotation -Message 'Test warning' -Level Warning
433 $output | Should -Be '##vso[task.logissue type=warning]Test warning'
434 }
435
436 It 'Outputs task.logissue for error' {
437 $output = Write-CIAnnotation -Message 'Test error' -Level Error
438 $output | Should -Be '##vso[task.logissue type=error]Test error'
439 }
440
441 It 'Maps Notice to info type' {
442 $output = Write-CIAnnotation -Message 'Test notice' -Level Notice
443 $output | Should -Be '##vso[task.logissue type=info]Test notice'
444 }
445
446 It 'Includes sourcepath for file' {
447 $output = Write-CIAnnotation -Message 'Test' -Level Warning -File 'src/test.ps1'
448 $output | Should -Be '##vso[task.logissue type=warning;sourcepath=src/test.ps1]Test'
449 }
450
451 It 'Includes line and column numbers' {
452 $output = Write-CIAnnotation -Message 'Test' -Level Warning -File 'test.ps1' -Line 42 -Column 10
453 $output | Should -Be '##vso[task.logissue type=warning;sourcepath=test.ps1;linenumber=42;columnnumber=10]Test'
454 }
455 }
456
457 Context 'In local environment' {
458 BeforeEach {
459 Clear-MockCIEnvironment
460 }
461
462 It 'Uses Write-Warning for all levels' {
463 # Write-Warning outputs to warning stream, not standard output
464 $output = Write-CIAnnotation -Message 'Test message' -Level Warning 3>&1
465 $output | Should -Match 'WARNING.*Test message'
466 }
467
468 It 'Includes file location in local output' {
469 $output = Write-CIAnnotation -Message 'Test' -Level Warning -File 'test.ps1' -Line 42 3>&1
470 $output | Should -Match '\[test\.ps1:42\]'
471 }
472 }
473
474 Context 'Workflow command injection prevention (GitHub Actions)' {
475 BeforeEach {
476 $script:mockFiles = Initialize-MockCIEnvironment
477 }
478
479 AfterEach {
480 Remove-MockCIFiles -MockFiles $script:mockFiles
481 }
482
483 It 'Escapes newlines in message to prevent command injection' {
484 $maliciousMessage = "Test`n::set-output name=pwned::true"
485 $output = Write-CIAnnotation -Message $maliciousMessage -Level Warning
486 $output | Should -Not -Match '::set-output'
487 $output | Should -Match '%0A'
488 }
489
490 It 'Escapes carriage returns in message' {
491 $maliciousMessage = "Test`r::error::Injected"
492 $output = Write-CIAnnotation -Message $maliciousMessage -Level Warning
493 $output | Should -Not -Match '::error::Injected'
494 $output | Should -Match '%0D'
495 }
496
497 It 'Escapes percent signs in message' {
498 $maliciousMessage = 'Test %0A injection attempt'
499 $output = Write-CIAnnotation -Message $maliciousMessage -Level Warning
500 $output | Should -Match '%250A'
501 }
502
503 It 'Escapes colons and commas in file path' {
504 $maliciousFile = 'file:injection,col=1'
505 $output = Write-CIAnnotation -Message 'Test' -Level Warning -File $maliciousFile
506 $output | Should -Match '%3A'
507 $output | Should -Match '%2C'
508 }
509
510 It 'Prevents full command injection via file parameter' {
511 $maliciousFile = "path`n::error::Pwned"
512 $output = Write-CIAnnotation -Message 'Test' -Level Warning -File $maliciousFile
513 $output | Should -Not -Match '::error::Pwned'
514 }
515 }
516
517 Context 'Workflow command injection prevention (Azure DevOps)' {
518 BeforeEach {
519 Clear-MockCIEnvironment
520 $env:TF_BUILD = 'True'
521 }
522
523 It 'Escapes newlines in message to prevent command injection' {
524 $maliciousMessage = "Test`n##vso[task.setvariable variable=pwned]true"
525 $output = Write-CIAnnotation -Message $maliciousMessage -Level Warning
526 $output | Should -Not -Match '##vso\[task\.setvariable'
527 $output | Should -Match '%AZP0A'
528 }
529
530 It 'Escapes closing brackets in file path' {
531 $maliciousFile = 'path]##vso[task.setvariable variable=pwned]true'
532 $output = Write-CIAnnotation -Message 'Test' -Level Warning -File $maliciousFile
533 $output | Should -Match '%AZP5D'
534 }
535
536 It 'Escapes semicolons in file path' {
537 $maliciousFile = 'path;linenumber=999'
538 $output = Write-CIAnnotation -Message 'Test' -Level Warning -File $maliciousFile
539 $output | Should -Match '%AZP3B'
540 }
541
542 It 'Prevents full command injection via message' {
543 $maliciousMessage = "Test`n##vso[task.complete result=Failed]"
544 $output = Write-CIAnnotation -Message $maliciousMessage -Level Warning
545 $output | Should -Not -Match '##vso\[task\.complete'
546 }
547 }
548}
549
550Describe 'Write-CIAnnotations' -Tag 'Unit' {
551 BeforeAll {
552 Save-CIEnvironment
553 }
554
555 AfterAll {
556 Restore-CIEnvironment
557 }
558
559 Context 'In GitHub Actions environment' {
560 BeforeEach {
561 $script:mockFiles = Initialize-MockCIEnvironment
562 }
563
564 AfterEach {
565 Remove-MockCIFiles -MockFiles $script:mockFiles
566 }
567
568 It 'Outputs error and warning annotations from summary' {
569 $summary = [pscustomobject]@{
570 Results = @(
571 [pscustomobject]@{
572 RelativePath = 'test.md'
573 Issues = @(
574 [pscustomobject]@{ Type = 'Error'; Message = 'Test error'; Line = 42 },
575 [pscustomobject]@{ Type = 'Warning'; Message = 'Test warning'; Line = 0 }
576 )
577 }
578 )
579 }
580
581 $output = Write-CIAnnotations -Summary $summary
582 $output | Should -Contain '::error file=test.md,line=42::Test error'
583 $output | Should -Contain '::warning file=test.md,line=1::Test warning'
584 }
585
586 It 'Escapes newlines in message' {
587 $summary = [pscustomobject]@{
588 Results = @(
589 [pscustomobject]@{
590 RelativePath = 'test.md'
591 Issues = @(
592 [pscustomobject]@{ Type = 'Error'; Message = "line1`nline2"; Line = 1 }
593 )
594 }
595 )
596 }
597
598 $output = Write-CIAnnotations -Summary $summary
599 $output | Should -Match 'line1%0Aline2'
600 }
601 }
602
603 Context 'In Azure DevOps environment' {
604 BeforeEach {
605 Clear-MockCIEnvironment
606 $env:TF_BUILD = 'True'
607 }
608
609 It 'Outputs task.logissue entries for issues' {
610 $summary = [pscustomobject]@{
611 Results = @(
612 [pscustomobject]@{
613 RelativePath = 'test.md'
614 Issues = @(
615 [pscustomobject]@{ Type = 'Error'; Message = 'Test error'; Line = 10; Column = 4 }
616 )
617 }
618 )
619 }
620
621 $output = Write-CIAnnotations -Summary $summary
622 $output | Should -Be '##vso[task.logissue type=error;sourcepath=test.md;linenumber=10;columnnumber=4]Test error'
623 }
624 }
625
626 Context 'In local environment' {
627 BeforeEach {
628 Clear-MockCIEnvironment
629 }
630
631 It 'Does not throw when emitting annotations' {
632 $summary = [pscustomobject]@{
633 Results = @(
634 [pscustomobject]@{
635 RelativePath = 'test.md'
636 Issues = @(
637 [pscustomobject]@{ Type = 'Warning'; Message = 'Test warning'; Line = 2 }
638 )
639 }
640 )
641 }
642
643 { Write-CIAnnotations -Summary $summary } | Should -Not -Throw
644 }
645 }
646
647 Context 'With no issues' {
648 BeforeEach {
649 Clear-MockCIEnvironment
650 }
651
652 It 'Returns nothing when no issues exist' {
653 $summary = [pscustomobject]@{
654 Results = @(
655 [pscustomobject]@{ RelativePath = 'test.md'; Issues = @() }
656 )
657 }
658
659 $output = Write-CIAnnotations -Summary $summary
660 $output | Should -BeNullOrEmpty
661 }
662 }
663}
664
665Describe 'Set-CITaskResult' -Tag 'Unit' {
666 BeforeAll {
667 Save-CIEnvironment
668 }
669
670 AfterAll {
671 Restore-CIEnvironment
672 }
673
674 Context 'In GitHub Actions environment' {
675 BeforeEach {
676 $script:mockFiles = Initialize-MockCIEnvironment
677 }
678
679 AfterEach {
680 Remove-MockCIFiles -MockFiles $script:mockFiles
681 }
682
683 It 'Outputs error for Failed result' {
684 $output = Set-CITaskResult -Result Failed
685 $output | Should -Be '::error::Task failed'
686 }
687
688 It 'Does not output for Succeeded result' {
689 $output = Set-CITaskResult -Result Succeeded
690 $output | Should -BeNullOrEmpty
691 }
692
693 It 'Does not output for SucceededWithIssues result' {
694 $output = Set-CITaskResult -Result SucceededWithIssues
695 $output | Should -BeNullOrEmpty
696 }
697 }
698
699 Context 'In Azure DevOps environment' {
700 BeforeEach {
701 Clear-MockCIEnvironment
702 $env:TF_BUILD = 'True'
703 }
704
705 It 'Outputs task.complete for Succeeded' {
706 $output = Set-CITaskResult -Result Succeeded
707 $output | Should -Be '##vso[task.complete result=Succeeded]'
708 }
709
710 It 'Outputs task.complete for SucceededWithIssues' {
711 $output = Set-CITaskResult -Result SucceededWithIssues
712 $output | Should -Be '##vso[task.complete result=SucceededWithIssues]'
713 }
714
715 It 'Outputs task.complete for Failed' {
716 $output = Set-CITaskResult -Result Failed
717 $output | Should -Be '##vso[task.complete result=Failed]'
718 }
719 }
720
721 Context 'In local environment' {
722 BeforeEach {
723 Clear-MockCIEnvironment
724 }
725
726 It 'Does not produce console output' {
727 $output = Set-CITaskResult -Result Succeeded
728 $output | Should -BeNullOrEmpty
729 }
730 }
731}
732
733Describe 'Publish-CIArtifact' -Tag 'Unit' {
734 BeforeAll {
735 Save-CIEnvironment
736 }
737
738 AfterAll {
739 Restore-CIEnvironment
740 }
741
742 Context 'In GitHub Actions environment' {
743 BeforeEach {
744 $script:mockFiles = Initialize-MockCIEnvironment
745 $script:tempArtifact = Join-Path ([System.IO.Path]::GetTempPath()) 'test-artifact.txt'
746 'artifact content' | Set-Content -Path $script:tempArtifact
747 }
748
749 AfterEach {
750 Remove-MockCIFiles -MockFiles $script:mockFiles
751 Remove-Item -Path $script:tempArtifact -Force -ErrorAction SilentlyContinue
752 }
753
754 It 'Sets artifact outputs' {
755 Publish-CIArtifact -Path $script:tempArtifact -Name 'test-artifact'
756 $content = Get-Content -Path $env:GITHUB_OUTPUT -Raw
757 $content | Should -Match "artifact-path-test-artifact=$([regex]::Escape($script:tempArtifact))"
758 $content | Should -Match 'artifact-name-test-artifact=test-artifact'
759 }
760 }
761
762 Context 'In Azure DevOps environment' {
763 BeforeEach {
764 Clear-MockCIEnvironment
765 $env:TF_BUILD = 'True'
766 $script:tempArtifact = Join-Path ([System.IO.Path]::GetTempPath()) 'test-artifact.txt'
767 'artifact content' | Set-Content -Path $script:tempArtifact
768 }
769
770 AfterEach {
771 Remove-Item -Path $script:tempArtifact -Force -ErrorAction SilentlyContinue
772 }
773
774 It 'Outputs artifact.upload command' {
775 $output = Publish-CIArtifact -Path $script:tempArtifact -Name 'test-artifact'
776 $output | Should -Match '##vso\[artifact\.upload containerfolder=test-artifact;artifactname=test-artifact\]'
777 }
778
779 It 'Uses ContainerFolder when specified' {
780 $output = Publish-CIArtifact -Path $script:tempArtifact -Name 'test-artifact' -ContainerFolder 'custom-folder'
781 $output | Should -Match '##vso\[artifact\.upload containerfolder=custom-folder;artifactname=test-artifact\]'
782 }
783 }
784
785 Context 'With non-existent path' {
786 BeforeEach {
787 Clear-MockCIEnvironment
788 $env:TF_BUILD = 'True'
789 }
790
791 It 'Outputs warning for missing path' {
792 $warning = $null
793 Publish-CIArtifact -Path 'C:\nonexistent\file.txt' -Name 'test' -WarningVariable warning 3>&1
794 $warning | Should -Match 'Artifact path not found'
795 }
796
797 It 'Does not produce command output for missing path' {
798 $output = Publish-CIArtifact -Path 'C:\nonexistent\file.txt' -Name 'test' 3>$null
799 $output | Should -BeNullOrEmpty
800 }
801 }
802
803 Context 'In local environment' {
804 BeforeEach {
805 Clear-MockCIEnvironment
806 $script:tempArtifact = Join-Path ([System.IO.Path]::GetTempPath()) 'test-artifact.txt'
807 'artifact content' | Set-Content -Path $script:tempArtifact
808 }
809
810 AfterEach {
811 Remove-Item -Path $script:tempArtifact -Force -ErrorAction SilentlyContinue
812 }
813
814 It 'Does not produce console output' {
815 $output = Publish-CIArtifact -Path $script:tempArtifact -Name 'test-artifact'
816 $output | Should -BeNullOrEmpty
817 }
818 }
819}
820