diff --git a/.github/scripts/Invoke-TestsAsBasicUser.ps1 b/.github/scripts/Invoke-TestsAsBasicUser.ps1
index 60d33c6..89b6645 100644
--- a/.github/scripts/Invoke-TestsAsBasicUser.ps1
+++ b/.github/scripts/Invoke-TestsAsBasicUser.ps1
@@ -11,7 +11,7 @@
results through a file of this account, which the restricted token can write.
.PARAMETER ResultPath
- Specifies the path of the result file in the NUnit format.
+ Specifies an absolute path, or a path relative to the repository, for the result file in the NUnit format.
.PARAMETER Title
Specifies the heading of the test results in the job summary. It can't contain a double quote, a percent sign, or a
@@ -167,7 +167,7 @@ public static class NTFSSecurityBasicUserProcess
'@
$repositoryPath = (Resolve-Path -LiteralPath (Join-Path -Path $PSScriptRoot -ChildPath '..\..')).ProviderPath
-$resultFullPath = [IO.Path]::GetFullPath((Join-Path -Path $repositoryPath -ChildPath $ResultPath))
+$resultFullPath = [IO.Path]::GetFullPath([IO.Path]::Combine($repositoryPath, $ResultPath))
$resultFolder = Split-Path -Path $resultFullPath -Parent
if (-not (Test-Path -LiteralPath $resultFolder)) {
New-Item -ItemType Directory -Path $resultFolder | Out-Null
diff --git a/Tests/BasicUserRunner.Tests.ps1 b/Tests/BasicUserRunner.Tests.ps1
new file mode 100644
index 0000000..1ac46bb
--- /dev/null
+++ b/Tests/BasicUserRunner.Tests.ps1
@@ -0,0 +1,81 @@
+<#
+ Tests the basic-user CI wrapper without creating a process or changing privileges. A fake native process writes
+ the same result-file boundary as the child; only the Add-Type call of the copied wrapper is mocked.
+#>
+[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
+ 'PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Pester shares variables between blocks.'
+)]
+param ()
+
+BeforeAll {
+ Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath 'TestHelpers.psm1') -Force
+ $sandbox = New-TestSandbox -Name 'BasicUserWrapper'
+ $repository = Join-Path -Path $sandbox -ChildPath 'Repository'
+ $scripts = Join-Path -Path $repository -ChildPath '.github\scripts'
+ Assert-TestSandboxPath -Sandbox $sandbox -Path $scripts
+ New-Item -ItemType Directory -Path $scripts -Force | Out-Null
+ $wrapper = Join-Path -Path $scripts -ChildPath 'Invoke-TestsAsBasicUser.ps1'
+ Copy-Item -LiteralPath (Join-Path -Path $PSScriptRoot -ChildPath '..\.github\scripts\Invoke-TestsAsBasicUser.ps1') -Destination $wrapper
+ Add-Type -TypeDefinition @"
+using System;
+using System.IO;
+using System.Text.RegularExpressions;
+
+public static class NTFSSecurityBasicUserProcess
+{
+ public static int Calls;
+ public static string WorkingDirectory;
+
+ public static int Run(string applicationName, string commandLine, string currentDirectory)
+ {
+ Calls++;
+ WorkingDirectory = currentDirectory;
+ var match = Regex.Match(commandLine, "-ResultPath \"([^\"]+)\"");
+ if (!match.Success)
+ throw new InvalidOperationException("The child command has no result path.");
+ File.WriteAllText(match.Groups[1].Value, "");
+ return 0;
+ }
+}
+"@
+}
+
+AfterAll {
+ Remove-TestSandbox -Sandbox $sandbox
+}
+
+Describe 'Invoke-TestsAsBasicUser.ps1 result paths' {
+ BeforeEach {
+ [NTFSSecurityBasicUserProcess]::Calls = 0
+ [NTFSSecurityBasicUserProcess]::WorkingDirectory = $null
+ Mock -CommandName Add-Type -ParameterFilter { $TypeDefinition -like '*class NTFSSecurityBasicUserProcess*' }
+ }
+
+ It 'Should copy the result to an absolute path, also when that path contains spaces' {
+ $result = Join-Path -Path $sandbox -ChildPath 'Absolute results\Result.xml'
+ Assert-TestSandboxPath -Sandbox $sandbox -Path $result
+
+ & $wrapper -ResultPath $result -Title 'Absolute result path' | Out-Null
+
+ Get-Content -LiteralPath $result -Raw | Should -BeExactly ''
+ [NTFSSecurityBasicUserProcess]::Calls | Should -Be 1
+ [NTFSSecurityBasicUserProcess]::WorkingDirectory | Should -Be $repository
+ Should -Invoke -CommandName Add-Type -Times 1 -Exactly
+ }
+
+ It 'Should resolve a relative path against the repository, not the caller location' {
+ $result = Join-Path -Path $repository -ChildPath 'Relative results\Result.xml'
+ Assert-TestSandboxPath -Sandbox $sandbox -Path $result
+ Push-Location -LiteralPath $sandbox
+ try {
+ & $wrapper -ResultPath 'Relative results\Result.xml' -Title 'Relative result path' | Out-Null
+ }
+ finally {
+ Pop-Location
+ }
+
+ Get-Content -LiteralPath $result -Raw | Should -BeExactly ''
+ [NTFSSecurityBasicUserProcess]::Calls | Should -Be 1
+ [NTFSSecurityBasicUserProcess]::WorkingDirectory | Should -Be $repository
+ }
+}