Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 138 additions & 0 deletions .github/scripts/Expose-TestData.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
if ([string]::IsNullOrWhiteSpace($env:PSMODULE_TEST_DATA)) {
Write-Output 'No test data was provided by the calling workflow.'
return
}
try {
$data = $env:PSMODULE_TEST_DATA | ConvertFrom-Json -ErrorAction Stop
} catch {
throw "The 'TestData' secret must be valid JSON with 'secrets' and/or 'variables' maps."
}
if ($null -eq $data -or $data -isnot [pscustomobject]) {
throw "The 'TestData' secret must be a JSON object with 'secrets' and/or 'variables' maps."
}
$allowedTopLevelKeys = @('secrets', 'variables')
foreach ($propertyName in $data.PSObject.Properties.Name) {
if ($allowedTopLevelKeys -notcontains $propertyName) {
throw "The 'TestData' secret only supports 'secrets' and 'variables' maps."
}
}
$reservedNames = @('CI', 'HOME', 'PATH', 'PWD', 'SHELL', 'PSMODULE_TEST_DATA')
$reservedPrefixes = @('GITHUB_', 'RUNNER_', 'ACTIONS_')
function Assert-EnvironmentName {
<#
.SYNOPSIS
Validates that a TestData key can safely be written to GITHUB_ENV.
#>
param([string] $Name)
if ($Name -notmatch '^[A-Za-z_][A-Za-z0-9_]*$') {
throw 'TestData keys must be valid environment variable names.'
}
$normalized = $Name.ToUpperInvariant()
if ($reservedNames -contains $normalized) {
throw 'TestData keys must not override reserved environment variables.'
}
foreach ($prefix in $reservedPrefixes) {
if ($normalized.StartsWith($prefix)) {
throw 'TestData keys must not override reserved environment variables.'
}
}
}
function Assert-Map {
<#
.SYNOPSIS
Validates that a TestData section is a JSON object map.
#>
param(
[object] $Map,
[string] $Name
)
if ($null -eq $Map) { return }
if ($Map -isnot [pscustomobject]) {
throw "The 'TestData.$Name' value must be a JSON object."
}
}
function Get-EnvironmentValue {
<#
.SYNOPSIS
Converts a scalar TestData value to an environment variable value.
#>
param(
[object] $Value,
[string] $Name
)
if ($null -eq $Value) { return '' }
if (
$Value -is [pscustomobject] -or
($Value -is [System.Collections.IEnumerable] -and $Value -isnot [string])
) {
throw "Values in 'TestData.$Name' must be scalar values."
}
return [string]$Value
}
function Add-EnvFromMap {
<#
.SYNOPSIS
Writes validated TestData entries to GITHUB_ENV.
#>
param(
[object] $Map,
[string] $Name,
[switch] $Mask
)
Assert-Map -Map $Map -Name $Name
if ($null -eq $Map) { return }
$count = 0
foreach ($item in $Map.PSObject.Properties) {
$name = $item.Name
Assert-EnvironmentName -Name $name
$value = Get-EnvironmentValue -Value $item.Value -Name $Name
if ($Mask) {
foreach ($line in ($value -split "`n")) {
$line = $line.TrimEnd("`r")
if ($line.Length -gt 0) {
Write-Output "::add-mask::$line"
}
}
}
do {
$delimiter = "GHENV_$([guid]::NewGuid().ToString('N'))"
} while ($value.Contains($delimiter))
Add-Content -Path $env:GITHUB_ENV -Value "$name<<$delimiter" -Encoding utf8
Add-Content -Path $env:GITHUB_ENV -Value $value -Encoding utf8
Add-Content -Path $env:GITHUB_ENV -Value $delimiter -Encoding utf8
$count++
}
if ($count -gt 0) {
if ($Mask) {
Write-Output "Exposed $count secret value(s) as environment variables."
} else {
Write-Output "Exposed $count variable value(s) as environment variables."
}
}
}

Assert-Map -Map $data.secrets -Name 'secrets'
Assert-Map -Map $data.variables -Name 'variables'

