microsoft/hve-core

Public

mirrored from https://github.com/microsoft/hve-coreAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
2015-accessibility-planner-playbook

Branches

Tags

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

Clone

HTTPS

Download ZIP

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

464lines · modeblame

0a90f573Jason5 months ago1#Requires -Modules Pester
2# Copyright (c) Microsoft Corporation.
3# SPDX-License-Identifier: MIT
4<#
5.SYNOPSIS
6Pester tests for Invoke-PythonTests.ps1 script
7.DESCRIPTION
8Tests 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
c5fcf0b3Bill Berry2 months ago21# Create stub functions so they can be mocked even when not installed
0a90f573Jason5 months ago22function global:pytest { '' }
c5fcf0b3Bill Berry2 months ago23function global:uv { '' }
0a90f573Jason5 months ago24
25. $script:ScriptPath
26}
27
28AfterAll {
29Remove-Item -Path 'Function:\pytest' -Force -ErrorAction SilentlyContinue
c5fcf0b3Bill Berry2 months ago30Remove-Item -Path 'Function:\uv' -Force -ErrorAction SilentlyContinue
0a90f573Jason5 months ago31}
32
33#region Parameter Validation Tests
34
35Describe 'Invoke-PythonTests Parameter Validation' -Tag 'Unit' {
36Context 'RepoRoot parameter' {
37BeforeEach {
38Mock Get-ChildItem { @() }
39Mock Get-Command { [PSCustomObject]@{ Source = 'pytest' } } -ParameterFilter { $Name -eq 'pytest' }
c5fcf0b3Bill Berry2 months ago40Mock Get-Command { $null } -ParameterFilter { $Name -eq 'uv' }
0a90f573Jason5 months ago41Mock Push-Location {}
42Mock Pop-Location {}
43}
44
45It 'Accepts custom RepoRoot' {
46$repoRoot = Join-Path $TestDrive 'test-repo'
47New-Item -ItemType Directory -Path $repoRoot -Force | Out-Null
48{ Invoke-PythonTests -RepoRoot $repoRoot } | Should -Not -Throw
49}
50}
51
52Context 'OutputPath parameter' {
53BeforeEach {
54Mock Get-ChildItem { @() }
55Mock Get-Command { [PSCustomObject]@{ Source = 'pytest' } } -ParameterFilter { $Name -eq 'pytest' }
c5fcf0b3Bill Berry2 months ago56Mock Get-Command { $null } -ParameterFilter { $Name -eq 'uv' }
0a90f573Jason5 months ago57Mock Push-Location {}
58Mock Pop-Location {}
59}
60
61It 'Accepts custom OutputPath' {
62$outputPath = Join-Path $TestDrive 'test-output.json'
63{ Invoke-PythonTests -RepoRoot $TestDrive -OutputPath $outputPath } | Should -Not -Throw
64}
65}
66
67Context 'Verbosity parameter' {
68BeforeEach {
69Mock Get-ChildItem { @() }
70Mock Get-Command { [PSCustomObject]@{ Source = 'pytest' } } -ParameterFilter { $Name -eq 'pytest' }
c5fcf0b3Bill Berry2 months ago71Mock Get-Command { $null } -ParameterFilter { $Name -eq 'uv' }
0a90f573Jason5 months ago72Mock Push-Location {}
73Mock Pop-Location {}
74}
75
76It 'Defaults to -v verbosity' {
77{ Invoke-PythonTests -RepoRoot $TestDrive } | Should -Not -Throw
78}
79
80It 'Accepts custom verbosity' {
81{ Invoke-PythonTests -RepoRoot $TestDrive -Verbosity '-vv' } | Should -Not -Throw
82}
83}
84}
85
86#endregion
87
88#region Tool Availability Tests
89
90Describe 'pytest Tool Availability' -Tag 'Unit' {
91Context 'Tool not installed' {
92BeforeEach {
93Mock Push-Location {}
94Mock Pop-Location {}
95Mock Get-ChildItem {
96@([PSCustomObject]@{
97FullName = (Join-Path $TestDrive 'skill1/pyproject.toml')
98Directory = [PSCustomObject]@{ FullName = (Join-Path $TestDrive 'skill1') }
99})
100}
101Mock Get-Command { $null } -ParameterFilter { $Name -eq 'pytest' }
c5fcf0b3Bill Berry2 months ago102Mock Get-Command { $null } -ParameterFilter { $Name -eq 'uv' }
3b922278Bill Berry4 months ago103Mock Test-Path { $true } -ParameterFilter { $Path -like '*tests' }
c5fcf0b3Bill Berry2 months ago104Mock Test-Path { $false } -ParameterFilter { $Path -like '*uv.lock' }
0a90f573Jason5 months ago105}
106
3b922278Bill Berry4 months ago107It 'Returns failure when pytest not available' {
0a90f573Jason5 months ago108$result = Invoke-PythonTests -RepoRoot $TestDrive
109$result.success | Should -BeFalse
3b922278Bill Berry4 months ago110}
111
112It 'Reports skill path in errors' {
113$result = Invoke-PythonTests -RepoRoot $TestDrive
114$result.errors | Should -Contain (Join-Path $TestDrive 'skill1')
0a90f573Jason5 months ago115}
116
117It 'Reports zero skills tested when pytest missing' {
118$result = Invoke-PythonTests -RepoRoot $TestDrive
119$result.skillsTested | Should -Be 0
120}
121
3b922278Bill Berry4 months ago122It 'Reports zero passed' {
0a90f573Jason5 months ago123$result = Invoke-PythonTests -RepoRoot $TestDrive
124$result.passed | Should -Be 0
125}
126}
127
128Context 'Tool installed' {
129BeforeEach {
130Mock Push-Location {}
131Mock Pop-Location {}
132Mock Get-ChildItem { @() }
133Mock Get-Command { [PSCustomObject]@{ Source = 'pytest' } } -ParameterFilter { $Name -eq 'pytest' }
c5fcf0b3Bill Berry2 months ago134Mock Get-Command { $null } -ParameterFilter { $Name -eq 'uv' }
0a90f573Jason5 months ago135}
136
137It 'Proceeds when pytest available' {
138{ Invoke-PythonTests -RepoRoot $TestDrive } | Should -Not -Throw
139}
140}
141}
142
143#endregion
144
145#region Skill Discovery Tests
146
147Describe 'Python Skill Discovery for Testing' -Tag 'Unit' {
148Context 'No Python skills found' {
149BeforeEach {
150Mock Push-Location {}
151Mock Pop-Location {}
152Mock Get-ChildItem { @() }
153Mock Get-Command { [PSCustomObject]@{ Source = 'pytest' } } -ParameterFilter { $Name -eq 'pytest' }
c5fcf0b3Bill Berry2 months ago154Mock Get-Command { $null } -ParameterFilter { $Name -eq 'uv' }
0a90f573Jason5 months ago155}
156
157It 'Returns success with zero skills' {
158$result = Invoke-PythonTests -RepoRoot $TestDrive
159$result.success | Should -BeTrue
160$result.skillsTested | Should -Be 0
161}
162
163It 'Reports zero passed and failed' {
164$result = Invoke-PythonTests -RepoRoot $TestDrive
165$result.passed | Should -Be 0
166$result.failed | Should -Be 0
167}
168}
169
170Context 'Skill without tests directory' {
171BeforeEach {
172$script:NoTestsSkillDir = Join-Path $TestDrive 'no-tests-skill'
173Mock Push-Location {}
174Mock Pop-Location {}
175Mock Get-Command { [PSCustomObject]@{ Source = 'pytest' } } -ParameterFilter { $Name -eq 'pytest' }
c5fcf0b3Bill Berry2 months ago176Mock Get-Command { $null } -ParameterFilter { $Name -eq 'uv' }
0a90f573Jason5 months ago177Mock Get-ChildItem {
178@([PSCustomObject]@{
179FullName = (Join-Path $script:NoTestsSkillDir 'pyproject.toml')
180Directory = [PSCustomObject]@{ FullName = $script:NoTestsSkillDir }
181})
182}
183Mock Test-Path { $false } -ParameterFilter { $Path -like '*tests' }
c5fcf0b3Bill Berry2 months ago184Mock Test-Path { $false } -ParameterFilter { $Path -like '*uv.lock' }
0a90f573Jason5 months ago185Mock pytest { $global:LASTEXITCODE = 0; '' }
186}
187
188It 'Skips skill without tests directory' {
189$result = Invoke-PythonTests -RepoRoot $TestDrive
190$result.skillsTested | Should -Be 0
191}
192
193It 'Does not call pytest for skill without tests' {
194Invoke-PythonTests -RepoRoot $TestDrive
195Should -Invoke -CommandName pytest -Times 0
196}
197}
198
199Context 'Excludes node_modules from discovery' {
200BeforeEach {
201Mock Push-Location {}
202Mock Pop-Location {}
203Mock Get-Command { [PSCustomObject]@{ Source = 'pytest' } } -ParameterFilter { $Name -eq 'pytest' }
c5fcf0b3Bill Berry2 months ago204Mock Get-Command { $null } -ParameterFilter { $Name -eq 'uv' }
0a90f573Jason5 months ago205Mock Get-ChildItem {
206@([PSCustomObject]@{
207FullName = (Join-Path $TestDrive 'node_modules/pkg/pyproject.toml')
208Directory = [PSCustomObject]@{ FullName = (Join-Path $TestDrive 'node_modules/pkg') }
209})
210}
211}
212
213It 'Filters out node_modules paths' {
214$result = Invoke-PythonTests -RepoRoot $TestDrive
215$result.skillsTested | Should -Be 0
216}
217}
218}
219
220#endregion
221
222#region Test Execution Tests
223
224Describe 'Pytest Execution' -Tag 'Unit' {
225BeforeAll {
226$script:SkillDir = Join-Path $TestDrive 'test-skill'
227}
228
229BeforeEach {
230Mock Push-Location {}
231Mock Pop-Location {}
232Mock Get-Command { [PSCustomObject]@{ Source = 'pytest' } } -ParameterFilter { $Name -eq 'pytest' }
c5fcf0b3Bill Berry2 months ago233Mock Get-Command { $null } -ParameterFilter { $Name -eq 'uv' }
0a90f573Jason5 months ago234Mock Get-ChildItem {
235@([PSCustomObject]@{
236FullName = (Join-Path $script:SkillDir 'pyproject.toml')
237Directory = [PSCustomObject]@{ FullName = $script:SkillDir }
238})
239}
240Mock Test-Path { $true } -ParameterFilter { $Path -like '*tests' }
c5fcf0b3Bill Berry2 months ago241Mock Test-Path { $false } -ParameterFilter { $Path -like '*uv.lock' }
0a90f573Jason5 months ago242}
243
244Context 'Tests pass' {
245BeforeEach {
246Mock pytest { $global:LASTEXITCODE = 0; '3 passed' }
247}
248
249It 'Returns success when pytest passes' {
250$result = Invoke-PythonTests -RepoRoot $TestDrive
251$result.success | Should -BeTrue
252}
253
254It 'Increments passed counter' {
255$result = Invoke-PythonTests -RepoRoot $TestDrive
256$result.passed | Should -Be 1
257}
258
259It 'Reports one skill tested' {
260$result = Invoke-PythonTests -RepoRoot $TestDrive
261$result.skillsTested | Should -Be 1
262}
263
264It 'Reports zero failures' {
265$result = Invoke-PythonTests -RepoRoot $TestDrive
266$result.failed | Should -Be 0
267}
268
269It 'Marks skill as passed in details' {
270$result = Invoke-PythonTests -RepoRoot $TestDrive
271$result.details[0].passed | Should -BeTrue
272}
273}
274
275Context 'Tests fail' {
276BeforeEach {
277Mock pytest { $global:LASTEXITCODE = 1; '1 failed, 2 passed' }
278}
279
280It 'Returns failure when pytest fails' {
281$result = Invoke-PythonTests -RepoRoot $TestDrive
282$result.success | Should -BeFalse
283}
284
285It 'Increments failed counter' {
286$result = Invoke-PythonTests -RepoRoot $TestDrive
287$result.failed | Should -Be 1
288}
289
290It 'Records skill path in errors' {
291$result = Invoke-PythonTests -RepoRoot $TestDrive
292$result.errors | Should -Contain $script:SkillDir
293}
294
295It 'Marks skill as failed in details' {
296$result = Invoke-PythonTests -RepoRoot $TestDrive
297$result.details[0].passed | Should -BeFalse
298}
299}
300
301Context 'Pytest throws exception' {
302BeforeEach {
303Mock pytest { throw 'pytest crashed' }
304}
305
306It 'Handles pytest exception gracefully' {
307$result = Invoke-PythonTests -RepoRoot $TestDrive
308$result.success | Should -BeFalse
309}
310
311It 'Increments failed counter on exception' {
312$result = Invoke-PythonTests -RepoRoot $TestDrive
313$result.failed | Should -Be 1
314}
315
316It 'Records error with skill path' {
317$result = Invoke-PythonTests -RepoRoot $TestDrive
318$result.errors | Should -Not -BeNullOrEmpty
319}
320}
321
322Context 'Multiple skills' {
323BeforeEach {
324$script:Skill1Dir = Join-Path $TestDrive 'skill-a'
325$script:Skill2Dir = Join-Path $TestDrive 'skill-b'
326Mock Get-ChildItem {
327@(
328[PSCustomObject]@{
329FullName = (Join-Path $script:Skill1Dir 'pyproject.toml')
330Directory = [PSCustomObject]@{ FullName = $script:Skill1Dir }
331},
332[PSCustomObject]@{
333FullName = (Join-Path $script:Skill2Dir 'pyproject.toml')
334Directory = [PSCustomObject]@{ FullName = $script:Skill2Dir }
335}
336)
337}
338Mock pytest { $global:LASTEXITCODE = 0; 'passed' }
339}
340
341It 'Tests all discovered skills' {
342$result = Invoke-PythonTests -RepoRoot $TestDrive
343$result.skillsTested | Should -Be 2
344}
345
346It 'Counts all passing skills' {
347$result = Invoke-PythonTests -RepoRoot $TestDrive
348$result.passed | Should -Be 2
349}
350}
c5fcf0b3Bill Berry2 months ago351
352Context 'Locked uv project' {
353BeforeEach {
354Mock Get-Command { [PSCustomObject]@{ Source = 'uv' } } -ParameterFilter { $Name -eq 'uv' }
355Mock Test-Path { $true } -ParameterFilter { $Path -like '*uv.lock' }
356Mock uv {
357if ($args[0] -eq 'sync') {
358$global:LASTEXITCODE = 0
359return 'synced'
360}
361
362$global:LASTEXITCODE = 0
363return '3 passed'
364}
365}
366
367It 'Uses uv runner for locked project' {
368$result = Invoke-PythonTests -RepoRoot $TestDrive
369$result.details[0].runner | Should -Be 'uv'
370}
371
372It 'Returns success when uv pytest passes' {
373$result = Invoke-PythonTests -RepoRoot $TestDrive
374$result.success | Should -BeTrue
375$result.passed | Should -Be 1
376}
377
378It 'Runs uv sync before pytest' {
379Invoke-PythonTests -RepoRoot $TestDrive
380Should -Invoke -CommandName uv -ParameterFilter { $args[0] -eq 'sync' -and $args[1] -eq '--locked' -and $args[2] -eq '--dev' } -Times 1
381}
382
383It 'Runs pytest through uv' {
384Invoke-PythonTests -RepoRoot $TestDrive
385Should -Invoke -CommandName uv -ParameterFilter { $args[0] -eq 'run' -and $args[1] -eq 'pytest' -and $args[2] -eq 'tests/' } -Times 1
386}
387}
388
389Context 'Locked uv project sync failure' {
390BeforeEach {
391Mock Get-Command { [PSCustomObject]@{ Source = 'uv' } } -ParameterFilter { $Name -eq 'uv' }
392Mock Test-Path { $true } -ParameterFilter { $Path -like '*uv.lock' }
393Mock uv {
394$global:LASTEXITCODE = 1
395'sync failed'
396}
397}
398
399It 'Returns failure when uv sync fails' {
400$result = Invoke-PythonTests -RepoRoot $TestDrive
401$result.success | Should -BeFalse
402}
403
404It 'Records sync phase failure details' {
405$result = Invoke-PythonTests -RepoRoot $TestDrive
406$result.details[0].runner | Should -Be 'uv'
407$result.details[0].phase | Should -Be 'sync'
408$result.details[0].passed | Should -BeFalse
409}
410
411It 'Does not run pytest after failed sync' {
412Invoke-PythonTests -RepoRoot $TestDrive
413Should -Invoke -CommandName uv -ParameterFilter { $args[0] -eq 'run' } -Times 0
414}
415}
0a90f573Jason5 months ago416}
417
418#endregion
419
420#region Output Persistence Tests
421
422Describe 'Output Persistence' -Tag 'Unit' {
423BeforeAll {
424$script:OutputSkillDir = Join-Path $TestDrive 'output-skill'
425}
426
427BeforeEach {
428Mock Push-Location {}
429Mock Pop-Location {}
430Mock Get-Command { [PSCustomObject]@{ Source = 'pytest' } } -ParameterFilter { $Name -eq 'pytest' }
c5fcf0b3Bill Berry2 months ago431Mock Get-Command { $null } -ParameterFilter { $Name -eq 'uv' }
0a90f573Jason5 months ago432Mock Get-ChildItem {
433@([PSCustomObject]@{
434FullName = (Join-Path $script:OutputSkillDir 'pyproject.toml')
435Directory = [PSCustomObject]@{ FullName = $script:OutputSkillDir }
436})
437}
438Mock Test-Path { $true } -ParameterFilter { $Path -like '*tests' }
c5fcf0b3Bill Berry2 months ago439Mock Test-Path { $false } -ParameterFilter { $Path -like '*uv.lock' }
0a90f573Jason5 months ago440Mock pytest { $global:LASTEXITCODE = 0; 'passed' }
441}
442
443Context 'OutputPath specified' {
444It 'Writes JSON results to OutputPath' {
445$outputPath = Join-Path $TestDrive 'test-results.json'
446Invoke-PythonTests -RepoRoot $TestDrive -OutputPath $outputPath
447Test-Path $outputPath | Should -BeTrue
448}
449
450It 'Produces valid JSON output' {
451$outputPath = Join-Path $TestDrive 'test-results2.json'
452Invoke-PythonTests -RepoRoot $TestDrive -OutputPath $outputPath
453{ Get-Content $outputPath -Raw | ConvertFrom-Json } | Should -Not -Throw
454}
455}
456
457Context 'OutputPath not specified' {
458It 'Does not throw when OutputPath omitted' {
459{ Invoke-PythonTests -RepoRoot $TestDrive } | Should -Not -Throw
460}
461}
462}
463
464#endregion