Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 5b058fc

Browse files
committed
feat: implement safe system path testing standards and enhance Remove-StaleFiles function
1 parent 0d60741 commit 5b058fc

File tree

3 files changed

+163
-113
lines changed

3 files changed

+163
-113
lines changed

CLAUDE.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,37 @@ When writing PowerShell code in this module, follow these standards:
8989
- **Ensure correct spelling and grammar in comments**
9090
- **Use en-GB spelling:** "colour" not "color", "realise" not "realize", "centre" not "center"
9191

92+
## Testing Standards
93+
94+
### Safe System Path Testing
95+
When writing tests for functions that interact with system paths (like Remove-StaleFiles):
96+
97+
- **NEVER use real system paths in tests:** Avoid testing with actual system directories like `'C:\'`, `'/'`, `'/etc'`, `'C:\Windows'`, etc.
98+
- **Always use mocks for system path detection:** Use `InModuleScope` with `Mock` to simulate system path behaviour safely
99+
- **Use TestDrive for file operations:** Create test files and directories within Pester's `$TestDrive` for safe, isolated testing
100+
- **Mock internal functions when needed:** For functions like `Test-SystemPath`, extract them as standalone functions if they need to be mocked
101+
102+
#### Example Safe System Path Testing Pattern:
103+
```powershell
104+
It 'Should prevent execution on system paths' {
105+
# Mock the system path detection function
106+
InModuleScope PSF {
107+
Mock Test-SystemPath { return $true }
108+
}
109+
110+
# Test with safe mock directory - the mock ensures any path is treated as a system path
111+
{ Remove-StaleFiles -Path $MockSystemPath -Age 10 -ErrorAction Stop } |
112+
Should -Throw -ExpectedMessage '*system directory*'
113+
}
114+
```
115+
116+
#### Never Do This:
117+
```powershell
118+
# DANGEROUS - Never test with real system paths
119+
{ Remove-StaleFiles -Path 'C:\' -Age 10 } | Should -Throw
120+
{ Remove-StaleFiles -Path '/' -Age 10 } | Should -Throw
121+
```
122+
92123
## Key Constraints
93124

94125
- No external dependencies beyond standard PowerShell modules

functions/Remove-StaleFiles.ps1