$secretNames = @()
if ($null -ne $data.secrets) {
$secretNames = @($data.secrets.PSObject.Properties.Name)
}
$variableNames = @()
if ($null -ne $data.variables) {
$variableNames = @($data.variables.PSObject.Properties.Name)
}
$secretNameSet = [System.Collections.Generic.HashSet[string]]::new(
[System.StringComparer]::OrdinalIgnoreCase
)
foreach ($secretName in $secretNames) {
[void] $secretNameSet.Add($secretName)
}
foreach ($variableName in $variableNames) {
if ($secretNameSet.Contains($variableName)) {
throw 'TestData keys must not be duplicated across secrets and variables.'
}
}

Add-EnvFromMap -Map $data.secrets -Name 'secrets' -Mask
Add-EnvFromMap -Map $data.variables -Name 'variables'
Comment thread
MariusStorhaug marked this conversation as resolved.
41 changes: 12 additions & 29 deletions .github/workflows/AfterAll-ModuleLocal.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,42 +3,18 @@ name: AfterAll-ModuleLocal
on:
workflow_call:
secrets:
TEST_APP_ENT_CLIENT_ID:
description: The client ID of an Enterprise GitHub App for running tests.
required: false
TEST_APP_ENT_PRIVATE_KEY:
description: The private key of an Enterprise GitHub App for running tests.
required: false
TEST_APP_ORG_CLIENT_ID:
description: The client ID of an Organization GitHub App for running tests.
required: false
TEST_APP_ORG_PRIVATE_KEY:
description: The private key of an Organization GitHub App for running tests.
required: false
TEST_USER_ORG_FG_PAT:
description: The fine-grained personal access token with org access for running tests.
required: false
TEST_USER_USER_FG_PAT:
description: The fine-grained personal access token with user account access for running tests.
required: false
TEST_USER_PAT:
description: The classic personal access token for running tests.
TestData:
description: |
Optional single-line JSON object with 'secrets' and 'variables' maps. Each entry is exposed
as an environment variable available to the AfterAll teardown script; 'secrets' values are
masked in the logs, 'variables' values are not.
required: false
inputs:
Settings:
type: string
description: The complete settings object including test suites.
required: true

env:
TEST_APP_ENT_CLIENT_ID: ${{ secrets.TEST_APP_ENT_CLIENT_ID }}
TEST_APP_ENT_PRIVATE_KEY: ${{ secrets.TEST_APP_ENT_PRIVATE_KEY }}
TEST_APP_ORG_CLIENT_ID: ${{ secrets.TEST_APP_ORG_CLIENT_ID }}
TEST_APP_ORG_PRIVATE_KEY: ${{ secrets.TEST_APP_ORG_PRIVATE_KEY }}
TEST_USER_ORG_FG_PAT: ${{ secrets.TEST_USER_ORG_FG_PAT }}
TEST_USER_USER_FG_PAT: ${{ secrets.TEST_USER_USER_FG_PAT }}
TEST_USER_PAT: ${{ secrets.TEST_USER_PAT }}

permissions:
contents: read # to checkout the repo

Expand All @@ -55,6 +31,13 @@ jobs:
persist-credentials: false
fetch-depth: 0

- name: Expose caller-provided test data
shell: pwsh
env:
PSMODULE_TEST_DATA: ${{ secrets.TestData }}
run: |
Comment thread
MariusStorhaug marked this conversation as resolved.
./.github/scripts/Expose-TestData.ps1

- name: Run AfterAll Teardown Scripts
if: always()
uses: PSModule/GitHub-Script@1ee97bbc652d19c38ae12f6e1e47e9d9fbd12d0a # v1.8.0
Expand Down
41 changes: 12 additions & 29 deletions .github/workflows/BeforeAll-ModuleLocal.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,42 +3,18 @@ name: BeforeAll-ModuleLocal
on:
workflow_call:
secrets:
TEST_APP_ENT_CLIENT_ID:
description: The client ID of an Enterprise GitHub App for running tests.
required: false
TEST_APP_ENT_PRIVATE_KEY:
description: The private key of an Enterprise GitHub App for running tests.
required: false
TEST_APP_ORG_CLIENT_ID:
description: The client ID of an Organization GitHub App for running tests.
required: false
TEST_APP_ORG_PRIVATE_KEY:
description: The private key of an Organization GitHub App for running tests.
required: false
TEST_USER_ORG_FG_PAT:
description: The fine-grained personal access token with org access for running tests.
required: false
TEST_USER_USER_FG_PAT:
description: The fine-grained personal access token with user account access for running tests.
required: false
TEST_USER_PAT:
description: The classic personal access token for running tests.
TestData:
description: |
Optional single-line JSON object with 'secrets' and 'variables' maps. Each entry is exposed
as an environment variable available to the BeforeAll setup script; 'secrets' values are
masked in the logs, 'variables' values are not.
required: false
inputs:
Settings:
type: string
description: The complete settings object including test suites.
required: true

