microsoft/hve-core

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
docs/stale-docs-refresh-2026-05-20

Branches

Tags

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

Clone

HTTPS

Download ZIP

.github/skills/github/gh-code-scanning/tests/Get-CodeScanningAlerts.Tests.ps1

206lines · modecode

1#Requires -Modules Pester
2# Copyright (c) Microsoft Corporation.
3# SPDX-License-Identifier: MIT
4
5BeforeAll {
6 $script:ScriptPath = Join-Path $PSScriptRoot '../scripts/Get-CodeScanningAlerts.ps1'
7 $script:OriginalGhPager = $env:GH_PAGER
8
9 # Sample alert JSON representing two rules with multiple occurrences
10 $script:MockAlertJson = '[{"number":1,"rule":{"id":"js/sql-injection","description":"Database query built from user-controlled sources","security_severity_level":"high"},"tool":{"name":"CodeQL"},"most_recent_instance":{"location":{"path":"src/db.js"}}},{"number":2,"rule":{"id":"js/sql-injection","description":"Database query built from user-controlled sources","security_severity_level":"high"},"tool":{"name":"CodeQL"},"most_recent_instance":{"location":{"path":"src/api.js"}}},{"number":3,"rule":{"id":"js/xss","description":"Cross-site scripting vulnerability","security_severity_level":"medium"},"tool":{"name":"CodeQL"},"most_recent_instance":{"location":{"path":"src/render.js"}}}]'
11}
12
13AfterAll {
14 $env:GH_PAGER = $script:OriginalGhPager
15}
16
17Describe 'Get-CodeScanningAlerts' -Tag 'Unit' {
18
19 BeforeEach {
20 # Create a gh function in current scope; child scopes (scripts called with &) inherit it.
21 # This intercepts calls to 'gh' without relying on Pester Mock for external executables.
22 $script:capturedGhArgs = $null
23 $capturedArgsRef = [ref]$script:capturedGhArgs
24 $mockJson = $script:MockAlertJson
25 ${Function:gh} = {
26 $capturedArgsRef.Value = $args
27 $global:LASTEXITCODE = 0
28 return $mockJson
29 }.GetNewClosure()
30 }
31
32 AfterEach {
33 Remove-Item -Path 'Function:gh' -ErrorAction SilentlyContinue
34 $global:LASTEXITCODE = 0
35 }
36
37 Context 'Pager suppression' {
38 BeforeEach {
39 $env:GH_PAGER = 'pager-was-set'
40 ${Function:gh} = { $global:LASTEXITCODE = 0 }.GetNewClosure()
41 & $script:ScriptPath -Owner 'owner' -Repo 'repo'
42 }
43 AfterEach {
44 Remove-Item Env:GH_PAGER -ErrorAction SilentlyContinue
45 }
46
47 It 'Suppresses pager by clearing GH_PAGER before invoking gh' {
48 $env:GH_PAGER | Should -BeNullOrEmpty
49 }
50 }
51
52 Context 'Default output format (Table)' {
53 It 'Produces output when OutputFormat is Table (default)' {
54 $result = & $script:ScriptPath -Owner 'testorg' -Repo 'testrepo' | Out-String
55
56 $result | Should -Not -BeNullOrEmpty
57 }
58 }
59
60 Context 'JSON output format' {
61 It 'Produces valid JSON array when OutputFormat is Json' {
62 $result = & $script:ScriptPath -Owner 'testorg' -Repo 'testrepo' -OutputFormat Json
63
64 $parsed = $result | ConvertFrom-Json
65 $parsed | Should -Not -BeNullOrEmpty
66 $parsed.Count | Should -BeGreaterThan 0
67 }
68
69 It 'Groups alerts by rule and sorts by count descending' {
70 $result = & $script:ScriptPath -Owner 'testorg' -Repo 'testrepo' -OutputFormat Json
71 $parsed = $result | ConvertFrom-Json
72
73 $parsed[0].RuleId | Should -Be 'js/sql-injection'
74 $parsed[0].Count | Should -Be 2
75 $parsed[1].RuleId | Should -Be 'js/xss'
76 $parsed[1].Count | Should -Be 1
77 }
78
79 It 'Produces valid JSON array when OutputFormat is GroupedJson' {
80 $result = & $script:ScriptPath -Owner 'testorg' -Repo 'testrepo' -OutputFormat GroupedJson
81
82 $parsed = $result | ConvertFrom-Json
83 $parsed | Should -Not -BeNullOrEmpty
84 $parsed.Count | Should -BeGreaterThan 0
85 }
86
87 It 'Serializes SamplePaths as a JSON array even when only one path exists' {
88 # js/xss has a single occurrence; verify the raw JSON uses bracket notation,
89 # not a bare string (ConvertFrom-Json re-unwraps single-element arrays so
90 # the raw string is the authoritative check)
91 $result = & $script:ScriptPath -Owner 'testorg' -Repo 'testrepo' -OutputFormat Json
92 $rawJson = $result | Out-String
93
94 $rawJson | Should -Match '"SamplePaths":\s*\['
95 }
96
97 It 'Serializes SamplePaths as a JSON array when alert has no associated file path' {
98 $noPathJson = '[{"number":10,"rule":{"id":"BranchProtectionID","description":"Branch-Protection","security_severity_level":"high"},"tool":{"name":"Scorecard"},"most_recent_instance":{"location":{"path":"no file associated with this alert"}}}]'
99 ${Function:gh} = {
100 $global:LASTEXITCODE = 0
101 return $noPathJson
102 }.GetNewClosure()
103
104 $result = & $script:ScriptPath -Owner 'testorg' -Repo 'testrepo' -OutputFormat Json
105 $rawJson = $result | Out-String
106
107 $rawJson | Should -Match '"SamplePaths":\s*\['
108 $rawJson | Should -Match 'no file associated with this alert'
109 }
110
111 It 'Deduplicates and sorts SamplePaths across multiple occurrences of the same rule' {
112 $multiPathJson = '[{"number":1,"rule":{"id":"py/empty-except","description":"Empty except","security_severity_level":null},"tool":{"name":"CodeQL"},"most_recent_instance":{"location":{"path":"scripts/b.py"}}},{"number":2,"rule":{"id":"py/empty-except","description":"Empty except","security_severity_level":null},"tool":{"name":"CodeQL"},"most_recent_instance":{"location":{"path":"scripts/a.py"}}},{"number":3,"rule":{"id":"py/empty-except","description":"Empty except","security_severity_level":null},"tool":{"name":"CodeQL"},"most_recent_instance":{"location":{"path":"scripts/a.py"}}}]'
113 ${Function:gh} = {
114 $global:LASTEXITCODE = 0
115 return $multiPathJson
116 }.GetNewClosure()
117
118 $result = & $script:ScriptPath -Owner 'testorg' -Repo 'testrepo' -OutputFormat Json
119 $parsed = $result | ConvertFrom-Json
120
121 $parsed[0].SamplePaths | Should -HaveCount 2
122 $parsed[0].SamplePaths[0] | Should -Be 'scripts/a.py'
123 $parsed[0].SamplePaths[1] | Should -Be 'scripts/b.py'
124 }
125 }
126
127 Context 'Branch parameter' {
128 It 'Defaults to main branch when Branch is not specified' {
129 & $script:ScriptPath -Owner 'testorg' -Repo 'testrepo' | Out-Null
130
131 $script:capturedGhArgs | Should -Contain 'repos/testorg/testrepo/code-scanning/alerts?state=open&ref=refs/heads/main&per_page=100'
132 }
133
134 It 'Uses specified branch when Branch is provided' {
135 & $script:ScriptPath -Owner 'testorg' -Repo 'testrepo' -Branch 'develop' | Out-Null
136
137 $script:capturedGhArgs | Should -Contain 'repos/testorg/testrepo/code-scanning/alerts?state=open&ref=refs/heads/develop&per_page=100'
138 }
139 }
140
141 Context 'Error propagation' {
142 It 'Throws when gh api returns non-zero exit code' {
143 ${Function:gh} = {
144 $global:LASTEXITCODE = 1
145 return 'Error: authentication required'
146 }
147
148 { & $script:ScriptPath -Owner 'testorg' -Repo 'testrepo' } | Should -Throw
149 }
150
151 It 'Throws with scope refresh hint when gh api returns 403' {
152 ${Function:gh} = {
153 if ($args[0] -eq 'auth') {
154 $global:LASTEXITCODE = 0
155 return 'Logged in to github.com'
156 }
157 $global:LASTEXITCODE = 1
158 return 'HTTP 403: Resource not accessible by integration'
159 }
160
161 { & $script:ScriptPath -Owner 'testorg' -Repo 'testrepo' } | Should -Throw '*gh auth refresh -s security_events*'
162 }
163 }
164}
165
166Describe 'Get-CodeScanningAlerts - Prerequisite guards' -Tag 'Unit' {
167
168 BeforeEach {
169 Remove-Item 'Function:gh' -ErrorAction SilentlyContinue
170 $global:LASTEXITCODE = 0
171 }
172
173 AfterEach {
174 Remove-Item 'Function:gh' -ErrorAction SilentlyContinue
175 Remove-Item 'Function:Get-Command' -ErrorAction SilentlyContinue
176 $global:LASTEXITCODE = 0
177 }
178
179 Context 'gh CLI not available' {
180 It 'Throws with gh install link when gh is not on PATH' {
181 # Shadow Get-Command so it reports gh as missing regardless of environment
182 ${Function:Get-Command} = {
183 if ($args[0] -eq 'gh') { return $null }
184 Microsoft.PowerShell.Core\Get-Command @args
185 }
186
187 { & $script:ScriptPath -Owner 'testorg' -Repo 'testrepo' } | Should -Throw '*https://cli.github.com*'
188 }
189 }
190
191 Context 'gh CLI not authenticated' {
192 It 'Throws with auth hint when gh auth status returns non-zero' {
193 $mockJson = $script:MockAlertJson
194 ${Function:gh} = {
195 if ($args[0] -eq 'auth') {
196 $global:LASTEXITCODE = 1
197 return 'You are not logged into any GitHub hosts.'
198 }
199 $global:LASTEXITCODE = 0
200 return $mockJson
201 }.GetNewClosure()
202
203 { & $script:ScriptPath -Owner 'testorg' -Repo 'testrepo' } | Should -Throw '*gh auth login*'
204 }
205 }
206}
207