Skip to content

Commit 26c5bf8

Browse files
committed
Add tests and test hooks
1 parent 94a1563 commit 26c5bf8

File tree

3 files changed

+193
-1
lines changed

3 files changed

+193
-1
lines changed

src/code/InternalHooks.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,28 @@ public class InternalHooks
1717

1818
internal static string MARPrefix;
1919

20+
// PSContentPath testing hooks
21+
internal static string LastUserContentPathSource;
22+
internal static string LastUserContentPath;
23+
2024
public static void SetTestHook(string property, object value)
2125
{
2226
var fieldInfo = typeof(InternalHooks).GetField(property, BindingFlags.Static | BindingFlags.NonPublic);
2327
fieldInfo?.SetValue(null, value);
2428
}
2529

30+
public static object GetTestHook(string property)
31+
{
32+
var fieldInfo = typeof(InternalHooks).GetField(property, BindingFlags.Static | BindingFlags.NonPublic);
33+
return fieldInfo?.GetValue(null);
34+
}
35+
36+
public static void ClearPSContentPathHooks()
37+
{
38+
LastUserContentPathSource = null;
39+
LastUserContentPath = null;
40+
}
41+
2642
public static string GetUserString()
2743
{
2844
return Microsoft.PowerShell.PSResourceGet.Cmdlets.UserAgentInfo.UserAgentString();

src/code/Utils.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1182,6 +1182,8 @@ private static string GetUserContentPath(PSCmdlet psCmdlet, Version psVersion, s
11821182
if (!string.IsNullOrEmpty(userContentPath))
11831183
{
11841184
psCmdlet.WriteVerbose($"User content path from Get-PSContentPath: {userContentPath}");
1185+
InternalHooks.LastUserContentPathSource = "Get-PSContentPath";
1186+
InternalHooks.LastUserContentPath = userContentPath;
11851187
return userContentPath;
11861188
}
11871189
}
@@ -1198,6 +1200,8 @@ private static string GetUserContentPath(PSCmdlet psCmdlet, Version psVersion, s
11981200

11991201
// Fallback to legacy location
12001202
psCmdlet.WriteVerbose($"Using legacy location: {legacyPath}");
1203+
InternalHooks.LastUserContentPathSource = "Legacy";
1204+
InternalHooks.LastUserContentPath = legacyPath;
12011205
return legacyPath;
12021206
}
12031207

@@ -1215,6 +1219,7 @@ private static void GetStandardPlatformPaths(
12151219
}
12161220
catch {
12171221
// Fallback if dynamic access fails
1222+
psCmdlet.WriteWarning("Unable to determine PowerShell version from $PSVersionTable");
12181223
}
12191224

12201225
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
@@ -1253,7 +1258,7 @@ private static void GetStandardPlatformPaths(
12531258
Directory.CreateDirectory(localUserDir);
12541259
}
12551260

1256-
allUsersDir = Path.Combine("/usr", "local", "share", "powershell");
1261+
allUsersDir = Path.Combine("/", "usr", "local", "share", "powershell");
12571262
}
12581263
}
12591264