env:
TEST_APP_ENT_CLIENT_ID: ${{ secrets.TEST_APP_ENT_CLIENT_ID }}
TEST_APP_ENT_PRIVATE_KEY: ${{ secrets.TEST_APP_ENT_PRIVATE_KEY }}
TEST_APP_ORG_CLIENT_ID: ${{ secrets.TEST_APP_ORG_CLIENT_ID }}
TEST_APP_ORG_PRIVATE_KEY: ${{ secrets.TEST_APP_ORG_PRIVATE_KEY }}
TEST_USER_ORG_FG_PAT: ${{ secrets.TEST_USER_ORG_FG_PAT }}
TEST_USER_USER_FG_PAT: ${{ secrets.TEST_USER_USER_FG_PAT }}
TEST_USER_PAT: ${{ secrets.TEST_USER_PAT }}

permissions:
contents: read # to checkout the repo

Expand All @@ -55,6 +31,13 @@ jobs:
persist-credentials: false
fetch-depth: 0

- name: Expose caller-provided test data
shell: pwsh
env:
PSMODULE_TEST_DATA: ${{ secrets.TestData }}
run: |
Comment thread
MariusStorhaug marked this conversation as resolved.
./.github/scripts/Expose-TestData.ps1

- name: Run BeforeAll Setup Scripts
uses: PSModule/GitHub-Script@1ee97bbc652d19c38ae12f6e1e47e9d9fbd12d0a # v1.8.0
with:
Expand Down
31 changes: 0 additions & 31 deletions .github/workflows/Test-Module.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,43 +2,12 @@ name: Test-Module

on:
workflow_call:
secrets:
TEST_APP_ENT_CLIENT_ID:
description: The client ID of an Enterprise GitHub App for running tests.
required: false
TEST_APP_ENT_PRIVATE_KEY:
description: The private key of an Enterprise GitHub App for running tests.
required: false
TEST_APP_ORG_CLIENT_ID:
description: The client ID of an Organization GitHub App for running tests.
required: false
TEST_APP_ORG_PRIVATE_KEY:
description: The private key of an Organization GitHub App for running tests.
required: false
TEST_USER_ORG_FG_PAT:
description: The fine-grained personal access token with org access for running tests.
required: false
TEST_USER_USER_FG_PAT:
description: The fine-grained personal access token with user account access for running tests.
required: false
TEST_USER_PAT:
description: The classic personal access token for running tests.
required: false
inputs:
Settings:
type: string
description: The settings object as a JSON string.
required: true

env:
TEST_APP_ENT_CLIENT_ID: ${{ secrets.TEST_APP_ENT_CLIENT_ID }}
TEST_APP_ENT_PRIVATE_KEY: ${{ secrets.TEST_APP_ENT_PRIVATE_KEY }}
TEST_APP_ORG_CLIENT_ID: ${{ secrets.TEST_APP_ORG_CLIENT_ID }}
TEST_APP_ORG_PRIVATE_KEY: ${{ secrets.TEST_APP_ORG_PRIVATE_KEY }}
TEST_USER_ORG_FG_PAT: ${{ secrets.TEST_USER_ORG_FG_PAT }}
TEST_USER_USER_FG_PAT: ${{ secrets.TEST_USER_USER_FG_PAT }}
TEST_USER_PAT: ${{ secrets.TEST_USER_PAT }}

permissions:
contents: read # to checkout the repo and create releases on the repo

