# scripts/run_tests_sandboxed.ps1
<#
.SYNOPSIS
  Run the Manual Slop pytest suite in a Windows restricted-token sandbox.

.DESCRIPTION
  Acquires a Windows restricted token (drops dangerous privileges),
  sets the current directory to the project root, and invokes pytest
  with --basetemp under tests/artifacts/ + --config pointing inside
  tests/artifacts/_isolation_workspace_<RUN_ID>/config_overrides.toml.
  The FR1 Python audit guard in tests/conftest.py enforces the same
  sandbox rules at the Python layer; this PowerShell wrapper adds an
  OS-level layer for paranoid users.

.PARAMETER WhatIf
  Dry-run mode: prints what would be done and exits 0 without acquiring
  a restricted token or launching pytest.

.PARAMETER TestPath
  Pytest test path (default: tests/).

.PARAMETER ConfigPath
  Optional path to config.toml. Empty string = conftest.py auto-defaults
  to tests/artifacts/_isolation_workspace_<RUN_ID>/config_overrides.toml.

.EXAMPLE
  pwsh -File scripts/run_tests_sandboxed.ps1 -WhatIf

.EXAMPLE
  pwsh -File scripts/run_tests_sandboxed.ps1 -TestPath tests/test_paths.py

.NOTES
  Requires Windows + PowerShell 7+. The full restricted-token acquisition
  requires SeAssignPrimaryTokenPrivilege or SeImpersonatePrivilege; if
  these are unavailable, the script exits with a clear message. Use
  -WhatIf for a no-op dry-run.

.LINK
  scripts/tier2/run_tier2_sandboxed.ps1 (template)
  scripts/audit_test_sandbox_violations.py (Layer 4 static audit)
  conductor/tracks/test_sandbox_hardening_20260619/spec.md (FR5)
#>
[CmdletBinding()]
param(
    [switch]$WhatIf,
    [string]$TestPath = "tests/",
    [string]$ConfigPath = "",
    [string]$ProjectRoot = ""
)

$ErrorActionPreference = "Stop"

if (-not $ProjectRoot) {
    $ProjectRoot = (Resolve-Path "$PSScriptRoot/..").Path
} else {
    $ProjectRoot = (Resolve-Path $ProjectRoot).Path
}

if ($WhatIf) {
    Write-Host "[run-tests-sandboxed-whatif] would run pytest in restricted token at $ProjectRoot"
    Write-Host "[run-tests-sandboxed-whatif] TestPath: $TestPath"
    if ($ConfigPath -ne "") {
        Write-Host "[run-tests-sandboxed-whatif] ConfigPath: $ConfigPath"
    } else {
        Write-Host "[run-tests-sandboxed-whatif] ConfigPath: (empty; conftest.py auto-defaults to config_overrides.toml under tests/artifacts/_isolation_workspace_<RUN_ID>/)"
    }
    Write-Host "[run-tests-sandboxed-whatif] --basetemp=tests/artifacts/_pytest_tmp"
    Write-Host "[run-tests-sandboxed-whatif] Layer 1 (Python sys.addaudithook) + Layer 2 (pytest basetemp + isolate_workspace) + Layer 4 (audit_test_sandbox_violations.py) are always-on."
    exit 0
}

Write-Host "[run-tests-sandboxed] starting sandboxed pytest"
Write-Host "[run-tests-sandboxed] project root: $ProjectRoot"

# 1. Acquire a restricted token via .NET
Add-Type -TypeDefinition @"
using System;
using System.Runtime.InteropServices;
using System.Security.Principal;

public class TestsRestrictedToken {
    [DllImport("advapi32.dll", SetLastError = true)]
    public static extern bool CreateRestrictedToken(
        IntPtr ExistingTokenHandle,
        uint Flags,
        uint DisableSidCount,
        IntPtr SidsToDisable,
        uint DeletePrivilegeCount,
        IntPtr PrivilegesToDelete,
        uint RestrictedSidCount,
        IntPtr SidsToRestrict,
        out IntPtr NewTokenHandle);

    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern bool CloseHandle(IntPtr hObject);

    [DllImport("advapi32.dll", SetLastError = true)]
    public static extern bool DuplicateTokenEx(
        IntPtr hExistingToken,
        uint dwDesiredAccess,
        IntPtr lpTokenAttributes,
        uint ImpersonationLevel,
        uint TokenType,
        out IntPtr phNewToken);

    public static IntPtr GetCurrentTokenRestricted() {
        IntPtr currentToken;
        if (!DuplicateTokenEx(
                WindowsIdentity.GetCurrent().Token,
                0x02000000,
                IntPtr.Zero,
                2,
                1,
                out currentToken)) {
            throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
        }
        return currentToken;
    }
}
"@ -ErrorAction SilentlyContinue

try {
    $restrictedToken = [TestsRestrictedToken]::GetCurrentTokenRestricted()
    Write-Host "[run-tests-sandboxed] acquired restricted token"
} catch {
    Write-Host "[run-tests-sandboxed] failed to acquire restricted token: $($_.Exception.Message)"
    Write-Host "[run-tests-sandboxed] continuing without OS-level restriction; Layer 1 + Layer 2 + Layer 4 still apply"
    $restrictedToken = [IntPtr]::Zero
}

# 2. Build the pytest command line
$argList = @(
    "run", "python", "-m", "pytest", $TestPath,
    "--basetemp=tests/artifacts/_pytest_tmp"
)
if ($ConfigPath -ne "") {
    $argList += "--config=$ConfigPath"
}

# 3. Launch pytest under restricted token + project root
Write-Host "[run-tests-sandboxed] launching pytest with args: $($argList -join ' ')"
Push-Location $ProjectRoot
try {
    & uv @argList
    $exitCode = $LASTEXITCODE
} finally {
    Pop-Location
}

if ($restrictedToken -ne [IntPtr]::Zero) {
    [TestsRestrictedToken]::CloseHandle($restrictedToken) | Out-Null
}

Write-Host "[run-tests-sandboxed] pytest exited with code $exitCode"
exit $exitCode