microsoft/hve-core

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
copilot/update-workflow-file-and-script

Branches

Tags

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

Clone

HTTPS

Download ZIP

scripts/tests/linting/Link-Lang-Check.Tests.ps1

572lines · modecode

1#Requires -Modules Pester
2# Copyright (c) Microsoft Corporation.
3# SPDX-License-Identifier: MIT
4<#
5.SYNOPSIS
6 Pester tests for Link-Lang-Check.ps1 script
7.DESCRIPTION
8 Tests for language path link checker functions:
9 - Get-GitTextFile
10 - Find-LinksInFile
11 - Repair-LinksInFile
12 - Repair-AllLink
13 - ConvertTo-JsonOutput
14#>
15
16BeforeAll {
17 $script:ScriptPath = Join-Path $PSScriptRoot '../../linting/Link-Lang-Check.ps1'
18 . $script:ScriptPath
19
20 $script:FixtureDir = Join-Path $PSScriptRoot '../Fixtures/Linting'
21}
22
23AfterAll {
24}
25
26#region Get-GitTextFile Tests
27
28Describe 'Get-GitTextFile' -Tag 'Unit' {
29 Context 'Git command succeeds' {
30 BeforeEach {
31 Mock git {
32 $global:LASTEXITCODE = 0
33 return @('file1.md', 'file2.ps1', 'subdir/file3.txt')
34 } -ParameterFilter { $args -contains '--name-only' }
35 }
36
37 It 'Returns array of file paths' {
38 $result = Get-GitTextFile
39 $result | Should -BeOfType [string]
40 $result.Count | Should -Be 3
41 }
42
43 It 'Includes all returned files' {
44 $result = Get-GitTextFile
45 $result | Should -Contain 'file1.md'
46 $result | Should -Contain 'file2.ps1'
47 $result | Should -Contain 'subdir/file3.txt'
48 }
49 }
50
51 Context 'Git command fails' {
52 BeforeEach {
53 Mock git {
54 $global:LASTEXITCODE = 128
55 return 'fatal: not a git repository'
56 } -ParameterFilter { $args -contains '--name-only' }
57
58 Mock Write-Error {}
59 }
60
61 It 'Returns empty array on git error' {
62 $result = Get-GitTextFile
63 $result | Should -BeNullOrEmpty
64 }
65 }
66
67 Context 'Empty repository' {
68 BeforeEach {
69 Mock git {
70 $global:LASTEXITCODE = 0
71 return @()
72 } -ParameterFilter { $args -contains '--name-only' }
73 }
74
75 It 'Returns empty array for empty repo' {
76 $result = Get-GitTextFile
77 $result | Should -BeNullOrEmpty
78 }
79 }
80}
81
82#endregion
83
84#region Find-LinksInFile Tests
85
86Describe 'Find-LinksInFile' -Tag 'Unit' {
87 BeforeAll {
88 $script:TempDir = Join-Path ([System.IO.Path]::GetTempPath()) ([System.Guid]::NewGuid().ToString())
89 New-Item -ItemType Directory -Path $script:TempDir -Force | Out-Null
90 }
91
92 AfterAll {
93 Remove-Item -Path $script:TempDir -Recurse -Force -ErrorAction SilentlyContinue
94 }
95
96 Context 'File with en-us links' {
97 BeforeEach {
98 $script:TestFile = Join-Path $script:TempDir 'test-links.md'
99 @'
100# Test Document
101
102Visit https://docs.microsoft.com/en-us/azure for Azure docs.
103Also see https://learn.microsoft.com/en-us/dotnet/api for .NET API.
104'@ | Set-Content -Path $script:TestFile
105 }
106
107 It 'Finds all en-us links' {
108 $result = Find-LinksInFile -FilePath $script:TestFile
109 $result.Count | Should -Be 2
110 }
111
112 It 'Returns correct file path' {
113 $result = Find-LinksInFile -FilePath $script:TestFile
114 $result[0].File | Should -Be $script:TestFile
115 }
116
117 It 'Returns correct line numbers' {
118 $result = Find-LinksInFile -FilePath $script:TestFile
119 $result[0].LineNumber | Should -Be 3
120 $result[1].LineNumber | Should -Be 4
121 }
122
123 It 'Provides fixed URL without en-us' {
124 $result = Find-LinksInFile -FilePath $script:TestFile
125 $result[0].FixedUrl | Should -Not -Match 'en-us/'
126 $result[0].FixedUrl | Should -Be 'https://docs.microsoft.com/azure'
127 }
128 }
129
130 Context 'File without en-us links' {
131 BeforeEach {
132 $script:CleanFile = Join-Path $script:TempDir 'clean-links.md'
133 @'
134# Clean Document
135
136Visit https://docs.microsoft.com/azure for docs.
137'@ | Set-Content -Path $script:CleanFile
138 }
139
140 It 'Returns empty array when no en-us links found' {
141 $result = Find-LinksInFile -FilePath $script:CleanFile
142 $result | Should -BeNullOrEmpty
143 }
144 }
145
146 Context 'Nonexistent file' {
147 It 'Returns empty array for nonexistent file' {
148 $result = Find-LinksInFile -FilePath 'C:\nonexistent\file.md'
149 $result | Should -BeNullOrEmpty
150 }
151 }
152
153 Context 'Multiple links on same line' {
154 BeforeEach {
155 $script:MultiLinkFile = Join-Path $script:TempDir 'multi-links.md'
156 'See https://docs.microsoft.com/en-us/a and https://docs.microsoft.com/en-us/b here.' |
157 Set-Content -Path $script:MultiLinkFile
158 }
159
160 It 'Finds all links on same line' {
161 $result = Find-LinksInFile -FilePath $script:MultiLinkFile
162 $result.Count | Should -Be 2
163 $result[0].LineNumber | Should -Be 1
164 $result[1].LineNumber | Should -Be 1
165 }
166 }
167}
168
169#endregion
170
171#region Repair-LinksInFile Tests
172
173Describe 'Repair-LinksInFile' -Tag 'Unit' {
174 BeforeAll {
175 $script:TempDir = Join-Path ([System.IO.Path]::GetTempPath()) ([System.Guid]::NewGuid().ToString())
176 New-Item -ItemType Directory -Path $script:TempDir -Force | Out-Null
177 }
178
179 AfterAll {
180 Remove-Item -Path $script:TempDir -Recurse -Force -ErrorAction SilentlyContinue
181 }
182
183 Context 'File with links to repair' {
184 BeforeEach {
185 $script:RepairFile = Join-Path $script:TempDir 'repair-test.md'
186 'Visit https://docs.microsoft.com/en-us/azure for docs.' |
187 Set-Content -Path $script:RepairFile
188
189 $script:Links = @(
190 [PSCustomObject]@{
191 OriginalUrl = 'https://docs.microsoft.com/en-us/azure'
192 FixedUrl = 'https://docs.microsoft.com/azure'
193 }
194 )
195 }
196
197 It 'Returns true when file is modified' {
198 $result = Repair-LinksInFile -FilePath $script:RepairFile -Links $script:Links
199 $result | Should -BeTrue
200 }
201
202 It 'Replaces en-us in file content' {
203 Repair-LinksInFile -FilePath $script:RepairFile -Links $script:Links
204 $content = Get-Content -Path $script:RepairFile -Raw
205 $content | Should -Not -Match 'en-us/'
206 $content | Should -Match 'https://docs.microsoft.com/azure'
207 }
208 }
209
210 Context 'File with no changes needed' {
211 BeforeEach {
212 $script:NoChangeFile = Join-Path $script:TempDir 'no-change.md'
213 'Visit https://docs.microsoft.com/azure for docs.' |
214 Set-Content -Path $script:NoChangeFile
215
216 $script:NoMatchLinks = @(
217 [PSCustomObject]@{
218 OriginalUrl = 'https://example.com/en-us/page'
219 FixedUrl = 'https://example.com/page'
220 }
221 )
222 }
223
224 It 'Returns false when no changes made' {
225 $result = Repair-LinksInFile -FilePath $script:NoChangeFile -Links $script:NoMatchLinks
226 $result | Should -BeFalse
227 }
228 }
229
230 Context 'Nonexistent file' {
231 It 'Returns false for nonexistent file' {
232 $links = @([PSCustomObject]@{ OriginalUrl = 'a'; FixedUrl = 'b' })
233 $result = Repair-LinksInFile -FilePath 'C:\nonexistent\file.md' -Links $links
234 $result | Should -BeFalse
235 }
236 }
237}
238
239#endregion
240
241#region Repair-AllLink Tests
242
243Describe 'Repair-AllLink' -Tag 'Unit' {
244 BeforeAll {
245 $script:TempDir = Join-Path ([System.IO.Path]::GetTempPath()) ([System.Guid]::NewGuid().ToString())
246 New-Item -ItemType Directory -Path $script:TempDir -Force | Out-Null
247 }
248
249 AfterAll {
250 Remove-Item -Path $script:TempDir -Recurse -Force -ErrorAction SilentlyContinue
251 }
252
253 Context 'Multiple files with links' {
254 BeforeEach {
255 $script:File1 = Join-Path $script:TempDir 'file1.md'
256 $script:File2 = Join-Path $script:TempDir 'file2.md'
257
258 'Link: https://docs.microsoft.com/en-us/a' | Set-Content -Path $script:File1
259 'Link: https://docs.microsoft.com/en-us/b' | Set-Content -Path $script:File2
260
261 $script:AllLinks = @(
262 [PSCustomObject]@{
263 File = $script:File1
264 LineNumber = 1
265 OriginalUrl = 'https://docs.microsoft.com/en-us/a'
266 FixedUrl = 'https://docs.microsoft.com/a'
267 },
268 [PSCustomObject]@{
269 File = $script:File2
270 LineNumber = 1
271 OriginalUrl = 'https://docs.microsoft.com/en-us/b'
272 FixedUrl = 'https://docs.microsoft.com/b'
273 }
274 )
275 }
276
277 It 'Returns count of modified files' {
278 $result = Repair-AllLink -AllLinks $script:AllLinks
279 $result | Should -Be 2
280 }
281
282 It 'Modifies all files' {
283 Repair-AllLink -AllLinks $script:AllLinks
284 (Get-Content $script:File1 -Raw) | Should -Not -Match 'en-us/'
285 (Get-Content $script:File2 -Raw) | Should -Not -Match 'en-us/'
286 }
287 }
288
289 Context 'Empty links array' {
290 It 'Returns zero for empty input' {
291 $result = Repair-AllLink -AllLinks @()
292 $result | Should -Be 0
293 }
294 }
295}
296
297#endregion
298
299#region ConvertTo-JsonOutput Tests
300
301Describe 'ConvertTo-JsonOutput' -Tag 'Unit' {
302 Context 'Valid link objects' {
303 BeforeEach {
304 $script:Links = @(
305 [PSCustomObject]@{
306 File = 'test.md'
307 LineNumber = 5
308 OriginalUrl = 'https://example.com/en-us/page'
309 FixedUrl = 'https://example.com/page'
310 }
311 )
312 }
313
314 It 'Returns array of objects' {
315 $result = ConvertTo-JsonOutput -Links $script:Links
316 $result | Should -BeOfType [PSCustomObject]
317 }
318
319 It 'Uses snake_case property names' {
320 $result = ConvertTo-JsonOutput -Links $script:Links
321 $result[0].PSObject.Properties.Name | Should -Contain 'file'
322 $result[0].PSObject.Properties.Name | Should -Contain 'line_number'
323 $result[0].PSObject.Properties.Name | Should -Contain 'original_url'
324 }
325
326 It 'Excludes FixedUrl from output' {
327 $result = ConvertTo-JsonOutput -Links $script:Links
328 $result[0].PSObject.Properties.Name | Should -Not -Contain 'FixedUrl'
329 $result[0].PSObject.Properties.Name | Should -Not -Contain 'fixed_url'
330 }
331
332 It 'Preserves values correctly' {
333 $result = ConvertTo-JsonOutput -Links $script:Links
334 $result[0].file | Should -Be 'test.md'
335 $result[0].line_number | Should -Be 5
336 $result[0].original_url | Should -Be 'https://example.com/en-us/page'
337 }
338 }
339
340 Context 'Empty input' {
341 It 'Returns empty array for empty input' {
342 $result = ConvertTo-JsonOutput -Links @()
343 $result | Should -BeNullOrEmpty
344 }
345 }
346}
347
348#endregion
349
350#region ExcludePaths Filtering Tests
351
352Describe 'ExcludePaths Filtering' -Tag 'Integration' {
353 BeforeAll {
354 $script:ScriptPath = Join-Path $PSScriptRoot '../../linting/Link-Lang-Check.ps1'
355 $script:TempDir = Join-Path ([System.IO.Path]::GetTempPath()) ([System.Guid]::NewGuid().ToString())
356 New-Item -ItemType Directory -Path $script:TempDir -Force | Out-Null
357 }
358
359 AfterAll {
360 Remove-Item -Path $script:TempDir -Recurse -Force -ErrorAction SilentlyContinue
361 }
362
363 Context 'Script invocation with ExcludePaths' {
364 BeforeEach {
365 # Create test directory structure
366 $script:TestsDir = Join-Path $script:TempDir 'scripts/tests/linting'
367 $script:DocsDir = Join-Path $script:TempDir 'docs'
368 New-Item -ItemType Directory -Path $script:TestsDir -Force | Out-Null
369 New-Item -ItemType Directory -Path $script:DocsDir -Force | Out-Null
370
371 # Create test file with en-us link (should be excluded)
372 $testFile = Join-Path $script:TestsDir 'test.md'
373 'Link: https://docs.microsoft.com/en-us/test' | Set-Content -Path $testFile
374
375 # Create docs file with en-us link (should be included)
376 $docsFile = Join-Path $script:DocsDir 'readme.md'
377 'Link: https://docs.microsoft.com/en-us/azure' | Set-Content -Path $docsFile
378 }
379
380 It 'Excludes files matching single pattern' {
381 Push-Location $script:TempDir
382 try {
383 # Initialize git repo for Get-GitTextFile to work
384 git init --quiet 2>$null
385 git add -A 2>$null
386 git commit -m 'init' --quiet 2>$null
387
388 # Script outputs JSON by default (when not in -Fix mode)
389 $result = & $script:ScriptPath -ExcludePaths 'scripts/tests/**' 2>$null
390 $jsonResult = $result | ConvertFrom-Json -ErrorAction SilentlyContinue
391
392 # Should only find the docs file, not the tests file
393 if ($null -ne $jsonResult -and $jsonResult.Count -gt 0) {
394 $jsonResult | ForEach-Object { $_.file } | Should -Not -Match 'scripts/tests'
395 }
396 }
397 finally {
398 Pop-Location
399 }
400 }
401
402 It 'Excludes files matching multiple patterns' {
403 Push-Location $script:TempDir
404 try {
405 # Create additional directory to exclude
406 $buildDir = Join-Path $script:TempDir 'build'
407 New-Item -ItemType Directory -Path $buildDir -Force | Out-Null
408 $buildFile = Join-Path $buildDir 'output.md'
409 'Link: https://docs.microsoft.com/en-us/build' | Set-Content -Path $buildFile
410
411 git add -A 2>$null
412 git commit -m 'add build' --quiet 2>$null
413
414 $result = & $script:ScriptPath -ExcludePaths @('scripts/tests/**', 'build/**') 2>$null
415 $jsonResult = $result | ConvertFrom-Json -ErrorAction SilentlyContinue
416
417 if ($null -ne $jsonResult -and $jsonResult.Count -gt 0) {
418 $files = $jsonResult | ForEach-Object { $_.file }
419 $files | Should -Not -Match 'scripts/tests'
420 $files | Should -Not -Match 'build/'
421 }
422 }
423 finally {
424 Pop-Location
425 }
426 }
427
428 It 'Processes all files when ExcludePaths is empty' {
429 Push-Location $script:TempDir
430 try {
431 $result = & $script:ScriptPath 2>$null
432 $jsonResult = $result | ConvertFrom-Json -ErrorAction SilentlyContinue
433
434 # Should find links in both test and docs files
435 $jsonResult.Count | Should -BeGreaterOrEqual 2
436 }
437 finally {
438 Pop-Location
439 }
440 }
441 }
442
443 Context 'Pattern matching behavior' {
444 It 'Matches glob pattern with double asterisk' {
445 # Test the -like pattern matching used in the script
446 $testPaths = @(
447 'scripts/tests/linting/test.md',
448 'scripts/tests/security/check.ps1',
449 'scripts/linting/main.ps1',
450 'docs/readme.md'
451 )
452 $pattern = 'scripts/tests/**'
453
454 $excluded = $testPaths | Where-Object { $_ -like $pattern }
455 $included = $testPaths | Where-Object { $_ -notlike $pattern }
456
457 $excluded | Should -Contain 'scripts/tests/linting/test.md'
458 $excluded | Should -Contain 'scripts/tests/security/check.ps1'
459 $included | Should -Contain 'scripts/linting/main.ps1'
460 $included | Should -Contain 'docs/readme.md'
461 }
462
463 It 'Matches multiple patterns correctly' {
464 $testPaths = @(
465 'scripts/tests/test.md',
466 'build/output.md',
467 'node_modules/pkg/file.js',
468 'src/main.ps1'
469 )
470 $patterns = @('scripts/tests/**', 'build/**', 'node_modules/**')
471
472 $included = $testPaths | Where-Object {
473 $path = $_
474 $isExcluded = $false
475 foreach ($p in $patterns) {
476 if ($path -like $p) {
477 $isExcluded = $true
478 break
479 }
480 }
481 -not $isExcluded
482 }
483
484 $included.Count | Should -Be 1
485 $included | Should -Contain 'src/main.ps1'
486 }
487 }
488}
489
490#endregion
491
492#region Invoke-LinkLanguageCheck Tests
493
494Describe 'Invoke-LinkLanguageCheck' -Tag 'Unit' {
495 BeforeAll {
496 Mock Get-GitTextFile { return @('file1.md', 'file2.md') }
497 Mock Test-Path { return $true } -ParameterFilter { $PathType -eq 'Leaf' }
498 }
499
500 Context 'No links found' {
501 BeforeAll {
502 Mock Find-LinksInFile { return @() }
503 }
504
505 It 'Outputs empty JSON array when -Fix is not set' {
506 $result = Invoke-LinkLanguageCheck
507 $result | Should -Be '[]'
508 }
509
510 It 'Outputs no-links message when -Fix is set' {
511 $result = Invoke-LinkLanguageCheck -Fix
512 $result | Should -Be "No URLs containing 'en-us' were found."
513 }
514 }
515
516 Context 'Links found with -Fix' {
517 BeforeAll {
518 $script:mockLinks = @(
519 @{ File = 'file1.md'; LineNumber = 5; OriginalUrl = 'https://learn.microsoft.com/en-us/docs'; FixedUrl = 'https://learn.microsoft.com/docs' }
520 )
521 Mock Find-LinksInFile { return $script:mockLinks }
522 Mock Repair-AllLink { return 1 }
523 }
524
525 It 'Calls Repair-AllLink and reports fix count' {
526 $result = Invoke-LinkLanguageCheck -Fix
527 $result | Should -BeLike 'Fixed * URLs in 1 files*'
528 Should -Invoke Repair-AllLink -Times 1
529 }
530 }
531
532 Context 'Links found without -Fix' {
533 BeforeAll {
534 $script:mockLinks = @(
535 @{ File = 'file1.md'; LineNumber = 5; OriginalUrl = 'https://learn.microsoft.com/en-us/docs'; FixedUrl = 'https://learn.microsoft.com/docs' }
536 )
537 Mock Find-LinksInFile { return $script:mockLinks }
538 Mock ConvertTo-JsonOutput { return @(@{ File = 'file1.md'; Line = 5 }) }
539 }
540
541 It 'Outputs JSON via ConvertTo-JsonOutput' {
542 $result = Invoke-LinkLanguageCheck
543 $result | Should -Not -BeNullOrEmpty
544 Should -Invoke ConvertTo-JsonOutput -Times 1
545 }
546 }
547
548 Context 'ExcludePaths filtering' {
549 BeforeAll {
550 Mock Get-GitTextFile { return @('src/file.md', 'node_modules/pkg/file.md', 'build/out.md') }
551 Mock Find-LinksInFile { return @() }
552 }
553
554 It 'Excludes files matching exclusion patterns' {
555 Invoke-LinkLanguageCheck -ExcludePaths @('node_modules/**', 'build/**')
556 Should -Invoke Find-LinksInFile -Times 1
557 }
558 }
559
560 Context 'Non-file paths are skipped' {
561 BeforeAll {
562 Mock Get-GitTextFile { return @('not-a-file') }
563 Mock Test-Path { return $false } -ParameterFilter { $PathType -eq 'Leaf' }
564 Mock Find-LinksInFile { return @() }
565 }
566
567 It 'Does not call Find-LinksInFile for non-files' {
568 Invoke-LinkLanguageCheck
569 Should -Invoke Find-LinksInFile -Times 0
570 }
571 }
572}