Expand Down
39 changes: 12 additions & 27 deletions .github/workflows/Test-ModuleLocal.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,11 @@ name: Test-ModuleLocal
on:
workflow_call:
secrets:
TEST_APP_ENT_CLIENT_ID:
description: The client ID of an Enterprise GitHub App for running tests.
required: false
TEST_APP_ENT_PRIVATE_KEY:
description: The private key of an Enterprise GitHub App for running tests.
required: false
TEST_APP_ORG_CLIENT_ID:
description: The client ID of an Organization GitHub App for running tests.
required: false
TEST_APP_ORG_PRIVATE_KEY:
description: The private key of an Organization GitHub App for running tests.
required: false
TEST_USER_ORG_FG_PAT:
description: The fine-grained personal access token with org access for running tests.
required: false
TEST_USER_USER_FG_PAT:
description: The fine-grained personal access token with user account access for running tests.
required: false
TEST_USER_PAT:
description: The classic personal access token for running tests.
TestData:
description: |
Optional single-line JSON object with 'secrets' and 'variables' maps. Each entry is exposed
as an environment variable the module's Pester tests read via $env:<name>; 'secrets' values
are masked in the logs, 'variables' values are not.
required: false
inputs:
Settings:
Expand All @@ -34,13 +19,6 @@ permissions:
contents: read # to checkout the repo and create releases on the repo

env:
TEST_APP_ENT_CLIENT_ID: ${{ secrets.TEST_APP_ENT_CLIENT_ID }}
TEST_APP_ENT_PRIVATE_KEY: ${{ secrets.TEST_APP_ENT_PRIVATE_KEY }}
TEST_APP_ORG_CLIENT_ID: ${{ secrets.TEST_APP_ORG_CLIENT_ID }}
TEST_APP_ORG_PRIVATE_KEY: ${{ secrets.TEST_APP_ORG_PRIVATE_KEY }}
TEST_USER_ORG_FG_PAT: ${{ secrets.TEST_USER_ORG_FG_PAT }}
TEST_USER_USER_FG_PAT: ${{ secrets.TEST_USER_USER_FG_PAT }}
TEST_USER_PAT: ${{ secrets.TEST_USER_PAT }}
GITHUB_TOKEN: ${{ github.token }}

jobs:
Expand All @@ -58,6 +36,13 @@ jobs:
persist-credentials: false
fetch-depth: 0

- name: Expose caller-provided test data
shell: pwsh
env:
PSMODULE_TEST_DATA: ${{ secrets.TestData }}
run: |
Comment thread
MariusStorhaug marked this conversation as resolved.
./.github/scripts/Expose-TestData.ps1

- name: Download module artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
Expand Down
19 changes: 12 additions & 7 deletions .github/workflows/Workflow-Test-Default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,21 @@ permissions:

jobs:
WorkflowTestDefault:
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }}
uses: ./.github/workflows/workflow.yml
secrets:
APIKey: ${{ secrets.APIKey }}
TEST_APP_ENT_CLIENT_ID: ${{ secrets.TEST_APP_ENT_CLIENT_ID }}
TEST_APP_ENT_PRIVATE_KEY: ${{ secrets.TEST_APP_ENT_PRIVATE_KEY }}
TEST_APP_ORG_CLIENT_ID: ${{ secrets.TEST_APP_ORG_CLIENT_ID }}
TEST_APP_ORG_PRIVATE_KEY: ${{ secrets.TEST_APP_ORG_PRIVATE_KEY }}
TEST_USER_ORG_FG_PAT: ${{ secrets.TEST_USER_ORG_FG_PAT }}
TEST_USER_USER_FG_PAT: ${{ secrets.TEST_USER_USER_FG_PAT }}
TEST_USER_PAT: ${{ secrets.TEST_USER_PAT }}
# Self-test only: a dedicated, NON-SENSITIVE repository secret + variable exist purely to prove
# the TestData plumbing end to end - the "secrets" entry is masked and the "variables" entry is
# not, and both are exposed as $env:<name>. Their known values are asserted (value + length) in
# tests/.../Environment.Tests.ps1.
# Secrets use the direct "${{ secrets.X }}" form (CodeQL-clean; avoids toJSON(secrets.*)), which
# requires single-line secret values with no embedded quotes or backslashes; variables use
# toJSON(vars.X) so any characters are encoded safely. The folded '>-' scalar keeps the whole blob
# on ONE line so GitHub registers a single mask instead of one per line.
TestData: >-
{ "secrets": { "PSMODULE_TEST_SINGLELINE_SECRET": "${{ secrets.PSMODULE_TEST_SINGLELINE_SECRET }}" },
"variables": { "PSMODULE_TEST_VARIABLE": ${{ toJSON(vars.PSMODULE_TEST_VARIABLE) }} } }
Comment thread
MariusStorhaug marked this conversation as resolved.
with:
WorkingDirectory: tests/srcTestRepo
ImportantFilePatterns: |
Expand Down
Loading
Loading