Lines changed: 101 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,91 @@
1+
function Test-SystemPath {
2+
<#
3+
.SYNOPSIS
4+
Tests if a given path is a system directory that should be protected from bulk deletion.
5+
.DESCRIPTION
6+
This function checks if the specified path matches known system directories that should not be processed
7+
by Remove-StaleFiles for safety reasons. It checks against Windows system paths (drive roots, Program Files,
8+
Windows directory) and Unix/Linux system paths (/, /etc, /bin, etc.).
9+
.PARAMETER TestPath
10+
The path to test for system directory status.
11+
.OUTPUTS
12+
[bool] Returns $true if the path is a system directory, $false otherwise.
13+
#>
14+
param(
15+
[string] $TestPath
16+
)
17+
18+
# Resolve to absolute path.
19+
$FullPath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($TestPath)
20+
21+
# Get blocked paths using system variables where available.
22+
$BlockedPaths = @()
23+
24+
# Windows system paths
25+
if ($IsWindows -or $env:OS -eq 'Windows_NT') {
26+
# Drive roots (C:\, D:\, etc.).
27+
$BlockedPaths += Get-PSDrive -PSProvider FileSystem | ForEach-Object { $_.Root }
28+
29+
# System directories using environment variables.
30+
if ($env:SystemRoot) { $BlockedPaths += $env:SystemRoot }
31+
if ($env:ProgramFiles) { $BlockedPaths += $env:ProgramFiles }
32+
if ($env:ProgramData) { $BlockedPaths += $env:ProgramData }
33+
if ($env:ProgramW6432) { $BlockedPaths += $env:ProgramW6432 }
34+
if (${env:ProgramFiles(x86)}) { $BlockedPaths += ${env:ProgramFiles(x86)} }
35+
36+
# .NET system directory.
37+
try {
38+
$BlockedPaths += [Environment]::SystemDirectory
39+
} catch { }
40+
}
41+
42+
# Unix/Linux system paths.
43+
if ($IsLinux -or $IsMacOS -or (!$IsWindows -and $env:OS -ne 'Windows_NT')) {
44+
$BlockedPaths += @(
45+
'/',
46+
'/etc',
47+
'/bin',
48+
'/sbin',
49+
'/usr',
50+
'/boot',
51+
'/sys',
52+
'/proc'
53+
)
54+
}
55+
56+
# macOS additional paths.
57+
if ($IsMacOS) {
58+
$BlockedPaths += @(
59+
'/System',
60+
'/Applications'
61+
)
62+
}
63+
64+
# Check if the test path matches or is a parent of any blocked path.
65+
foreach ($BlockedPath in $BlockedPaths) {
66+
if (-not $BlockedPath) { continue }
67+
68+
try {
69+
$ResolvedBlockedPath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($BlockedPath)
70+
71+
# Normalise paths for comparison (handle trailing separators).
72+
$NormalisedTestPath = $FullPath.TrimEnd([System.IO.Path]::DirectorySeparatorChar, [System.IO.Path]::AltDirectorySeparatorChar)
73+
$NormalisedBlockedPath = $ResolvedBlockedPath.TrimEnd([System.IO.Path]::DirectorySeparatorChar, [System.IO.Path]::AltDirectorySeparatorChar)
74+
75+
# Check for exact match.
76+
if ($NormalisedTestPath -eq $NormalisedBlockedPath) {
77+
return $true
78+
}
79+
}
80+
catch {
81+
# If path resolution fails, skip this check.
82+
continue
83+
}
84+
}
85+
86+
return $false
87+
}
88+
189
function Remove-StaleFiles {
290
<#
391
.SYNOPSIS
@@ -58,102 +146,27 @@ function Remove-StaleFiles {
58146
$StartTime = Get-Date
59147

60148
# Set up logging - use appropriate temp directory for the platform.
61-
$TempDir = [System.IO.Path]::GetTempPath()
62-
$LogDir = Join-Path $TempDir 'PSF-Module/Logs'
63-
$LogName = 'RemoveStaleFiles'
149+
$TempDirectory = [System.IO.Path]::GetTempPath()
150+
$LogDirectory = Join-Path $TempDirectory 'PSF-Module/Logs'
151+
$LogFileName = 'RemoveStaleFiles'
64152
$DateSuffix = Get-Date -Format 'yyyy-MM-dd'
65-
$LogPath = Join-Path $LogDir ('{0}-{1}.log' -f $LogName, $DateSuffix)
153+
$LogPath = Join-Path $LogDirectory ('{0}-{1}.log' -f $LogFileName, $DateSuffix)
66154

67155
function Write-StaleFileLog {
68156
param(
69157
[string] $Message,
70158
[string] $Level = 'INFO'
71159
)
72160

73-
if (-not (Test-Path $LogDir)) {
74-
$null = New-Item -Path $LogDir -ItemType Directory -Force
161+
if (-not (Test-Path $LogDirectory)) {
162+
$null = New-Item -Path $LogDirectory -ItemType Directory -Force
75163
}
76164

77-
$Timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
78-
$LogEntry = '{0} [{1}] {2}' -f $Timestamp, $Level, $Message
79-
$LogEntry | Out-File -FilePath $LogPath -Append -Encoding UTF8
165+
$TimeStamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
166+
$LogEntry = '{0} [{1}] {2}' -f $TimeStamp, $Level, $Message
167+
$null = $LogEntry | Out-File -FilePath $LogPath -Append -Encoding UTF8
80168
}
81169

82-
function Test-SystemPath {
83-
param(
84-
[string] $TestPath
85-
)
86-
87-
# Resolve to absolute path.
88-
$FullPath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($TestPath)
89-
90-
# Get blocked paths using system variables where available.
91-
$BlockedPaths = @()
92-
93-
# Windows system paths
94-
if ($IsWindows -or $env:OS -eq 'Windows_NT') {
95-
# Drive roots (C:\, D:\, etc.).
96-
$BlockedPaths += Get-PSDrive -PSProvider FileSystem | ForEach-Object { $_.Root }
97-
98-
# System directories using environment variables.
99-
if ($env:SystemRoot) { $BlockedPaths += $env:SystemRoot }
100-
if ($env:ProgramFiles) { $BlockedPaths += $env:ProgramFiles }
101-
if ($env:ProgramData) { $BlockedPaths += $env:ProgramData }
102-
if ($env:ProgramW6432) { $BlockedPaths += $env:ProgramW6432 }
103-
if (${env:ProgramFiles(x86)}) { $BlockedPaths += ${env:ProgramFiles(x86)} }
104-
105-
# .NET system directory.
106-
try {
107-
$BlockedPaths += [Environment]::SystemDirectory
108-
} catch { }
109-
}
110-
111-
# Unix/Linux system paths.
112-
if ($IsLinux -or $IsMacOS -or (!$IsWindows -and $env:OS -ne 'Windows_NT')) {
113-
$BlockedPaths += @(
114-
'/',
115-
'/etc',
116-
'/bin',
117-
'/sbin',
118-
'/usr',
119-
'/boot',
120-
'/sys',
121-
'/proc'
122-
)
123-
}
124-
125-
# macOS additional paths.
126-
if ($IsMacOS) {
127-
$BlockedPaths += @(
128-
'/System',
129-
'/Applications'
130-
)
131-
}
132-
133-
# Check if the test path matches or is a parent of any blocked path.
134-
foreach ($BlockedPath in $BlockedPaths) {
135-
if (-not $BlockedPath) { continue }
136-
137-
try {
138-
$ResolvedBlockedPath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($BlockedPath)
139-
140-
# Normalize paths for comparison (handle trailing separators).
141-
$NormalizedTestPath = $FullPath.TrimEnd([System.IO.Path]::DirectorySeparatorChar, [System.IO.Path]::AltDirectorySeparatorChar)
142-
$NormalizedBlockedPath = $ResolvedBlockedPath.TrimEnd([System.IO.Path]::DirectorySeparatorChar, [System.IO.Path]::AltDirectorySeparatorChar)
143-
144-
# Check for exact match.
145-
if ($NormalizedTestPath -eq $NormalizedBlockedPath) {
146-
return $true
147-
}
148-
}
149-
catch {
150-
# If path resolution fails, skip this check.
151-
continue
152-
}
153-
}
154-
155-
return $false
156-
}
157170

158171
function Clear-OldLogs {
159172
param(
@@ -164,7 +177,7 @@ function Remove-StaleFiles {
164177

165178
if (Test-Path $LogDirectory) {
166179
$CutoffDate = (Get-Date).AddDays(-$RetentionDays)
167-
Get-ChildItem -Path $LogDirectory -Filter ('{0}-*.log' -f $LogPrefix) |
180+
$null = Get-ChildItem -Path $LogDirectory -Filter ('{0}-*.log' -f $LogPrefix) |
168181
Where-Object { $_.LastWriteTime -lt $CutoffDate } |
169182
ForEach-Object {
170183
try {
@@ -179,7 +192,7 @@ function Remove-StaleFiles {
179192
}
180193

181194
# Clean up old logs.
182-
Clear-OldLogs -LogDirectory $LogDir -LogPrefix $LogName -RetentionDays $LogRetentionDays
195+
Clear-OldLogs -LogDirectory $LogDirectory -LogPrefix $LogFileName -RetentionDays $LogRetentionDays
183196

184197
# Log function start.
185198
$LogParams = @(
@@ -264,14 +277,14 @@ function Remove-StaleFiles {
264277
if ($Files.Count -gt 0) {
265278
Write-Information (' Files: {0}' -f $Files.Count)
266279
if ($VerbosePreference -eq 'Continue') {
267-
$Files | ForEach-Object { Write-Verbose (' {0} ({1})' -f $_.FullName, (Get-Date $_.LastWriteTime -Format 'yyyy-MM-dd HH:mm:ss')) }
280+
$null = $Files | ForEach-Object { Write-Verbose (' {0} ({1})' -f $_.FullName, (Get-Date $_.LastWriteTime -Format 'yyyy-MM-dd HH:mm:ss')) }
268281
}
269282
}
270283

271284
if ($Directories.Count -gt 0) {
272285
Write-Information (' Directories: {0}' -f $Directories.Count)
273286
if ($VerbosePreference -eq 'Continue') {
274-
$Directories | ForEach-Object { Write-Verbose (' {0} ({1})' -f $_.FullName, (Get-Date $_.LastWriteTime -Format 'yyyy-MM-dd HH:mm:ss')) }
287+
$null = $Directories | ForEach-Object { Write-Verbose (' {0} ({1})' -f $_.FullName, (Get-Date $_.LastWriteTime -Format 'yyyy-MM-dd HH:mm:ss')) }
275288
}
276289
}
277290

0 commit comments

Comments
 (0)