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-PythonTests.Tests.ps1

383lines · modecode

1#Requires -Modules Pester
2# Copyright (c) Microsoft Corporation.
3# SPDX-License-Identifier: MIT
4<#
5.SYNOPSIS
6 Pester tests for Invoke-PythonTests.ps1 script
7.DESCRIPTION
8 Tests for Python testing wrapper script:
9 - Parameter validation
10 - Tool availability checks
11 - Skill discovery via pyproject.toml
12 - Tests directory detection
13 - Pytest execution and result handling
14 - Output file generation
15 - Summary counters
16#>
17
18BeforeAll {
19 $script:ScriptPath = Join-Path $PSScriptRoot '../../linting/Invoke-PythonTests.ps1'
20
21 # Create stub function for pytest so it can be mocked even when not installed
22 function global:pytest { '' }
23
24 . $script:ScriptPath
25}
26
27AfterAll {
28 Remove-Item -Path 'Function:\pytest' -Force -ErrorAction SilentlyContinue
29}
30
31#region Parameter Validation Tests
32
33Describe 'Invoke-PythonTests Parameter Validation' -Tag 'Unit' {
34 Context 'RepoRoot parameter' {
35 BeforeEach {
36 Mock Get-ChildItem { @() }
37 Mock Get-Command { [PSCustomObject]@{ Source = 'pytest' } } -ParameterFilter { $Name -eq 'pytest' }
38 Mock Push-Location {}
39 Mock Pop-Location {}
40 }
41
42 It 'Accepts custom RepoRoot' {
43 $repoRoot = Join-Path $TestDrive 'test-repo'
44 New-Item -ItemType Directory -Path $repoRoot -Force | Out-Null
45 { Invoke-PythonTests -RepoRoot $repoRoot } | Should -Not -Throw
46 }
47 }
48
49 Context 'OutputPath parameter' {
50 BeforeEach {
51 Mock Get-ChildItem { @() }
52 Mock Get-Command { [PSCustomObject]@{ Source = 'pytest' } } -ParameterFilter { $Name -eq 'pytest' }
53 Mock Push-Location {}
54 Mock Pop-Location {}
55 }
56
57 It 'Accepts custom OutputPath' {
58 $outputPath = Join-Path $TestDrive 'test-output.json'
59 { Invoke-PythonTests -RepoRoot $TestDrive -OutputPath $outputPath } | Should -Not -Throw
60 }
61 }
62
63 Context 'Verbosity parameter' {
64 BeforeEach {
65 Mock Get-ChildItem { @() }
66 Mock Get-Command { [PSCustomObject]@{ Source = 'pytest' } } -ParameterFilter { $Name -eq 'pytest' }
67 Mock Push-Location {}
68 Mock Pop-Location {}
69 }
70
71 It 'Defaults to -v verbosity' {
72 { Invoke-PythonTests -RepoRoot $TestDrive } | Should -Not -Throw
73 }
74
75 It 'Accepts custom verbosity' {
76 { Invoke-PythonTests -RepoRoot $TestDrive -Verbosity '-vv' } | Should -Not -Throw
77 }
78 }
79}
80
81#endregion
82
83#region Tool Availability Tests
84
85Describe 'pytest Tool Availability' -Tag 'Unit' {
86 Context 'Tool not installed' {
87 BeforeEach {
88 Mock Push-Location {}
89 Mock Pop-Location {}
90 Mock Get-ChildItem {
91 @([PSCustomObject]@{
92 FullName = (Join-Path $TestDrive 'skill1/pyproject.toml')
93 Directory = [PSCustomObject]@{ FullName = (Join-Path $TestDrive 'skill1') }
94 })
95 }
96 Mock Get-Command { $null } -ParameterFilter { $Name -eq 'pytest' }
97 Mock Test-Path { $true } -ParameterFilter { $Path -like '*tests' }
98 }
99
100 It 'Returns failure when pytest not available' {
101 $result = Invoke-PythonTests -RepoRoot $TestDrive
102 $result.success | Should -BeFalse
103 }
104
105 It 'Reports skill path in errors' {
106 $result = Invoke-PythonTests -RepoRoot $TestDrive
107 $result.errors | Should -Contain (Join-Path $TestDrive 'skill1')
108 }
109
110 It 'Reports zero skills tested when pytest missing' {
111 $result = Invoke-PythonTests -RepoRoot $TestDrive
112 $result.skillsTested | Should -Be 0
113 }
114
115 It 'Reports zero passed' {
116 $result = Invoke-PythonTests -RepoRoot $TestDrive
117 $result.passed | Should -Be 0
118 }
119 }
120
121 Context 'Tool installed' {
122 BeforeEach {
123 Mock Push-Location {}
124 Mock Pop-Location {}
125 Mock Get-ChildItem { @() }
126 Mock Get-Command { [PSCustomObject]@{ Source = 'pytest' } } -ParameterFilter { $Name -eq 'pytest' }
127 }
128
129 It 'Proceeds when pytest available' {
130 { Invoke-PythonTests -RepoRoot $TestDrive } | Should -Not -Throw
131 }
132 }
133}
134
135#endregion
136
137#region Skill Discovery Tests
138
139Describe 'Python Skill Discovery for Testing' -Tag 'Unit' {
140 Context 'No Python skills found' {
141 BeforeEach {
142 Mock Push-Location {}
143 Mock Pop-Location {}
144 Mock Get-ChildItem { @() }
145 Mock Get-Command { [PSCustomObject]@{ Source = 'pytest' } } -ParameterFilter { $Name -eq 'pytest' }
146 }
147
148 It 'Returns success with zero skills' {
149 $result = Invoke-PythonTests -RepoRoot $TestDrive
150 $result.success | Should -BeTrue
151 $result.skillsTested | Should -Be 0
152 }
153
154 It 'Reports zero passed and failed' {
155 $result = Invoke-PythonTests -RepoRoot $TestDrive
156 $result.passed | Should -Be 0
157 $result.failed | Should -Be 0
158 }
159 }
160
161 Context 'Skill without tests directory' {
162 BeforeEach {
163 $script:NoTestsSkillDir = Join-Path $TestDrive 'no-tests-skill'
164 Mock Push-Location {}
165 Mock Pop-Location {}
166 Mock Get-Command { [PSCustomObject]@{ Source = 'pytest' } } -ParameterFilter { $Name -eq 'pytest' }
167 Mock Get-ChildItem {
168 @([PSCustomObject]@{
169 FullName = (Join-Path $script:NoTestsSkillDir 'pyproject.toml')
170 Directory = [PSCustomObject]@{ FullName = $script:NoTestsSkillDir }
171 })
172 }
173 Mock Test-Path { $false } -ParameterFilter { $Path -like '*tests' }
174 Mock pytest { $global:LASTEXITCODE = 0; '' }
175 }
176
177 It 'Skips skill without tests directory' {
178 $result = Invoke-PythonTests -RepoRoot $TestDrive
179 $result.skillsTested | Should -Be 0
180 }
181
182 It 'Does not call pytest for skill without tests' {
183 Invoke-PythonTests -RepoRoot $TestDrive
184 Should -Invoke -CommandName pytest -Times 0
185 }
186 }
187
188 Context 'Excludes node_modules from discovery' {
189 BeforeEach {
190 Mock Push-Location {}
191 Mock Pop-Location {}
192 Mock Get-Command { [PSCustomObject]@{ Source = 'pytest' } } -ParameterFilter { $Name -eq 'pytest' }
193 Mock Get-ChildItem {
194 @([PSCustomObject]@{
195 FullName = (Join-Path $TestDrive 'node_modules/pkg/pyproject.toml')
196 Directory = [PSCustomObject]@{ FullName = (Join-Path $TestDrive 'node_modules/pkg') }
197 })
198 }
199 }
200
201 It 'Filters out node_modules paths' {
202 $result = Invoke-PythonTests -RepoRoot $TestDrive
203 $result.skillsTested | Should -Be 0
204 }
205 }
206}
207
208#endregion
209
210#region Test Execution Tests
211
212Describe 'Pytest Execution' -Tag 'Unit' {
213 BeforeAll {
214 $script:SkillDir = Join-Path $TestDrive 'test-skill'
215 }
216
217 BeforeEach {
218 Mock Push-Location {}
219 Mock Pop-Location {}
220 Mock Get-Command { [PSCustomObject]@{ Source = 'pytest' } } -ParameterFilter { $Name -eq 'pytest' }
221 Mock Get-ChildItem {
222 @([PSCustomObject]@{
223 FullName = (Join-Path $script:SkillDir 'pyproject.toml')
224 Directory = [PSCustomObject]@{ FullName = $script:SkillDir }
225 })
226 }
227 Mock Test-Path { $true } -ParameterFilter { $Path -like '*tests' }
228 }
229
230 Context 'Tests pass' {
231 BeforeEach {
232 Mock pytest { $global:LASTEXITCODE = 0; '3 passed' }
233 }
234
235 It 'Returns success when pytest passes' {
236 $result = Invoke-PythonTests -RepoRoot $TestDrive
237 $result.success | Should -BeTrue
238 }
239
240 It 'Increments passed counter' {
241 $result = Invoke-PythonTests -RepoRoot $TestDrive
242 $result.passed | Should -Be 1
243 }
244
245 It 'Reports one skill tested' {
246 $result = Invoke-PythonTests -RepoRoot $TestDrive
247 $result.skillsTested | Should -Be 1
248 }
249
250 It 'Reports zero failures' {
251 $result = Invoke-PythonTests -RepoRoot $TestDrive
252 $result.failed | Should -Be 0
253 }
254
255 It 'Marks skill as passed in details' {
256 $result = Invoke-PythonTests -RepoRoot $TestDrive
257 $result.details[0].passed | Should -BeTrue
258 }
259 }
260
261 Context 'Tests fail' {
262 BeforeEach {
263 Mock pytest { $global:LASTEXITCODE = 1; '1 failed, 2 passed' }
264 }
265
266 It 'Returns failure when pytest fails' {
267 $result = Invoke-PythonTests -RepoRoot $TestDrive
268 $result.success | Should -BeFalse
269 }
270
271 It 'Increments failed counter' {
272 $result = Invoke-PythonTests -RepoRoot $TestDrive
273 $result.failed | Should -Be 1
274 }
275
276 It 'Records skill path in errors' {
277 $result = Invoke-PythonTests -RepoRoot $TestDrive
278 $result.errors | Should -Contain $script:SkillDir
279 }
280
281 It 'Marks skill as failed in details' {
282 $result = Invoke-PythonTests -RepoRoot $TestDrive
283 $result.details[0].passed | Should -BeFalse
284 }
285 }
286
287 Context 'Pytest throws exception' {
288 BeforeEach {
289 Mock pytest { throw 'pytest crashed' }
290 }
291
292 It 'Handles pytest exception gracefully' {
293 $result = Invoke-PythonTests -RepoRoot $TestDrive
294 $result.success | Should -BeFalse
295 }
296
297 It 'Increments failed counter on exception' {
298 $result = Invoke-PythonTests -RepoRoot $TestDrive
299 $result.failed | Should -Be 1
300 }
301
302 It 'Records error with skill path' {
303 $result = Invoke-PythonTests -RepoRoot $TestDrive
304 $result.errors | Should -Not -BeNullOrEmpty
305 }
306 }
307
308 Context 'Multiple skills' {
309 BeforeEach {
310 $script:Skill1Dir = Join-Path $TestDrive 'skill-a'
311 $script:Skill2Dir = Join-Path $TestDrive 'skill-b'
312 Mock Get-ChildItem {
313 @(
314 [PSCustomObject]@{
315 FullName = (Join-Path $script:Skill1Dir 'pyproject.toml')
316 Directory = [PSCustomObject]@{ FullName = $script:Skill1Dir }
317 },
318 [PSCustomObject]@{
319 FullName = (Join-Path $script:Skill2Dir 'pyproject.toml')
320 Directory = [PSCustomObject]@{ FullName = $script:Skill2Dir }
321 }
322 )
323 }
324 Mock pytest { $global:LASTEXITCODE = 0; 'passed' }
325 }
326
327 It 'Tests all discovered skills' {
328 $result = Invoke-PythonTests -RepoRoot $TestDrive
329 $result.skillsTested | Should -Be 2
330 }
331
332 It 'Counts all passing skills' {
333 $result = Invoke-PythonTests -RepoRoot $TestDrive
334 $result.passed | Should -Be 2
335 }
336 }
337}
338
339#endregion
340
341#region Output Persistence Tests
342
343Describe 'Output Persistence' -Tag 'Unit' {
344 BeforeAll {
345 $script:OutputSkillDir = Join-Path $TestDrive 'output-skill'
346 }
347
348 BeforeEach {
349 Mock Push-Location {}
350 Mock Pop-Location {}
351 Mock Get-Command { [PSCustomObject]@{ Source = 'pytest' } } -ParameterFilter { $Name -eq 'pytest' }
352 Mock Get-ChildItem {
353 @([PSCustomObject]@{
354 FullName = (Join-Path $script:OutputSkillDir 'pyproject.toml')
355 Directory = [PSCustomObject]@{ FullName = $script:OutputSkillDir }
356 })
357 }
358 Mock Test-Path { $true } -ParameterFilter { $Path -like '*tests' }
359 Mock pytest { $global:LASTEXITCODE = 0; 'passed' }
360 }
361
362 Context 'OutputPath specified' {
363 It 'Writes JSON results to OutputPath' {
364 $outputPath = Join-Path $TestDrive 'test-results.json'
365 Invoke-PythonTests -RepoRoot $TestDrive -OutputPath $outputPath
366 Test-Path $outputPath | Should -BeTrue
367 }
368
369 It 'Produces valid JSON output' {
370 $outputPath = Join-Path $TestDrive 'test-results2.json'
371 Invoke-PythonTests -RepoRoot $TestDrive -OutputPath $outputPath
372 { Get-Content $outputPath -Raw | ConvertFrom-Json } | Should -Not -Throw
373 }
374 }
375
376 Context 'OutputPath not specified' {
377 It 'Does not throw when OutputPath omitted' {
378 { Invoke-PythonTests -RepoRoot $TestDrive } | Should -Not -Throw
379 }
380 }
381}
382
383#endregion
384