test/PSContentPath.Tests.ps1

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
# Copyright (c) Microsoft Corporation.
2+
# Licensed under the MIT License.
3+
4+
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
5+
Param()
6+
7+
$ProgressPreference = "SilentlyContinue"
8+
$modPath = "$psscriptroot/PSGetTestUtils.psm1"
9+
Import-Module $modPath -Force
10+
11+
Describe 'PSUserContentPath/PSContentPath - End-to-End Install Location' -Tags 'CI' {
12+
BeforeAll {
13+
$script:originalPSModulePath = $env:PSModulePath
14+
$script:actualConfigPath = Join-Path $env:LOCALAPPDATA "PowerShell\powershell.config.json"
15+
$script:configBackup = $null
16+
17+
# Detect if Get-PSContentPath cmdlet is available (requires PSContentPath experimental feature)
18+
$script:getPSContentPathAvailable = $false
19+
$script:isPSContentPathEnabled = $false
20+
$script:sessionContentPath = $null
21+
try {
22+
# Check if Get-PSContentPath cmdlet exists
23+
$null = Get-Command Get-PSContentPath -ErrorAction Stop
24+
$script:getPSContentPathAvailable = $true
25+
26+
# Get the actual session path
27+
$script:sessionContentPath = Get-PSContentPath
28+
$documentsPath = [Environment]::GetFolderPath('MyDocuments')
29+
$documentsPS = Join-Path $documentsPath "PowerShell"
30+
31+
# If Get-PSContentPath returns something other than Documents, the feature is enabled
32+
$script:isPSContentPathEnabled = $script:sessionContentPath -ne $documentsPS
33+
} catch {
34+
# Get-PSContentPath not available (feature disabled)
35+
}
36+
37+
# Backup existing config if it exists
38+
if (Test-Path $script:actualConfigPath) {
39+
$script:configBackup = Get-Content $script:actualConfigPath -Raw
40+
}
41+
42+
$localRepo = "psgettestlocal"
43+
$testModuleName = "PSContentPathTestModule"
44+
Get-NewPSResourceRepositoryFile
45+
Register-LocalRepos
46+
47+
# Create a test module
48+
New-TestModule -moduleName $testModuleName -repoName $localRepo -packageVersion "1.0.0" -prereleaseLabel "" -tags @()
49+
}
50+
51+
AfterEach {
52+
# Restore PSModulePath
53+
$env:PSModulePath = $script:originalPSModulePath
54+
# Clean up installed test modules
55+
Uninstall-PSResource $testModuleName -Version "*" -SkipDependencyCheck -ErrorAction SilentlyContinue
56+
# Clear testing hooks
57+
[Microsoft.PowerShell.PSResourceGet.UtilClasses.InternalHooks]::ClearPSContentPathHooks()
58+
}
59+
60+
AfterAll {
61+
# Restore original config
62+
if ($null -ne $script:configBackup) {
63+
Set-Content -Path $script:actualConfigPath -Value $script:configBackup -Force
64+
}
65+
Get-RevertPSResourceRepositoryFile
66+
}
67+
68+
Context "PSResourceGet behavior on PS 7.7+" {
69+
It "Should use Get-PSContentPath when available, Legacy when not" {
70+
Install-PSResource -Name $testModuleName -Repository $localRepo -Scope CurrentUser -TrustRepository
71+
72+
$pathSource = [Microsoft.PowerShell.PSResourceGet.UtilClasses.InternalHooks]::GetTestHook("LastUserContentPathSource")
73+
$pathUsed = [Microsoft.PowerShell.PSResourceGet.UtilClasses.InternalHooks]::GetTestHook("LastUserContentPath")
74+
75+
if ($script:getPSContentPathAvailable) {
76+
# When Get-PSContentPath cmdlet exists, PSResourceGet should use it
77+
$pathSource | Should -Be "Get-PSContentPath"
78+
$pathUsed | Should -Be $script:sessionContentPath
79+
} else {
80+
# When Get-PSContentPath cmdlet doesn't exist, PSResourceGet should use legacy path
81+
$pathSource | Should -Be "Legacy"
82+
$documentsPath = [Environment]::GetFolderPath('MyDocuments')
83+
$pathUsed | Should -BeLike "*$documentsPath*PowerShell"
84+
}
85+
86+
# Module should be installed
87+
$res = Get-InstalledPSResource -Name $testModuleName
88+
$res.Name | Should -Be $testModuleName
89+
}
90+
}
91+
92+
Context "When PSContentPath feature is enabled in session (PS >= 7.7)" {
93+
It "Should install to custom LocalAppData path (not Documents)" {
94+
if (-not $script:getPSContentPathAvailable -or -not $script:isPSContentPathEnabled) {
95+
Set-ItResult -Skipped -Because "PSContentPath feature not enabled in this session"
96+
return
97+
}
98+
99+
Install-PSResource -Name $testModuleName -Repository $localRepo -Scope CurrentUser -TrustRepository
100+
101+
$pathSource = [Microsoft.PowerShell.PSResourceGet.UtilClasses.InternalHooks]::GetTestHook("LastUserContentPathSource")
102+
$pathUsed = [Microsoft.PowerShell.PSResourceGet.UtilClasses.InternalHooks]::GetTestHook("LastUserContentPath")
103+
104+
# PSResourceGet should call Get-PSContentPath
105+
$pathSource | Should -Be "Get-PSContentPath"
106+
107+
# Path should NOT be Documents (feature enabled means custom path)
108+
$documentsPath = [Environment]::GetFolderPath('MyDocuments')
109+
$pathUsed | Should -Not -BeLike "*$documentsPath*"
110+
111+
# Module should be installed in custom path
112+
$res = Get-InstalledPSResource -Name $testModuleName
113+
$res.Name | Should -Be $testModuleName
114+
$res.InstalledLocation | Should -Not -BeLike "*$documentsPath*"
115+
}
116+
}
117+
118+
Context "PSResourceGet correctly delegates path resolution (PS >= 7.7)" {
119+
It "Should always defer to Get-PSContentPath when cmdlet is available" {
120+
if (-not $script:getPSContentPathAvailable) {
121+
Set-ItResult -Skipped -Because "Get-PSContentPath cmdlet not available"
122+
return
123+
}
124+
125+
$beforePath = Get-PSContentPath
126+
127+
Install-PSResource -Name $testModuleName -Repository $localRepo -Scope CurrentUser -TrustRepository
128+
129+
$pathSource = [Microsoft.PowerShell.PSResourceGet.UtilClasses.InternalHooks]::GetTestHook("LastUserContentPathSource")
130+
$pathUsed = [Microsoft.PowerShell.PSResourceGet.UtilClasses.InternalHooks]::GetTestHook("LastUserContentPath")
131+
132+
# PSResourceGet should call Get-PSContentPath
133+
$pathSource | Should -Be "Get-PSContentPath"
134+
135+
# Path should match what Get-PSContentPath returns
136+
$pathUsed | Should -Be $beforePath
137+
138+
# Module should be installed
139+
$res = Get-InstalledPSResource -Name $testModuleName
140+
$res.Name | Should -Be $testModuleName
141+
}
142+
}
143+
144+
Context "AllUsers scope should not be affected by PSContentPath/PSUserContentPath" {
145+
BeforeAll {
146+
if (!$IsWindows -or !(Test-IsAdmin)) { return }
147+
}
148+
It "Should install to Program Files (AllUsers not affected by PSContentPath)" {
149+
if (!$IsWindows -or !(Test-IsAdmin)) {
150+
Set-ItResult -Skipped -Because "Test requires Windows and Administrator privileges"
151+
return
152+
}
153+
Install-PSResource -Name $testModuleName -Repository $localRepo -Scope AllUsers -TrustRepository
154+
$programFilesPath = [Environment]::GetFolderPath('ProgramFiles')
155+
$expectedPath = Join-Path $programFilesPath "PowerShell\Modules\$testModuleName"
156+
Test-Path $expectedPath | Should -BeTrue
157+
$res = Get-InstalledPSResource -Name $testModuleName
158+
$res.Name | Should -Be $testModuleName
159+
$res.InstalledLocation | Should -BeLike "*Program Files*PowerShell*Modules*"
160+
}
161+
}
162+
}
163+
164+
function Test-IsAdmin {
165+
if ($IsWindows) {
166+
$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
167+
$principal = New-Object Security.Principal.WindowsPrincipal($identity)
168+
return $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
169+
}
170+
return $false
171+
}

0 commit comments

Comments
 (0)