microsoft/hve-core

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
feat/context-working

Branches

Tags

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

Clone

HTTPS

Download ZIP

scripts/tests/linting/Invoke-LinkLanguageCheck.Tests.ps1

571lines ยท modecode

1#Requires -Modules Pester
2# Copyright (c) Microsoft Corporation.
3# SPDX-License-Identifier: MIT
4<#
5.SYNOPSIS
6 Pester tests for Invoke-LinkLanguageCheck.ps1 script
7.DESCRIPTION
8 Tests for Link Language Check wrapper script:
9 - Link-Lang-Check.ps1 invocation
10 - JSON parsing
11 - GitHub Actions integration
12 - Exit code handling
13#>
14
15BeforeAll {
16 $script:ScriptPath = Join-Path $PSScriptRoot '../../linting/Invoke-LinkLanguageCheck.ps1'
17 $script:ModulePath = Join-Path $PSScriptRoot '../../linting/Modules/LintingHelpers.psm1'
18 $script:CIHelpersPath = Join-Path $PSScriptRoot '../../lib/Modules/CIHelpers.psm1'
19
20 # Import modules for mocking
21 Import-Module $script:ModulePath -Force
22 Import-Module $script:CIHelpersPath -Force
23
24 . $script:ScriptPath
25}
26
27AfterAll {
28 Remove-Module LintingHelpers -Force -ErrorAction SilentlyContinue
29 Remove-Module CIHelpers -Force -ErrorAction SilentlyContinue
30}
31
32#region Link-Lang-Check Invocation Tests
33
34Describe 'Link-Lang-Check.ps1 Invocation' -Tag 'Unit' {
35 Context 'Script discovery' {
36 It 'Link-Lang-Check.ps1 exists' {
37 $linkLangCheckPath = Join-Path $PSScriptRoot '../../linting/Link-Lang-Check.ps1'
38 Test-Path $linkLangCheckPath | Should -BeTrue
39 }
40 }
41
42 Context 'Normal execution' {
43 It 'Invoke-LinkLanguageCheck.ps1 exists' {
44 $scriptExists = Test-Path $script:ScriptPath
45 $scriptExists | Should -BeTrue
46 }
47 }
48}
49
50#endregion
51
52#region Invoke-LinkLanguageCheckCore Tests
53
54Describe 'Invoke-LinkLanguageCheckCore' -Tag 'Unit' {
55 Context 'Not in git repository' {
56 BeforeEach {
57 Mock git {
58 $global:LASTEXITCODE = 128
59 return 'fatal: not a git repository'
60 } -ParameterFilter { $args -contains 'rev-parse' }
61
62 Mock Write-Error { }
63 }
64
65 It 'Returns failure exit code' {
66 Invoke-LinkLanguageCheckCore -ExcludePaths @() | Should -Be 1
67 }
68 }
69
70 Context 'Issues found in link scan' {
71 BeforeEach {
72 $script:RepoRoot = $TestDrive
73 $script:MockLinkLang = Join-Path $TestDrive 'mock-link-lang.ps1'
74 $script:WriteHostMessages = @()
75
76 @'
77param([string[]]$ExcludePaths = @())
78$json = @"
79[
80 {"file":"docs/a.md","line_number":1,"original_url":"https://docs.microsoft.com/en-us/a"},
81 {"file":"docs/b.md","line_number":2,"original_url":"https://docs.microsoft.com/en-us/b"}
82]
83"@
84
85Write-Output $json
86'@ | Set-Content -Path $script:MockLinkLang -Encoding utf8
87
88 Mock git {
89 $global:LASTEXITCODE = 0
90 return $script:RepoRoot
91 } -ParameterFilter { $args -contains 'rev-parse' }
92
93 Mock Join-Path {
94 return $script:MockLinkLang
95 } -ParameterFilter { $ChildPath -eq 'Link-Lang-Check.ps1' }
96
97 Mock Write-CIAnnotation { }
98 Mock Set-CIOutput { }
99 Mock Set-CIEnv { }
100 Mock Write-CIStepSummary { }
101 Mock Write-Host {
102 param($Object)
103 $script:WriteHostMessages += [string]$Object
104 }
105 }
106
107 It 'Returns failure exit code and records outputs' {
108 Invoke-LinkLanguageCheckCore -ExcludePaths @('scripts/tests/**') -OutputPath 'logs/link-lang-check-results.json' | Should -Be 1
109 Should -Invoke Set-CIOutput -Times 1
110 Should -Invoke Set-CIEnv -Times 1
111 Should -Invoke Write-CIAnnotation -Times 2
112 Should -Invoke Write-CIStepSummary -Times 1
113
114 Should -Invoke Write-Host -Times 1 -ParameterFilter { $Object -like '*๐Ÿ“„ docs/a.md*' }
115 Should -Invoke Write-Host -Times 1 -ParameterFilter { $Object -like '*๐Ÿ“„ docs/b.md*' }
116 Should -Invoke Write-Host -ParameterFilter { $Object -like '*Line 1:*' }
117 Should -Invoke Write-Host -ParameterFilter { $Object -like '*Line 2:*' }
118 Should -Invoke Write-Host -Times 1 -ParameterFilter { $Object -like '*failed with 2 issue*' }
119
120 $script:WriteHostMessages | Should -Contain '๐Ÿ“„ docs/a.md'
121 $script:WriteHostMessages | Should -Contain '๐Ÿ“„ docs/b.md'
122 }
123
124 It 'Calls Get-StandardTimestamp for result JSON timestamp' {
125 Mock Get-StandardTimestamp { return 'MOCK-TIMESTAMP' }
126
127 Invoke-LinkLanguageCheckCore -ExcludePaths @('scripts/tests/**') -OutputPath 'logs/link-lang-check-results.json' | Out-Null
128
129 Should -Invoke Get-StandardTimestamp -Times 1
130
131 $resultFile = Join-Path $script:RepoRoot 'logs/link-lang-check-results.json'
132 $json = Get-Content $resultFile -Raw | ConvertFrom-Json
133 $json.Timestamp | Should -Be 'MOCK-TIMESTAMP'
134 }
135 }
136
137 Context 'No issues found' {
138 BeforeEach {
139 $script:RepoRoot = $TestDrive
140 $script:MockLinkLang = Join-Path $TestDrive 'mock-link-lang-empty.ps1'
141
142 @'
143param([string[]]$ExcludePaths = @())
144$json = @"
145[]
146"@
147
148Write-Output $json
149'@ | Set-Content -Path $script:MockLinkLang -Encoding utf8
150
151 Mock git {
152 $global:LASTEXITCODE = 0
153 return $script:RepoRoot
154 } -ParameterFilter { $args -contains 'rev-parse' }
155
156 Mock Join-Path {
157 return $script:MockLinkLang
158 } -ParameterFilter { $ChildPath -eq 'Link-Lang-Check.ps1' }
159
160 Mock Set-CIOutput { }
161 Mock Write-CIStepSummary { }
162 Mock Write-Host {
163 param($Object)
164 $script:WriteHostMessages += [string]$Object
165 }
166 }
167
168 It 'Returns success exit code and records outputs' {
169 $script:WriteHostMessages = @()
170 Invoke-LinkLanguageCheckCore -ExcludePaths @() -OutputPath 'logs/link-lang-check-results.json' | Should -Be 0
171 Should -Invoke Set-CIOutput -Times 1
172 Should -Invoke Write-CIStepSummary -Times 1
173 Should -Invoke Write-Host -Times 1 -ParameterFilter { $Object -like '*โœ… No URLs with language paths found*' }
174 Should -Invoke Write-Host -Times 0 -ParameterFilter { $Object -like '*๐Ÿ“„*' }
175 Should -Invoke Write-Host -Times 0 -ParameterFilter { $Object -like '*โš ๏ธ*' }
176 }
177
178 It 'Calls Get-StandardTimestamp for empty result JSON timestamp' {
179 Mock Get-StandardTimestamp { return 'MOCK-TIMESTAMP' }
180
181 Invoke-LinkLanguageCheckCore -ExcludePaths @() -OutputPath 'logs/link-lang-check-results.json' | Out-Null
182
183 Should -Invoke Get-StandardTimestamp -Times 1
184
185 $resultFile = Join-Path $script:RepoRoot 'logs/link-lang-check-results.json'
186 $json = Get-Content $resultFile -Raw | ConvertFrom-Json
187 $json.Timestamp | Should -Be 'MOCK-TIMESTAMP'
188 }
189 }
190}
191
192#endregion
193
194#region JSON Parsing Tests
195
196Describe 'JSON Output Parsing' -Tag 'Unit' {
197 Context 'Valid JSON with issues' {
198 BeforeEach {
199 $script:JsonWithIssues = @'
200[
201 {
202 "file": "docs/guide.md",
203 "line_number": 15,
204 "original_url": "https://docs.microsoft.com/en-us/azure"
205 },
206 {
207 "file": "README.md",
208 "line_number": 42,
209 "original_url": "https://learn.microsoft.com/en-us/dotnet"
210 }
211]
212'@
213 }
214
215 It 'Parses JSON array correctly' {
216 $result = $script:JsonWithIssues | ConvertFrom-Json
217 $result | Should -HaveCount 2
218 }
219
220 It 'Extracts file property' {
221 $result = $script:JsonWithIssues | ConvertFrom-Json
222 $result[0].file | Should -Be 'docs/guide.md'
223 }
224
225 It 'Extracts line_number property' {
226 $result = $script:JsonWithIssues | ConvertFrom-Json
227 $result[0].line_number | Should -Be 15
228 }
229
230 It 'Extracts original_url property' {
231 $result = $script:JsonWithIssues | ConvertFrom-Json
232 $result[0].original_url | Should -Be 'https://docs.microsoft.com/en-us/azure'
233 }
234 }
235
236 Context 'Empty JSON array' {
237 It 'Handles empty array' {
238 $result = '[]' | ConvertFrom-Json
239 $result | Should -BeNullOrEmpty
240 }
241 }
242
243 Context 'Invalid JSON' {
244 It 'Throws on malformed JSON' {
245 { 'not valid json' | ConvertFrom-Json } | Should -Throw
246 }
247 }
248}
249
250#endregion
251
252#region GitHub Actions Integration Tests
253
254Describe 'GitHub Actions Integration' -Tag 'Unit' {
255 Context 'Module exports verification' {
256 It 'Write-CIAnnotation is available in module' {
257 $module = Get-Module CIHelpers
258 $module.ExportedFunctions.Keys | Should -Contain 'Write-CIAnnotation'
259 }
260
261 It 'Set-CIOutput is available in module' {
262 $module = Get-Module CIHelpers
263 $module.ExportedFunctions.Keys | Should -Contain 'Set-CIOutput'
264 }
265
266 It 'Write-CIStepSummary is available in module' {
267 $module = Get-Module CIHelpers
268 $module.ExportedFunctions.Keys | Should -Contain 'Write-CIStepSummary'
269 }
270 }
271
272 Context 'GitHub Actions detection' {
273 It 'Detects GitHub Actions via GITHUB_ACTIONS env var' {
274 $originalValue = $env:GITHUB_ACTIONS
275 try {
276 $env:GITHUB_ACTIONS = 'true'
277 $env:GITHUB_ACTIONS | Should -Be 'true'
278
279 $env:GITHUB_ACTIONS = $null
280 $env:GITHUB_ACTIONS | Should -BeNullOrEmpty
281 }
282 finally {
283 $env:GITHUB_ACTIONS = $originalValue
284 }
285 }
286 }
287}
288
289#endregion
290
291#region Annotation Generation Tests
292
293Describe 'Annotation Generation' -Tag 'Unit' {
294 Context 'Annotation content' {
295 BeforeEach {
296 $script:Issue = [PSCustomObject]@{
297 file = 'docs/test.md'
298 line_number = 25
299 original_url = 'https://docs.microsoft.com/en-us/azure/overview'
300 }
301 }
302
303 It 'Issue object has required properties' {
304 $script:Issue.file | Should -Not -BeNullOrEmpty
305 $script:Issue.line_number | Should -BeGreaterThan 0
306 $script:Issue.original_url | Should -Match 'en-us'
307 }
308
309 It 'File path is workspace-relative' {
310 $script:Issue.file | Should -Not -Match '^[A-Z]:\\'
311 $script:Issue.file | Should -Not -Match '^/'
312 }
313 }
314
315 Context 'Annotation severity mapping' {
316 It 'Language path issues are warnings' {
317 # Link language issues are warnings, not errors
318 $severity = 'warning'
319 $severity | Should -Be 'warning'
320 }
321 }
322}
323
324#endregion
325
326#region Exit Code Tests
327
328Describe 'Exit Code Handling' -Tag 'Unit' {
329 Context 'No issues found' {
330 It 'Empty result indicates success' {
331 $issues = @()
332 $issues.Count | Should -Be 0
333 }
334 }
335
336 Context 'Issues found' {
337 BeforeEach {
338 $script:Issues = @(
339 [PSCustomObject]@{ file = 'test.md'; line_number = 1; original_url = 'https://example.com/en-us/page' }
340 )
341 }
342
343 It 'Non-empty result indicates issues present' {
344 $script:Issues.Count | Should -BeGreaterThan 0
345 }
346
347 It 'Script should warn but not fail on issues' {
348 # Link language issues are warnings, script continues
349 $warningExpected = $true
350 $warningExpected | Should -BeTrue
351 }
352 }
353}
354
355#endregion
356
357#region Output Format Tests
358
359Describe 'Output Format' -Tag 'Unit' {
360 Context 'Console output' {
361 BeforeEach {
362 $script:SampleIssue = [PSCustomObject]@{
363 file = 'README.md'
364 line_number = 10
365 original_url = 'https://docs.microsoft.com/en-us/azure'
366 }
367 }
368
369 It 'Issue can be formatted as string' {
370 $formatted = "[$($script:SampleIssue.file):$($script:SampleIssue.line_number)] $($script:SampleIssue.original_url)"
371 $formatted | Should -Be '[README.md:10] https://docs.microsoft.com/en-us/azure'
372 }
373 }
374
375 Context 'Summary statistics' {
376 BeforeEach {
377 $script:Issues = @(
378 [PSCustomObject]@{ file = 'a.md'; line_number = 1; original_url = 'url1' },
379 [PSCustomObject]@{ file = 'a.md'; line_number = 2; original_url = 'url2' },
380 [PSCustomObject]@{ file = 'b.md'; line_number = 1; original_url = 'url3' }
381 )
382 }
383
384 It 'Can count total issues' {
385 $script:Issues.Count | Should -Be 3
386 }
387
388 It 'Can count affected files' {
389 $fileCount = ($script:Issues | Select-Object -ExpandProperty file -Unique).Count
390 $fileCount | Should -Be 2
391 }
392 }
393}
394
395#endregion
396
397#region Integration with Link-Lang-Check Tests
398
399Describe 'Link-Lang-Check Integration' -Tag 'Integration' {
400 Context 'Script dependencies' {
401 It 'LintingHelpers module can be imported' {
402 { Import-Module $script:ModulePath -Force } | Should -Not -Throw
403 }
404
405 It 'Link-Lang-Check.ps1 exists at expected path' {
406 $linkLangCheckPath = Join-Path $PSScriptRoot '../../linting/Link-Lang-Check.ps1'
407 Test-Path $linkLangCheckPath | Should -BeTrue
408 }
409 }
410
411 Context 'Output compatibility' {
412 It 'Link-Lang-Check output can be parsed as JSON' {
413 # Sample output format from Link-Lang-Check.ps1
414 $sampleOutput = '[{"file":"test.md","line_number":1,"original_url":"https://example.com/en-us/page"}]'
415 { $sampleOutput | ConvertFrom-Json } | Should -Not -Throw
416 }
417
418 It 'Parsed output has expected structure' {
419 $sampleOutput = '[{"file":"test.md","line_number":1,"original_url":"https://example.com/en-us/page"}]'
420 $parsed = $sampleOutput | ConvertFrom-Json
421 $parsed[0].PSObject.Properties.Name | Should -Contain 'file'
422 $parsed[0].PSObject.Properties.Name | Should -Contain 'line_number'
423 $parsed[0].PSObject.Properties.Name | Should -Contain 'original_url'
424 }
425 }
426}
427
428#endregion
429
430#region OutputPath Parameter Tests
431
432Describe 'OutputPath Parameter' -Tag 'Unit' {
433 Context 'Default OutputPath' {
434 BeforeEach {
435 $script:RepoRoot = $TestDrive
436 $script:DefaultOutputPath = Join-Path $script:RepoRoot "logs/link-lang-check-results.json"
437 $script:MockLinkLang = Join-Path $TestDrive 'mock-link-lang-empty.ps1'
438
439 @'
440param([string[]]$ExcludePaths = @())
441Write-Output "[]"
442'@ | Set-Content -Path $script:MockLinkLang -Encoding utf8
443
444 Mock git {
445 $global:LASTEXITCODE = 0
446 return $script:RepoRoot
447 } -ParameterFilter { $args -contains 'rev-parse' }
448
449 Mock Join-Path {
450 return $script:MockLinkLang
451 } -ParameterFilter { $ChildPath -eq 'Link-Lang-Check.ps1' }
452
453 function Set-CIOutput { param($Name, $Value) }
454 function Set-CIEnv { param($Name, $Value) }
455 function Write-CIAnnotation { param($Message, $Level, $File, $Line) }
456 function Write-CIStepSummary { param($Content) }
457 }
458
459 It 'writes to default path when OutputPath not specified' {
460 $logsDir = Join-Path $script:RepoRoot "logs"
461 if (Test-Path $logsDir) { Remove-Item $logsDir -Recurse -Force }
462
463 Invoke-LinkLanguageCheckCore -ExcludePaths @() -OutputPath 'logs/link-lang-check-results.json' | Out-Null
464
465 Test-Path $script:DefaultOutputPath | Should -BeTrue
466
467 $content = Get-Content $script:DefaultOutputPath -Raw
468 $json = $content | ConvertFrom-Json
469 $json.summary.total_issues | Should -Be 0
470 }
471 }
472
473 Context 'Custom OutputPath' {
474 BeforeEach {
475 $script:RepoRoot = $TestDrive
476 $script:CustomOutputPath = Join-Path $TestDrive "custom/results/custom-output.json"
477 $script:MockLinkLang = Join-Path $TestDrive 'mock-link-lang-empty.ps1'
478
479 @'
480param([string[]]$ExcludePaths = @())
481Write-Output "[]"
482'@ | Set-Content -Path $script:MockLinkLang -Encoding utf8
483
484 Mock git {
485 $global:LASTEXITCODE = 0
486 return $script:RepoRoot
487 } -ParameterFilter { $args -contains 'rev-parse' }
488
489 Mock Join-Path {
490 return $script:MockLinkLang
491 } -ParameterFilter { $ChildPath -eq 'Link-Lang-Check.ps1' }
492
493 function Set-CIOutput { param($Name, $Value) }
494 function Set-CIEnv { param($Name, $Value) }
495 function Write-CIAnnotation { param($Message, $Level, $File, $Line) }
496 function Write-CIStepSummary { param($Content) }
497 }
498
499 It 'writes to custom path when OutputPath is specified' {
500 $customDir = Split-Path $script:CustomOutputPath -Parent
501 if (Test-Path $customDir) { Remove-Item $customDir -Recurse -Force }
502
503 Invoke-LinkLanguageCheckCore -ExcludePaths @() -OutputPath $script:CustomOutputPath | Out-Null
504
505 Test-Path $script:CustomOutputPath | Should -BeTrue
506 Test-Path (Join-Path $script:RepoRoot "logs/link-lang-check-results.json") | Should -BeFalse
507
508 $content = Get-Content $script:CustomOutputPath -Raw
509 $json = $content | ConvertFrom-Json
510 $json.script | Should -Be 'link-lang-check'
511 }
512
513 It 'creates parent directory if it does not exist' {
514 $deepPath = Join-Path $TestDrive "a/b/c/output.json"
515 $parentDir = Split-Path $deepPath -Parent
516
517 if (Test-Path $parentDir) { Remove-Item $parentDir -Recurse -Force }
518
519 Invoke-LinkLanguageCheckCore -ExcludePaths @() -OutputPath $deepPath | Out-Null
520
521 Test-Path $parentDir | Should -BeTrue
522 Test-Path $deepPath | Should -BeTrue
523 }
524 }
525
526 Context 'OutputPath with issues found' {
527 BeforeEach {
528 $script:RepoRoot = $TestDrive
529 $script:TestOutputPath = Join-Path $TestDrive "test-results/issues.json"
530 $script:MockLinkLang = Join-Path $TestDrive 'mock-link-lang-issues.ps1'
531
532 @'
533param([string[]]$ExcludePaths = @())
534$json = @"
535[{"file":"docs/test.md","line_number":5,"original_url":"https://example.com/en-us/page"}]
536"@
537Write-Output $json
538'@ | Set-Content -Path $script:MockLinkLang -Encoding utf8
539
540 Mock git {
541 $global:LASTEXITCODE = 0
542 return $script:RepoRoot
543 } -ParameterFilter { $args -contains 'rev-parse' }
544
545 Mock Join-Path {
546 return $script:MockLinkLang
547 } -ParameterFilter { $ChildPath -eq 'Link-Lang-Check.ps1' }
548
549 function Set-CIOutput { param($Name, $Value) }
550 function Set-CIEnv { param($Name, $Value) }
551 function Write-CIAnnotation { param($Message, $Level, $File, $Line) }
552 function Write-CIStepSummary { param($Content) }
553 function Get-CIPlatform { return 'github' }
554 function ConvertTo-AzureDevOpsEscaped { param($Value) return $Value }
555 }
556
557 It 'writes issues to specified OutputPath' {
558 Invoke-LinkLanguageCheckCore -ExcludePaths @() -OutputPath $script:TestOutputPath | Out-Null
559
560 Test-Path $script:TestOutputPath | Should -BeTrue
561
562 $content = Get-Content $script:TestOutputPath -Raw
563 $json = $content | ConvertFrom-Json
564 $json.summary.total_issues | Should -Be 1
565 $json.issues[0].file | Should -Be 'docs/test.md'
566 $json.issues[0].original_url | Should -Match 'en-us'
567 }
568 }
569}
570
571#endregion
572