microsoft/hve-core
Publicmirrored fromhttps://github.com/microsoft/hve-coreAvailable
scripts/tests/linting/Invoke-JsonLint.Tests.ps1
190lines · modecode
| 1 | #Requires -Modules Pester |
| 2 | # Copyright (c) Microsoft Corporation. |
| 3 | # SPDX-License-Identifier: MIT |
| 4 | <# |
| 5 | .SYNOPSIS |
| 6 | Pester tests for Invoke-JsonLint.ps1 script |
| 7 | .DESCRIPTION |
| 8 | Tests for the strict JSON validator: |
| 9 | - Strict parsing of valid and invalid JSON |
| 10 | - Detection of trailing commas, comments, and trailing content |
| 11 | - File discovery across target paths |
| 12 | - CI integration |
| 13 | #> |
| 14 | |
| 15 | BeforeAll { |
| 16 | $script:ScriptPath = Join-Path $PSScriptRoot '../../linting/Invoke-JsonLint.ps1' |
| 17 | $script:ModulePath = Join-Path $PSScriptRoot '../../linting/Modules/LintingHelpers.psm1' |
| 18 | $script:CIHelpersPath = Join-Path $PSScriptRoot '../../lib/Modules/CIHelpers.psm1' |
| 19 | |
| 20 | Import-Module $script:ModulePath -Force |
| 21 | Import-Module $script:CIHelpersPath -Force |
| 22 | |
| 23 | . $script:ScriptPath |
| 24 | |
| 25 | $script:TestRoot = Join-Path ([System.IO.Path]::GetTempPath()) ("json-lint-tests-" + [System.Guid]::NewGuid().ToString('N')) |
| 26 | New-Item -ItemType Directory -Force -Path $script:TestRoot | Out-Null |
| 27 | } |
| 28 | |
| 29 | AfterAll { |
| 30 | if ($script:TestRoot -and (Test-Path $script:TestRoot)) { |
| 31 | Remove-Item -Path $script:TestRoot -Recurse -Force -ErrorAction SilentlyContinue |
| 32 | } |
| 33 | Remove-Module LintingHelpers -Force -ErrorAction SilentlyContinue |
| 34 | Remove-Module CIHelpers -Force -ErrorAction SilentlyContinue |
| 35 | } |
| 36 | |
| 37 | #region Test-JsonFile Tests |
| 38 | |
| 39 | Describe 'Test-JsonFile' -Tag 'Unit' { |
| 40 | BeforeEach { |
| 41 | $script:CaseDir = Join-Path $script:TestRoot ([System.Guid]::NewGuid().ToString('N')) |
| 42 | New-Item -ItemType Directory -Force -Path $script:CaseDir | Out-Null |
| 43 | } |
| 44 | |
| 45 | It 'Returns null for valid JSON' { |
| 46 | $file = Join-Path $script:CaseDir 'valid.json' |
| 47 | '{ "name": "ok", "values": [1, 2, 3] }' | Set-Content -LiteralPath $file |
| 48 | Test-JsonFile -Path $file | Should -BeNullOrEmpty |
| 49 | } |
| 50 | |
| 51 | It 'Returns an issue for a trailing comma' { |
| 52 | $file = Join-Path $script:CaseDir 'trailing-comma.json' |
| 53 | '{ "name": "bad", }' | Set-Content -LiteralPath $file |
| 54 | $result = Test-JsonFile -Path $file |
| 55 | $result | Should -Not -BeNullOrEmpty |
| 56 | $result.File | Should -Be $file |
| 57 | } |
| 58 | |
| 59 | It 'Returns an issue for a JSON comment' { |
| 60 | $file = Join-Path $script:CaseDir 'comment.json' |
| 61 | @' |
| 62 | { |
| 63 | // not allowed |
| 64 | "name": "bad" |
| 65 | } |
| 66 | '@ | Set-Content -LiteralPath $file |
| 67 | Test-JsonFile -Path $file | Should -Not -BeNullOrEmpty |
| 68 | } |
| 69 | |
| 70 | It 'Returns an issue for trailing content after the root value' { |
| 71 | $file = Join-Path $script:CaseDir 'concat.json' |
| 72 | '{ "a": 1 }{ "b": 2 }' | Set-Content -LiteralPath $file |
| 73 | Test-JsonFile -Path $file | Should -Not -BeNullOrEmpty |
| 74 | } |
| 75 | |
| 76 | It 'Returns an issue for an empty file' { |
| 77 | $file = Join-Path $script:CaseDir 'empty.json' |
| 78 | '' | Set-Content -LiteralPath $file |
| 79 | $result = Test-JsonFile -Path $file |
| 80 | $result | Should -Not -BeNullOrEmpty |
| 81 | $result.Message | Should -Match 'empty' |
| 82 | } |
| 83 | |
| 84 | It 'Reports a positive line number for malformed JSON on a later line' { |
| 85 | $file = Join-Path $script:CaseDir 'line.json' |
| 86 | @' |
| 87 | { |
| 88 | "a": 1, |
| 89 | "b": 2, |
| 90 | } |
| 91 | '@ | Set-Content -LiteralPath $file |
| 92 | $result = Test-JsonFile -Path $file |
| 93 | $result.Line | Should -BeGreaterThan 0 |
| 94 | } |
| 95 | } |
| 96 | |
| 97 | #endregion Test-JsonFile Tests |
| 98 | |
| 99 | #region Invoke-JsonLintCore Tests |
| 100 | |
| 101 | Describe 'Invoke-JsonLintCore' -Tag 'Unit' { |
| 102 | BeforeEach { |
| 103 | Mock Set-CIOutput {} |
| 104 | Mock Set-CIEnv {} |
| 105 | Mock Write-CIStepSummary {} |
| 106 | Mock Write-CIAnnotation {} |
| 107 | |
| 108 | $script:CaseDir = Join-Path $script:TestRoot ([System.Guid]::NewGuid().ToString('N')) |
| 109 | New-Item -ItemType Directory -Force -Path $script:CaseDir | Out-Null |
| 110 | $script:Output = Join-Path $script:CaseDir 'json-lint-results.json' |
| 111 | } |
| 112 | |
| 113 | It 'Does not throw when all JSON files are valid' { |
| 114 | '{ "ok": true }' | Set-Content -LiteralPath (Join-Path $script:CaseDir 'a.json') |
| 115 | '[1, 2, 3]' | Set-Content -LiteralPath (Join-Path $script:CaseDir 'b.json') |
| 116 | { Invoke-JsonLintCore -Paths @($script:CaseDir) -OutputPath $script:Output } | Should -Not -Throw |
| 117 | } |
| 118 | |
| 119 | It 'Throws when a JSON file is malformed' { |
| 120 | '{ "ok": true }' | Set-Content -LiteralPath (Join-Path $script:CaseDir 'good.json') |
| 121 | '{ "bad": true, }' | Set-Content -LiteralPath (Join-Path $script:CaseDir 'bad.json') |
| 122 | { Invoke-JsonLintCore -Paths @($script:CaseDir) -OutputPath $script:Output } | Should -Throw |
| 123 | } |
| 124 | |
| 125 | It 'Does not throw when there are no JSON files to analyze' { |
| 126 | { Invoke-JsonLintCore -Paths @($script:CaseDir) -OutputPath $script:Output } | Should -Not -Throw |
| 127 | } |
| 128 | |
| 129 | It 'Skips missing target paths without throwing' { |
| 130 | $missing = Join-Path $script:CaseDir 'does-not-exist' |
| 131 | { Invoke-JsonLintCore -Paths @($missing) -OutputPath $script:Output } | Should -Not -Throw |
| 132 | } |
| 133 | |
| 134 | It 'Accepts a single JSON file as a target path' { |
| 135 | $file = Join-Path $script:CaseDir 'single.json' |
| 136 | '{ "ok": true }' | Set-Content -LiteralPath $file |
| 137 | { Invoke-JsonLintCore -Paths @($file) -OutputPath $script:Output } | Should -Not -Throw |
| 138 | } |
| 139 | |
| 140 | It 'Filters changed files to only those under target Paths in ChangedFilesOnly mode' { |
| 141 | Mock Get-ChangedFilesFromGit { @('scripts/linting/schemas/a.json', 'docs/x.json') } |
| 142 | Mock Test-JsonFile { $null } |
| 143 | Invoke-JsonLintCore -Paths @('scripts/linting/schemas') -ChangedFilesOnly -OutputPath $script:Output |
| 144 | Assert-MockCalled Test-JsonFile -Times 1 -Exactly -ParameterFilter { $Path -eq 'scripts/linting/schemas/a.json' } |
| 145 | Assert-MockCalled Test-JsonFile -Times 0 -Exactly -ParameterFilter { $Path -eq 'docs/x.json' } |
| 146 | } |
| 147 | |
| 148 | It 'Creates the OutputPath parent directory when missing' { |
| 149 | '{ "ok": true }' | Set-Content -LiteralPath (Join-Path $script:CaseDir 'a.json') |
| 150 | $nested = Join-Path $script:CaseDir 'nested/sub/json-lint-results.json' |
| 151 | { Invoke-JsonLintCore -Paths @($script:CaseDir) -OutputPath $nested } | Should -Not -Throw |
| 152 | Test-Path (Split-Path $nested -Parent) | Should -BeTrue |
| 153 | } |
| 154 | |
| 155 | It 'Emits a CI annotation with File and Error level for malformed JSON' { |
| 156 | '{ "x": 1, }' | Set-Content -LiteralPath (Join-Path $script:CaseDir 'bad.json') |
| 157 | { Invoke-JsonLintCore -Paths @($script:CaseDir) -OutputPath $script:Output } | Should -Throw |
| 158 | Assert-MockCalled Write-CIAnnotation -ParameterFilter { $File -like '*bad.json' -and $Level -eq 'Error' } |
| 159 | } |
| 160 | } |
| 161 | |
| 162 | #endregion Invoke-JsonLintCore Tests |
| 163 | |
| 164 | #region Repository JSON Tests |
| 165 | |
| 166 | Describe 'Repository JSON validity' -Tag 'Integration' { |
| 167 | It 'All schema and fixture JSON files parse strictly' { |
| 168 | $repoRoot = Join-Path $PSScriptRoot '../../..' |
| 169 | $targets = @('scripts/linting/schemas', 'scripts/tests/Fixtures') | ForEach-Object { |
| 170 | Join-Path $repoRoot $_ |
| 171 | } |
| 172 | |
| 173 | $failures = @() |
| 174 | foreach ($target in $targets) { |
| 175 | if (-not (Test-Path $target)) { continue } |
| 176 | Get-ChildItem -Path $target -File -Recurse -Filter '*.json' | |
| 177 | Where-Object { $_.Name -notlike 'invalid-*.json' } | |
| 178 | ForEach-Object { |
| 179 | $issue = Test-JsonFile -Path $_.FullName |
| 180 | if ($null -ne $issue) { |
| 181 | $failures += "$($issue.File): $($issue.Message)" |
| 182 | } |
| 183 | } |
| 184 | } |
| 185 | |
| 186 | $failures | Should -BeNullOrEmpty |
| 187 | } |
| 188 | } |
| 189 | |
| 190 | #endregion Repository JSON Tests |
| 191 | |