Skip to content

Commit 408e6b7

Browse files
fix(skills): fix powershell test coverage in pr-reference skill (#699)
Extracted the duplicated `Get-RepositoryRoot` function from the three PR-reference skill scripts into a shared PowerShell module (`shared.psm1`). The duplicate definitions interfered with Pester's coverage instrumentation when tests ran together, causing `list-changed-files.ps1` and `read-diff.ps1` to report 0% coverage despite all tests passing. The consolidated module supports both a lenient fallback mode and a `-Strict` switch that throws on failure, preserving the distinct behaviors each caller required. - fix(skills): created `shared.psm1` with unified `Get-RepositoryRoot` supporting `-Strict` switch and `Export-ModuleMember` - fix(skills): removed inline `Get-RepositoryRoot` from `generate.ps1`, `list-changed-files.ps1`, and `read-diff.ps1`; replaced with `Import-Module` of the shared module - fix(skills): updated `generate.ps1` to call `Get-RepositoryRoot -Strict` preserving its original throw-on-failure behavior - test(skills): consolidated `Get-RepositoryRoot` tests into `shared.Tests.ps1` with `-ModuleName shared` scoped mocks for both default and strict modes - test(skills): removed duplicate `Get-RepositoryRoot` describe blocks from `generate.Tests.ps1`, `list-changed-files.Tests.ps1`, and `read-diff.Tests.ps1` ## Related Issue(s) Fixes #694 ## Type of Change Select all that apply: **Code & Documentation:** - [x] Bug fix (non-breaking change fixing an issue) - [ ] New feature (non-breaking change adding functionality) - [ ] Breaking change (fix or feature causing existing functionality to change) - [ ] Documentation update **Infrastructure & Configuration:** - [ ] GitHub Actions workflow - [ ] Linting configuration (markdown, PowerShell, etc.) - [ ] Security configuration - [ ] DevContainer configuration - [ ] Dependency update **AI Artifacts:** - [ ] Reviewed contribution with `prompt-builder` agent and addressed all feedback - [ ] Copilot instructions (`.github/instructions/*.instructions.md`) - [ ] Copilot prompt (`.github/prompts/*.prompt.md`) - [ ] Copilot agent (`.github/agents/*.agent.md`) - [ ] Copilot skill (`.github/skills/*/SKILL.md`) > **Note for AI Artifact Contributors**: > > - **Agents**: Research, indexing/referencing other project (using standard VS Code GitHub Copilot/MCP tools), planning, and general implementation agents likely already exist. Review `.github/agents/` before creating new ones. > - **Skills**: Must include both bash and PowerShell scripts. See [Skills](../docs/contributing/skills.md). > - **Model Versions**: Only contributions targeting the **latest Anthropic and OpenAI models** will be accepted. Older model versions (e.g., GPT-3.5, Claude 3) will be rejected. > - See [Agents Not Accepted](../docs/contributing/custom-agents.md#agents-not-accepted) and [Model Version Requirements](../docs/contributing/ai-artifacts-common.md#model-version-requirements). **Other:** - [x] Script/automation (`.ps1`, `.sh`, `.py`) - [ ] Other (please describe): ## Sample Prompts (for AI Artifact Contributions) <!-- Delete this section if not applicable --> N/A — no AI artifact changes in this PR. ## Testing - Ran `npm run test:ps -- -TestPath ".github/skills/shared/pr-reference/tests/"` and confirmed all tests pass - Verified `shared.Tests.ps1` covers both default (fallback) and strict modes with module-scoped mocks - Confirmed duplicate `Get-RepositoryRoot` tests removed from `generate.Tests.ps1`, `list-changed-files.Tests.ps1`, and `read-diff.Tests.ps1` ## Checklist ### Required Checks - [ ] Documentation is updated (if applicable) - [x] Files follow existing naming conventions - [x] Changes are backwards compatible (if applicable) - [x] Tests added for new functionality (if applicable) ### AI Artifact Contributions <!-- If contributing an agent, prompt, instruction, or skill, complete these checks --> - [ ] Used `/prompt-analyze` to review contribution - [ ] Addressed all feedback from `prompt-builder` review - [ ] Verified contribution follows common standards and type-specific requirements ### Required Automated Checks The following validation commands must pass before merging: - [ ] Markdown linting: `npm run lint:md` - [ ] Spell checking: `npm run spell-check` - [ ] Frontmatter validation: `npm run lint:frontmatter` - [x] Skill structure validation: `npm run validate:skills` - [ ] Link validation: `npm run lint:md-links` - [x] PowerShell analysis: `npm run lint:ps` ## Security Considerations <!-- ⚠️ WARNING: Do not commit sensitive information such as API keys, passwords, or personal data --> - [x] This PR does not contain any sensitive or NDA information - [ ] Any new dependencies have been reviewed for security issues - [x] Security-related scripts follow the principle of least privilege ## Additional Notes The shared module uses `Export-ModuleMember -Function Get-RepositoryRoot` to expose only the intended public surface. The `-Strict` switch preserves the original `generate.ps1` throw behavior while `list-changed-files.ps1` and `read-diff.ps1` use the default `$PWD` fallback. 🧪 - Generated by Copilot Co-authored-by: Bill Berry <WilliamBerryiii@users.noreply.github.com>
1 parent 7b1c8e8 commit 408e6b7

8 files changed

Lines changed: 89 additions & 99 deletions

File tree

.github/skills/shared/pr-reference/scripts/generate.ps1

Lines changed: 3 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ param(
3737

3838
$ErrorActionPreference = 'Stop'
3939

40+
Import-Module (Join-Path $PSScriptRoot 'shared.psm1') -Force
41+
4042
function Test-GitAvailability {
4143
<#
4244
.SYNOPSIS
@@ -52,26 +54,6 @@ Throws a terminating error when git can't be resolved from PATH.
5254
}
5355
}
5456

55-
function Get-RepositoryRoot {
56-
<#
57-
.SYNOPSIS
58-
Gets the repository root path.
59-
.DESCRIPTION
60-
Runs git rev-parse --show-toplevel and throws when the command fails.
61-
.OUTPUTS
62-
System.String
63-
#>
64-
[OutputType([string])]
65-
param()
66-
67-
$repoRoot = (& git rev-parse --show-toplevel).Trim()
68-
if (-not $repoRoot) {
69-
throw "Unable to determine repository root."
70-
}
71-
72-
return $repoRoot
73-
}
74-
7557
function New-PrDirectory {
7658
<#
7759
.SYNOPSIS
@@ -454,7 +436,7 @@ System.IO.FileInfo
454436

455437
Test-GitAvailability
456438

457-
$repoRoot = Get-RepositoryRoot
439+
$repoRoot = Get-RepositoryRoot -Strict
458440

459441
if ($OutputPath) {
460442
$prReferencePath = $OutputPath

.github/skills/shared/pr-reference/scripts/list-changed-files.ps1

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -47,16 +47,7 @@ param(
4747

4848
$ErrorActionPreference = 'Stop'
4949

50-
function Get-RepositoryRoot {
51-
[OutputType([string])]
52-
param()
53-
54-
$root = & git rev-parse --show-toplevel 2>$null
55-
if ($LASTEXITCODE -eq 0 -and $root) {
56-
return $root.Trim()
57-
}
58-
return $PWD.Path
59-
}
50+
Import-Module (Join-Path $PSScriptRoot 'shared.psm1') -Force
6051

6152
function Get-FileChanges {
6253
[OutputType([PSCustomObject[]])]

.github/skills/shared/pr-reference/scripts/read-diff.ps1

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -79,16 +79,7 @@ param(
7979

8080
$ErrorActionPreference = 'Stop'
8181

82-
function Get-RepositoryRoot {
83-
[OutputType([string])]
84-
param()
85-
86-
$root = & git rev-parse --show-toplevel 2>$null
87-
if ($LASTEXITCODE -eq 0 -and $root) {
88-
return $root.Trim()
89-
}
90-
return $PWD.Path
91-
}
82+
Import-Module (Join-Path $PSScriptRoot 'shared.psm1') -Force
9283

9384
function Get-ChunkInfo {
9485
[OutputType([PSCustomObject])]
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Copyright (c) Microsoft Corporation.
2+
# SPDX-License-Identifier: MIT
3+
4+
function Get-RepositoryRoot {
5+
<#
6+
.SYNOPSIS
7+
Gets the repository root path.
8+
.DESCRIPTION
9+
Runs git rev-parse --show-toplevel to locate the repository root.
10+
In default mode, falls back to the current directory when git fails.
11+
With -Strict, throws a terminating error instead.
12+
.PARAMETER Strict
13+
When set, throws instead of falling back to the current directory.
14+
.OUTPUTS
15+
System.String
16+
#>
17+
[OutputType([string])]
18+
param(
19+
[switch]$Strict
20+
)
21+
22+
if ($Strict) {
23+
$repoRoot = (& git rev-parse --show-toplevel).Trim()
24+
if (-not $repoRoot) {
25+
throw "Unable to determine repository root."
26+
}
27+
return $repoRoot
28+
}
29+
30+
$root = & git rev-parse --show-toplevel 2>$null
31+
if ($LASTEXITCODE -eq 0 -and $root) {
32+
return $root.Trim()
33+
}
34+
return $PWD.Path
35+
}
36+
37+
Export-ModuleMember -Function Get-RepositoryRoot

.github/skills/shared/pr-reference/tests/generate.Tests.ps1

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -18,24 +18,6 @@ Describe 'Test-GitAvailability' {
1818
}
1919
}
2020

21-
Describe 'Get-RepositoryRoot' {
22-
It 'Returns a valid directory path' {
23-
$result = Get-RepositoryRoot
24-
$result | Should -Not -BeNullOrEmpty
25-
Test-Path -Path $result -PathType Container | Should -BeTrue
26-
}
27-
28-
It 'Returns path containing .git directory' {
29-
$result = Get-RepositoryRoot
30-
Test-Path -Path (Join-Path $result '.git') | Should -BeTrue
31-
}
32-
33-
It 'Should throw when repository root cannot be determined' {
34-
Mock git { $global:LASTEXITCODE = 0; return '' }
35-
{ Get-RepositoryRoot } | Should -Throw '*Unable to determine repository root*'
36-
}
37-
}
38-
3921
Describe 'New-PrDirectory' {
4022
BeforeAll {
4123
$script:tempRepo = Join-Path ([System.IO.Path]::GetTempPath()) ([System.Guid]::NewGuid().ToString())

.github/skills/shared/pr-reference/tests/list-changed-files.Tests.ps1

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -74,26 +74,6 @@ AfterAll {
7474
Remove-Item -Path $script:TempDir -Recurse -Force -ErrorAction SilentlyContinue
7575
}
7676

77-
Describe 'Get-RepositoryRoot' {
78-
It 'Returns a valid directory when in a git repository' {
79-
$result = Get-RepositoryRoot
80-
$result | Should -Not -BeNullOrEmpty
81-
Test-Path -Path $result -PathType Container | Should -BeTrue
82-
}
83-
84-
It 'Falls back to current directory when git fails' {
85-
Mock git { $global:LASTEXITCODE = 128; return $null }
86-
$result = Get-RepositoryRoot
87-
$result | Should -Be $PWD.Path
88-
}
89-
90-
It 'Falls back to current directory when git returns empty' {
91-
Mock git { $global:LASTEXITCODE = 0; return '' }
92-
$result = Get-RepositoryRoot
93-
$result | Should -Be $PWD.Path
94-
}
95-
}
96-
9777
Describe 'Get-FileChanges' {
9878
Context 'All change types' {
9979
It 'Returns all four changed files when filtering by All' {

.github/skills/shared/pr-reference/tests/read-diff.Tests.ps1

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -65,26 +65,6 @@ AfterAll {
6565
Remove-Item -Path $script:TempDir -Recurse -Force -ErrorAction SilentlyContinue
6666
}
6767

68-
Describe 'Get-RepositoryRoot' {
69-
It 'Returns a valid directory when in a git repository' {
70-
$result = Get-RepositoryRoot
71-
$result | Should -Not -BeNullOrEmpty
72-
Test-Path -Path $result -PathType Container | Should -BeTrue
73-
}
74-
75-
It 'Falls back to current directory when git fails' {
76-
Mock git { $global:LASTEXITCODE = 128; return $null }
77-
$result = Get-RepositoryRoot
78-
$result | Should -Be $PWD.Path
79-
}
80-
81-
It 'Falls back to current directory when git returns empty' {
82-
Mock git { $global:LASTEXITCODE = 0; return '' }
83-
$result = Get-RepositoryRoot
84-
$result | Should -Be $PWD.Path
85-
}
86-
}
87-
8868
Describe 'Get-ChunkInfo' {
8969
It 'Calculates total chunks when lines divide evenly' {
9070
$result = Get-ChunkInfo -TotalLines 1000 -ChunkSize 500
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
#Requires -Modules Pester
2+
# Copyright (c) Microsoft Corporation.
3+
# SPDX-License-Identifier: MIT
4+
5+
BeforeAll {
6+
Import-Module (Join-Path $PSScriptRoot '../scripts/shared.psm1') -Force
7+
}
8+
9+
Describe 'Get-RepositoryRoot' {
10+
Context 'Default (fallback) mode' {
11+
It 'Returns a valid directory when in a git repository' {
12+
$result = Get-RepositoryRoot
13+
$result | Should -Not -BeNullOrEmpty
14+
Test-Path -Path $result -PathType Container | Should -BeTrue
15+
}
16+
17+
It 'Returns path containing .git directory' {
18+
$result = Get-RepositoryRoot
19+
Test-Path -Path (Join-Path $result '.git') | Should -BeTrue
20+
}
21+
22+
It 'Falls back to current directory when git fails' {
23+
Mock git { $global:LASTEXITCODE = 128; return $null } -ModuleName shared
24+
$result = Get-RepositoryRoot
25+
$result | Should -Be $PWD.Path
26+
}
27+
28+
It 'Falls back to current directory when git returns empty' {
29+
Mock git { $global:LASTEXITCODE = 0; return '' } -ModuleName shared
30+
$result = Get-RepositoryRoot
31+
$result | Should -Be $PWD.Path
32+
}
33+
}
34+
35+
Context 'Strict mode' {
36+
It 'Returns a valid directory when in a git repository' {
37+
$result = Get-RepositoryRoot -Strict
38+
$result | Should -Not -BeNullOrEmpty
39+
Test-Path -Path $result -PathType Container | Should -BeTrue
40+
}
41+
42+
It 'Throws when repository root cannot be determined' {
43+
Mock git { $global:LASTEXITCODE = 0; return '' } -ModuleName shared
44+
{ Get-RepositoryRoot -Strict } | Should -Throw '*Unable to determine repository root*'
45+
}
46+
}
47+
}

0 commit comments

Comments
 (0)