#!/usr/bin/env pwsh
# ======================
# Script Init
# ======================
# Ensure we're running in PowerShell Core
if (-not ($PSVersionTable.PSEdition -eq "Core"))
{
Write-Host "This script requires PowerShell Core. Please install it first."
exit 1
}
Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
# ======================
# Variables and Constants
# ======================
# Set default log level (DEBUG, INFO, WARNING, ERROR)
$Script:LOG_LEVEL = if ($env:LOG_LEVEL)
{ $env:LOG_LEVEL
} else
{ "INFO"
}
# Required environment variables
$Script:SUITE = if ($env:SUITE)
{ $env:SUITE
} else
{ ""
}
$Script:WORKSPACE_DIR = if ($env:WORKSPACE_DIR)
{ $env:WORKSPACE_DIR
} else
{ ""
}
# Optional environment variables
$Script:CARGO_BUILD_PROFILE = if ($env:CARGO_BUILD_PROFILE)
{ $env:CARGO_BUILD_PROFILE
} else
{ "dev"
}
$Script:CARGO_FEATURES = if ($env:CARGO_FEATURES)
{ $env:CARGO_FEATURES
} else
{ ""
}
$Script:RUST_LOG = if ($env:RUST_LOG)
{ $env:RUST_LOG
} else
{ ""
}
$Script:CARGO_TEST_THREADS = if ($env:CARGO_TEST_THREADS)
{ $env:CARGO_TEST_THREADS
} else
{ ""
}
# Which static-analysis scanner to run when SUITE is "security-scan":
# semgrep | kics | secrets.
$Script:SCAN = if ($env:SCAN)
{ $env:SCAN
} else
{ ""
}
# Constants
$Script:VALID_SUITES = @("unit", "integration", "doc", "clippy", "fmt", "deny", "security-scan")
# Log file name for this script
$Script:LOG_FILE_NAME = "entropy_auth_test.log"
# ======================
# Shared Functions
# ======================
. "$PSScriptRoot/common.ps1"
# ======================
# Function Definitions
# ======================
# Ensure all mandatory environment variables are present
function Test-EnvironmentVariables
{
$absent = @()
if ([string]::IsNullOrEmpty($Script:SUITE))
{ $absent += "SUITE"
}
if ([string]::IsNullOrEmpty($Script:WORKSPACE_DIR))
{ $absent += "WORKSPACE_DIR"
}
if ($absent.Count -gt 0)
{
Write-Log -LogLevel "ERROR" -LogMessage "Missing required environment variables:"
foreach ($v in $absent)
{
Write-Log -LogLevel "ERROR" -LogMessage " - $v"
}
return $false
}
# Validate suite is a known value
if ($Script:SUITE -notin $Script:VALID_SUITES)
{
Write-Log -LogLevel "ERROR" -LogMessage "SUITE must be one of: $($Script:VALID_SUITES -join ', '). Got: '$($Script:SUITE)'"
return $false
}
Write-Log -LogLevel "INFO" -LogMessage "All required environment variables are present"
return $true
}
# Build common cargo arguments from configuration
function Get-CargoBaseArgs
{
$cargoArgs = @()
if ($Script:CARGO_BUILD_PROFILE -ne "dev")
{
$cargoArgs += "--profile"
$cargoArgs += $Script:CARGO_BUILD_PROFILE
}
if (-not [string]::IsNullOrEmpty($Script:CARGO_FEATURES))
{
$cargoArgs += "--features"
$cargoArgs += $Script:CARGO_FEATURES
}
return $cargoArgs
}
# Run unit tests via cargo test (library and inline tests)
function Invoke-UnitTests
{
Start-OperationTiming -Operation "unit_tests"
Write-Log -LogLevel "INFO" -LogMessage "=== Running Unit Tests ==="
$cargoArgs = @("test", "--lib") + (Get-CargoBaseArgs)
# Limit test threads if configured (required for tests that mutate env vars)
if (-not [string]::IsNullOrEmpty($Script:CARGO_TEST_THREADS))
{
$cargoArgs += "--"
$cargoArgs += "--test-threads=$($Script:CARGO_TEST_THREADS)"
}
Write-Log -LogLevel "DEBUG" -LogMessage "Working directory: $($Script:WORKSPACE_DIR)"
Write-Log -LogLevel "DEBUG" -LogMessage "Command: cargo $($cargoArgs -join ' ')"
Push-Location $Script:WORKSPACE_DIR
try
{
Write-Log -LogLevel "INFO" -LogMessage "Running cargo test --lib..."
& cargo @cargoArgs 2>&1 | ForEach-Object {
$line = "$_"
if (-not [string]::IsNullOrWhiteSpace($line))
{
if ($line -match 'test result:|FAILED|error\[' )
{
Write-Log -LogLevel "INFO" -LogMessage "[cargo test] $line"
} else
{
Write-Log -LogLevel "DEBUG" -LogMessage "[cargo test] $line"
}
}
}
if ($LASTEXITCODE -ne 0)
{
Write-Log -LogLevel "ERROR" -LogMessage "Unit tests failed with exit code $LASTEXITCODE"
return $false
}
Write-Log -LogLevel "INFO" -LogMessage "Unit tests passed" -OperationName "unit_tests"
return $true
} finally
{
Pop-Location
Stop-OperationTiming -Operation "unit_tests"
}
}
# Run integration tests via cargo test --test (tests/ directory)
function Invoke-IntegrationTests
{
Start-OperationTiming -Operation "integration_tests"
Write-Log -LogLevel "INFO" -LogMessage "=== Running Integration Tests ==="
$cargoArgs = @("test", "--test", "*") + (Get-CargoBaseArgs)
if (-not [string]::IsNullOrEmpty($Script:CARGO_TEST_THREADS))
{
$cargoArgs += "--"
$cargoArgs += "--test-threads=$($Script:CARGO_TEST_THREADS)"
}
Write-Log -LogLevel "DEBUG" -LogMessage "Working directory: $($Script:WORKSPACE_DIR)"
Write-Log -LogLevel "DEBUG" -LogMessage "Command: cargo $($cargoArgs -join ' ')"
Push-Location $Script:WORKSPACE_DIR
try
{
Write-Log -LogLevel "INFO" -LogMessage "Running cargo test --test '*'..."
& cargo @cargoArgs 2>&1 | ForEach-Object {
$line = "$_"
if (-not [string]::IsNullOrWhiteSpace($line))
{
if ($line -match 'test result:|FAILED|error\[')
{
Write-Log -LogLevel "INFO" -LogMessage "[cargo test] $line"
} else
{
Write-Log -LogLevel "DEBUG" -LogMessage "[cargo test] $line"
}
}
}
if ($LASTEXITCODE -ne 0)
{
Write-Log -LogLevel "ERROR" -LogMessage "Integration tests failed with exit code $LASTEXITCODE"
return $false
}
Write-Log -LogLevel "INFO" -LogMessage "Integration tests passed" -OperationName "integration_tests"
return $true
} finally
{
Pop-Location
Stop-OperationTiming -Operation "integration_tests"
}
}
# Run documentation tests via cargo test --doc
function Invoke-DocTests
{
Start-OperationTiming -Operation "doc_tests"
Write-Log -LogLevel "INFO" -LogMessage "=== Running Documentation Tests ==="
$cargoArgs = @("test", "--doc") + (Get-CargoBaseArgs)
Write-Log -LogLevel "DEBUG" -LogMessage "Working directory: $($Script:WORKSPACE_DIR)"
Write-Log -LogLevel "DEBUG" -LogMessage "Command: cargo $($cargoArgs -join ' ')"
Push-Location $Script:WORKSPACE_DIR
try
{
Write-Log -LogLevel "INFO" -LogMessage "Running cargo test --doc..."
& cargo @cargoArgs 2>&1 | ForEach-Object {
$line = "$_"
if (-not [string]::IsNullOrWhiteSpace($line))
{
if ($line -match 'test result:|FAILED|error\[')
{
Write-Log -LogLevel "INFO" -LogMessage "[cargo test] $line"
} else
{
Write-Log -LogLevel "DEBUG" -LogMessage "[cargo test] $line"
}
}
}
if ($LASTEXITCODE -ne 0)
{
Write-Log -LogLevel "ERROR" -LogMessage "Documentation tests failed with exit code $LASTEXITCODE"
return $false
}
Write-Log -LogLevel "INFO" -LogMessage "Documentation tests passed" -OperationName "doc_tests"
return $true
} finally
{
Pop-Location
Stop-OperationTiming -Operation "doc_tests"
}
}
# Run clippy lints (cargo clippy with warnings as errors)
function Invoke-ClippyCheck
{
Start-OperationTiming -Operation "clippy_check"
Write-Log -LogLevel "INFO" -LogMessage "=== Running Clippy Lints ==="
# Lint every feature by default (overridable via env) so the optional
# asym-jwt/oidc/saml/ssh-keys code paths are covered, not just the default
# feature set.
if ([string]::IsNullOrEmpty($Script:CARGO_FEATURES))
{ $Script:CARGO_FEATURES = "saml,oidc,ssh-keys,asym-jwt"
}
$cargoArgs = @("clippy", "--all-targets") + (Get-CargoBaseArgs) + @("--", "-D", "warnings", "-W", "clippy::pedantic")
Write-Log -LogLevel "DEBUG" -LogMessage "Working directory: $($Script:WORKSPACE_DIR)"
Write-Log -LogLevel "DEBUG" -LogMessage "Command: cargo $($cargoArgs -join ' ')"
Push-Location $Script:WORKSPACE_DIR
try
{
Write-Log -LogLevel "INFO" -LogMessage "Running cargo clippy --all-targets -- -D warnings -W clippy::pedantic..."
& cargo @cargoArgs 2>&1 | ForEach-Object {
$line = "$_"
if (-not [string]::IsNullOrWhiteSpace($line))
{
if ($line -match 'warning:|error\[')
{
Write-Log -LogLevel "INFO" -LogMessage "[clippy] $line"
} else
{
Write-Log -LogLevel "DEBUG" -LogMessage "[clippy] $line"
}
}
}
if ($LASTEXITCODE -ne 0)
{
Write-Log -LogLevel "ERROR" -LogMessage "Clippy check failed with exit code $LASTEXITCODE"
return $false
}
Write-Log -LogLevel "INFO" -LogMessage "Clippy check passed — zero warnings" -OperationName "clippy_check"
return $true
} finally
{
Pop-Location
Stop-OperationTiming -Operation "clippy_check"
}
}
# Run formatting check (cargo fmt --check)
function Invoke-FmtCheck
{
Start-OperationTiming -Operation "fmt_check"
Write-Log -LogLevel "INFO" -LogMessage "=== Running Formatting Check ==="
$cargoArgs = @("fmt", "--", "--check")
Write-Log -LogLevel "DEBUG" -LogMessage "Working directory: $($Script:WORKSPACE_DIR)"
Write-Log -LogLevel "DEBUG" -LogMessage "Command: cargo $($cargoArgs -join ' ')"
Push-Location $Script:WORKSPACE_DIR
try
{
Write-Log -LogLevel "INFO" -LogMessage "Running cargo fmt -- --check..."
& cargo @cargoArgs 2>&1 | ForEach-Object {
$line = "$_"
if (-not [string]::IsNullOrWhiteSpace($line))
{
if ($line -match 'Diff in|should be')
{
Write-Log -LogLevel "INFO" -LogMessage "[fmt] $line"
} else
{
Write-Log -LogLevel "DEBUG" -LogMessage "[fmt] $line"
}
}
}
if ($LASTEXITCODE -ne 0)
{
Write-Log -LogLevel "ERROR" -LogMessage "Formatting check failed with exit code $LASTEXITCODE"
return $false
}
Write-Log -LogLevel "INFO" -LogMessage "Formatting check passed — all files formatted" -OperationName "fmt_check"
return $true
} finally
{
Pop-Location
Stop-OperationTiming -Operation "fmt_check"
}
}
# Run supply-chain audit (cargo-deny: advisories, licenses, bans, sources).
# --all-features so optional deps (rsa via asym-jwt, …) are audited and the
# documented advisory ignores in deny.toml are exercised.
function Invoke-DenyCheck
{
Start-OperationTiming -Operation "deny_check"
Write-Log -LogLevel "INFO" -LogMessage "=== Running Supply-Chain Audit (cargo-deny) ==="
# Install on demand; the build-agent image does not ship cargo-deny.
if (-not (Get-Command cargo-deny -ErrorAction SilentlyContinue))
{
Write-Log -LogLevel "INFO" -LogMessage "Installing cargo-deny..."
& cargo install cargo-deny --locked
}
Push-Location $Script:WORKSPACE_DIR
try
{
Write-Log -LogLevel "INFO" -LogMessage "Running cargo deny --all-features check..."
& cargo deny --all-features check advisories licenses bans sources 2>&1 | ForEach-Object {
$line = "$_"
if (-not [string]::IsNullOrWhiteSpace($line))
{ Write-Log -LogLevel "DEBUG" -LogMessage "[cargo-deny] $line"
}
}
if ($LASTEXITCODE -ne 0)
{
Write-Log -LogLevel "ERROR" -LogMessage "cargo-deny check failed with exit code $LASTEXITCODE"
return $false
}
Write-Log -LogLevel "INFO" -LogMessage "cargo-deny check passed" -OperationName "deny_check"
return $true
} finally
{
Pop-Location
Stop-OperationTiming -Operation "deny_check"
}
}
# ======================
# Security Scanners (SAST)
# ======================
# Ported from the simple-finance test runner. Each scanner self-installs into
# the (Debian) test-agent on demand and emits a GitLab report artifact.
function Get-LinuxArch
{
# Release archives disagree on arch naming: kics uses amd64/arm64,
# gitleaks uses x64/arm64. Pass the project's convention.
param(
[ValidateSet('amd64', 'x64', 'x86_64')]
[string]$AmdConvention = 'amd64',
[ValidateSet('arm64', 'aarch64')]
[string]$ArmConvention = 'arm64'
)
$m = (& uname -m).Trim()
switch ($m)
{
'x86_64'
{ return $AmdConvention
}
'aarch64'
{ return $ArmConvention
}
'arm64'
{ return $ArmConvention
}
default
{ throw "Unsupported architecture: $m"
}
}
}
function Install-Semgrep
{
if (Get-Command semgrep -ErrorAction SilentlyContinue)
{ return
}
Write-Log -LogLevel "INFO" -LogMessage "Installing semgrep via pip (venv)"
# venv avoids PEP 668 "externally managed" rejection on newer Debian and
# keeps the agent's site-packages clean; symlink into PATH for callers.
$venv = "/opt/semgrep-venv"
if (-not (Test-Path "$venv/bin/semgrep"))
{
& apt-get update -qq
& apt-get install -y --no-install-recommends python3-venv python3-pip
if ($LASTEXITCODE -ne 0)
{ throw "python3-venv install failed"
}
& python3 -m venv $venv
if ($LASTEXITCODE -ne 0)
{ throw "python3 -m venv failed"
}
& "$venv/bin/pip" install --quiet --upgrade pip
& "$venv/bin/pip" install --quiet semgrep
if ($LASTEXITCODE -ne 0)
{ throw "semgrep install failed"
}
}
& ln -sf "$venv/bin/semgrep" /usr/local/bin/semgrep
}
function Install-Kics
{
# The release tarball ships the binary alone; the rule set lives in the
# source repo, so download the binary AND shallow-clone the matching tag.
$kicsBin = "/opt/kics/kics"
$kicsSrc = "/opt/kics/src"
$version = "2.1.20"
if ((Test-Path $kicsBin) -and (Test-Path "$kicsSrc/assets/queries") -and (Get-Command kics -ErrorAction SilentlyContinue))
{
return
}
Write-Log -LogLevel "INFO" -LogMessage "Installing kics $version (binary + queries)"
$arch = Get-LinuxArch -AmdConvention 'amd64' -ArmConvention 'arm64'
$url = "https://github.com/Checkmarx/kics/releases/download/v$version/kics_${version}_linux_${arch}.tar.gz"
New-Item -ItemType Directory -Path "/opt/kics" -Force | Out-Null
$tar = "/tmp/kics-$version-$arch.tar.gz"
Invoke-WebRequest -Uri $url -OutFile $tar
& tar -xzf $tar -C "/opt/kics"
Remove-Item -Force $tar
& ln -sf $kicsBin /usr/local/bin/kics
if (-not (Test-Path "$kicsSrc/assets/queries"))
{
Write-Log -LogLevel "INFO" -LogMessage "Cloning kics queries tag v$version"
& git clone --quiet --depth=1 --branch "v$version" `
"https://github.com/Checkmarx/kics.git" $kicsSrc
if ($LASTEXITCODE -ne 0)
{ throw "kics queries clone failed"
}
}
}
function Install-Gitleaks
{
if (Get-Command gitleaks -ErrorAction SilentlyContinue)
{ return
}
Write-Log -LogLevel "INFO" -LogMessage "Installing gitleaks binary"
$version = "8.30.1"
$arch = Get-LinuxArch -AmdConvention 'x64' -ArmConvention 'arm64'
$url = "https://github.com/gitleaks/gitleaks/releases/download/v$version/gitleaks_${version}_linux_${arch}.tar.gz"
$tmpRoot = if ($env:TMPDIR)
{ $env:TMPDIR
} else
{ "/tmp"
}
$tmp = New-Item -ItemType Directory -Path "$tmpRoot/gitleaks-install-$([guid]::NewGuid())" -Force
try
{
$tar = Join-Path $tmp.FullName 'gitleaks.tar.gz'
Invoke-WebRequest -Uri $url -OutFile $tar
& tar -xzf $tar -C $tmp.FullName
Move-Item -Force (Join-Path $tmp.FullName 'gitleaks') /usr/local/bin/gitleaks
} finally
{
Remove-Item -Recurse -Force $tmp.FullName -ErrorAction SilentlyContinue
}
}
# Run a single static-analysis scanner (selected by $Script:SCAN) and emit a
# GitLab report artifact. Findings fail the job.
function Invoke-SecurityScan
{
Start-OperationTiming -Operation "security_scan"
if ([string]::IsNullOrEmpty($Script:SCAN))
{
Write-Log -LogLevel "ERROR" -LogMessage "SCAN env var is required (semgrep | kics | secrets)"
return $false
}
Write-Log -LogLevel "INFO" -LogMessage "=== Running security scan: $($Script:SCAN) ==="
Push-Location $Script:WORKSPACE_DIR
try
{
switch ($Script:SCAN)
{
'semgrep'
{
Install-Semgrep
& semgrep ci --gitlab-sast --output=gl-sast-report.json --error
if ($LASTEXITCODE -ne 0)
{ throw "semgrep findings present"
}
}
'kics'
{
Install-Kics
# No kics.config in this crate (pure Rust, no IaC); rely on the
# bundled rule set and only fail on HIGH-severity findings.
& kics scan -p . -o . `
-q /opt/kics/src/assets/queries `
-b /opt/kics/src/assets/libraries `
--report-formats glsast `
--output-name results `
--fail-on high
$kicsExit = $LASTEXITCODE
if (Test-Path 'results.glsast.json')
{
Move-Item -Force 'results.glsast.json' 'gl-sast-report.json'
}
if ($kicsExit -ne 0)
{ throw "kics findings present (HIGH severity)"
}
}
'secrets'
{
Install-Gitleaks
$gitleaksArgs = @(
"detect", "--source=.",
"--report-path=gl-secret-detection-report.json",
"--report-format=json", "--no-banner", "--redact"
)
# Use a repo .gitleaks.toml when present; otherwise gitleaks'
# built-in rules.
if (Test-Path '.gitleaks.toml')
{ $gitleaksArgs += "--config=.gitleaks.toml"
}
& gitleaks @gitleaksArgs
if ($LASTEXITCODE -ne 0)
{ throw "gitleaks findings present"
}
}
default
{
throw "Unknown SCAN: '$($Script:SCAN)'. Expected one of: semgrep, kics, secrets."
}
}
} catch
{
Write-Log -LogLevel "ERROR" -LogMessage "Security scan failed: $_"
return $false
} finally
{
Pop-Location
Stop-OperationTiming -Operation "security_scan"
}
Write-Log -LogLevel "INFO" -LogMessage "Security scan passed: $($Script:SCAN)" -OperationName "security_scan"
return $true
}
# ======================
# Main Script Execution
# ======================
# Display Banner
Write-Host '
##############################################################################################
# #
# Entropy Softworks — entropy-auth Test Automation #
# #
##############################################################################################
=== Overview ===
Runs Rust test suites for the entropy-auth crate based on the SUITE
environment variable:
- unit : Library and inline unit tests (cargo test --lib)
- integration : Integration tests from the tests/ directory (cargo test --test)
- doc : Documentation example tests (cargo test --doc)
- clippy : Lint analysis with warnings as errors (cargo clippy -D warnings)
- fmt : Formatting check (cargo fmt -- --check)
=== Environment Variables ===
Required: SUITE, WORKSPACE_DIR
Optional: CARGO_BUILD_PROFILE, CARGO_FEATURES, CARGO_TEST_THREADS,
RUST_LOG, LOG_LEVEL
'
Start-OperationTiming -Operation "total_test"
# Validate environment variables
if (-not (Test-EnvironmentVariables))
{
Write-Log -LogLevel "ERROR" -LogMessage "Environment validation failed — aborting"
exit 1
}
# Verify required tooling
if (-not (Test-ToolInstallation -ToolName "cargo"))
{
Write-Log -LogLevel "ERROR" -LogMessage "cargo is required but not available"
exit 1
}
# Log configuration summary
Write-Log -LogLevel "INFO" -LogMessage "Configuration:"
Write-Log -LogLevel "INFO" -LogMessage " Suite : $($Script:SUITE)"
Write-Log -LogLevel "INFO" -LogMessage " Workspace : $($Script:WORKSPACE_DIR)"
Write-Log -LogLevel "INFO" -LogMessage " Build Profile : $($Script:CARGO_BUILD_PROFILE)"
if (-not [string]::IsNullOrEmpty($Script:CARGO_FEATURES))
{
Write-Log -LogLevel "INFO" -LogMessage " Features : $($Script:CARGO_FEATURES)"
}
if (-not [string]::IsNullOrEmpty($Script:CARGO_TEST_THREADS))
{
Write-Log -LogLevel "INFO" -LogMessage " Test Threads : $($Script:CARGO_TEST_THREADS)"
}
# Set RUST_LOG if provided
if (-not [string]::IsNullOrEmpty($Script:RUST_LOG))
{
$env:RUST_LOG = $Script:RUST_LOG
Write-Log -LogLevel "DEBUG" -LogMessage "RUST_LOG set to: $($Script:RUST_LOG)"
}
# Run the requested test suite
$testResult = $false
switch ($Script:SUITE)
{
"unit"
{
$testResult = Invoke-UnitTests
}
"integration"
{
$testResult = Invoke-IntegrationTests
}
"doc"
{
$testResult = Invoke-DocTests
}
"clippy"
{
$testResult = Invoke-ClippyCheck
}
"fmt"
{
$testResult = Invoke-FmtCheck
}
"deny"
{
$testResult = Invoke-DenyCheck
}
"security-scan"
{
$testResult = Invoke-SecurityScan
}
}
# Done
Stop-OperationTiming -Operation "total_test"
if ($testResult)
{
Write-Log -LogLevel "INFO" -LogMessage "Test suite completed successfully" -OperationName "total_test"
Show-TimingSummary
exit 0
} else
{
Write-Log -LogLevel "ERROR" -LogMessage "Test suite failed"
Show-TimingSummary
exit 1
}