From b1cb3fdc775f12d6752da795c22a3a15c3296cd5 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sat, 2 May 2026 14:25:27 +0200 Subject: [PATCH 01/12] Add SharedTestRepositories helpers for idempotent shared test infra --- tests/Data/SharedTestRepositories.ps1 | 117 ++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 tests/Data/SharedTestRepositories.ps1 diff --git a/tests/Data/SharedTestRepositories.ps1 b/tests/Data/SharedTestRepositories.ps1 new file mode 100644 index 000000000..a18847f60 --- /dev/null +++ b/tests/Data/SharedTestRepositories.ps1 @@ -0,0 +1,117 @@ +# Idempotent get-or-create helpers for the shared run-scoped test repositories. +# +# Background +# ---------- +# `tests/BeforeAll.ps1` provisions one repository per (OS, TokenType, RunID) for the +# happy-path workflow run. Several *.Tests.ps1 files then read that repository via +# their per-context `BeforeAll`. Before this helper existed, each test file simply +# threw if the repository was missing, which broke the GitHub Actions +# **Re-run failed jobs** path: `AfterAll-ModuleLocal` deletes the shared repository +# at the end of every attempt, and a partial rerun does not re-execute the +# successful `BeforeAll-ModuleLocal` job, so the leaf jobs landed on a non-existent +# repository (issue #590). +# +# These helpers move ownership of "ensure the shared repository exists" from a +# fragile precondition-check into a declarative get-or-create that any leaf job +# can call. On the happy path the repositories already exist (created by +# `BeforeAll.ps1`) and the helpers are a single `Get-GitHubRepository` call. On a +# partial rerun where the repositories were torn down, the helpers recreate them +# transparently so the leaf job can proceed. +# +# Functions defined here: +# - Initialize-SharedTestRepository primary `Test-{OS}-{TokenType}-{RunID}` repository (with readme/license/gitignore for release tests) +# - Initialize-SharedTestRepositoryExtras org-only `-2` and `-3` companion repositories used by Secrets/Variables tests + +function Initialize-SharedTestRepository { + [CmdletBinding()] + [OutputType([object])] + param( + [Parameter(Mandatory)] + [string] $Owner, + + [Parameter(Mandatory)] + [ValidateSet('user', 'organization')] + [string] $OwnerType, + + [Parameter(Mandatory)] + [string] $Name + ) + + $repo = switch ($OwnerType) { + 'user' { + Get-GitHubRepository -Name $Name -ErrorAction SilentlyContinue + } + 'organization' { + Get-GitHubRepository -Owner $Owner -Name $Name -ErrorAction SilentlyContinue + } + } + + if ($repo) { + return $repo + } + + Write-Host "Shared test repository '$Name' not found for owner '$Owner' ($OwnerType). Creating it now (self-heal path for partial reruns / issue #590)." + + # The primary shared repository is initialized with readme/license/gitignore so + # release tests have a default branch with content available for tag operations. + $createParams = @{ + Name = $Name + AddReadme = $true + License = 'mit' + Gitignore = 'VisualStudio' + } + + try { + $repo = switch ($OwnerType) { + 'user' { New-GitHubRepository @createParams } + 'organization' { New-GitHubRepository @createParams -Organization $Owner } + } + } catch { + # Another leaf job in the same matrix may have created the repository + # between our Get and our New. Re-fetch to recover from that race. + Write-Host "Create attempt for '$Name' failed ($($_.Exception.Message)). Re-fetching in case a parallel job created it." + $repo = switch ($OwnerType) { + 'user' { Get-GitHubRepository -Name $Name -ErrorAction SilentlyContinue } + 'organization' { Get-GitHubRepository -Owner $Owner -Name $Name -ErrorAction SilentlyContinue } + } + if (-not $repo) { + throw + } + } + + return $repo +} + +function Initialize-SharedTestRepositoryExtras { + [CmdletBinding()] + [OutputType([object[]])] + param( + [Parameter(Mandatory)] + [string] $Owner, + + [Parameter(Mandatory)] + [string] $BaseName + ) + + # Extras are only used for organization-scoped Secrets/Variables SelectedRepository tests. + $extras = @() + foreach ($suffix in 2, 3) { + $extraName = "$BaseName-$suffix" + $extra = Get-GitHubRepository -Owner $Owner -Name $extraName -ErrorAction SilentlyContinue + if (-not $extra) { + Write-Host "Shared extra test repository '$extraName' not found for owner '$Owner'. Creating it now (self-heal path for partial reruns / issue #590)." + try { + $extra = New-GitHubRepository -Organization $Owner -Name $extraName + } catch { + Write-Host "Create attempt for '$extraName' failed ($($_.Exception.Message)). Re-fetching in case a parallel job created it." + $extra = Get-GitHubRepository -Owner $Owner -Name $extraName -ErrorAction SilentlyContinue + if (-not $extra) { + throw + } + } + } + $extras += $extra + } + + return $extras +} From fc43af8daa53ce9a821dec5d5b4539d660a2b18d Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sat, 2 May 2026 14:25:41 +0200 Subject: [PATCH 02/12] Route global BeforeAll through SharedTestRepositories helpers --- tests/BeforeAll.ps1 | 27 +++++++-------------------- 1 file changed, 7 insertions(+), 20 deletions(-) diff --git a/tests/BeforeAll.ps1 b/tests/BeforeAll.ps1 index 208759100..229795f59 100644 --- a/tests/BeforeAll.ps1 +++ b/tests/BeforeAll.ps1 @@ -3,6 +3,7 @@ param() LogGroup 'BeforeAll - Global Test Setup' { $authCases = . "$PSScriptRoot/Data/AuthCases.ps1" + . "$PSScriptRoot/Data/SharedTestRepositories.ps1" $id = $env:GITHUB_RUN_ID if (-not $id) { throw 'GITHUB_RUN_ID environment variable is not set. Refusing to create or clean up test repositories with a non-deterministic name.' @@ -67,29 +68,15 @@ LogGroup 'BeforeAll - Global Test Setup' { } } - # Create the primary shared repository (with readme, license, gitignore for release tests). - $repoParams = @{ - Name = $repoName - AddReadme = $true - License = 'mit' - Gitignore = 'VisualStudio' - } - switch ($OwnerType) { - 'user' { - New-GitHubRepository @repoParams - } - 'organization' { - New-GitHubRepository @repoParams -Organization $Owner - } - } + # Provision the primary shared repository via the same idempotent helper that + # leaf jobs use, so the happy-path BeforeAll and the partial-rerun self-heal + # path (issue #590) follow the same code. + Initialize-SharedTestRepository -Owner $Owner -OwnerType $OwnerType -Name $repoName | Out-Null - # Create extra repositories needed by Secrets/Variables SelectedRepository tests. + # Provision extra repositories needed by Secrets/Variables SelectedRepository tests. # Only organization owners need them — those tests are skipped for user owners. if ($OwnerType -eq 'organization') { - foreach ($suffix in 2, 3) { - $extraName = "$repoName-$suffix" - New-GitHubRepository -Organization $Owner -Name $extraName - } + Initialize-SharedTestRepositoryExtras -Owner $Owner -BaseName $repoName | Out-Null } } } From 40e14a4e15fca0bfe4d97a1a1c7faa2590e1a912 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sat, 2 May 2026 14:26:52 +0200 Subject: [PATCH 03/12] Self-heal shared test repositories in leaf-test BeforeAll blocks --- tests/Actions.Tests.ps1 | 8 ++++---- tests/Environments.Tests.ps1 | 10 +++++++--- tests/Releases.Tests.ps1 | 10 +++++++--- tests/Secrets.Tests.ps1 | 18 ++++++++---------- tests/Variables.Tests.ps1 | 18 ++++++++---------- 5 files changed, 34 insertions(+), 30 deletions(-) diff --git a/tests/Actions.Tests.ps1 b/tests/Actions.Tests.ps1 index ef1821dff..96126d448 100644 --- a/tests/Actions.Tests.ps1 +++ b/tests/Actions.Tests.ps1 @@ -22,6 +22,7 @@ BeforeAll { if (-not $id) { throw 'GITHUB_RUN_ID is required for Actions tests because it is used to build repository-scoped names for OIDC operations.' } + . "$PSScriptRoot/Data/SharedTestRepositories.ps1" } Describe 'Actions' { @@ -61,10 +62,9 @@ Describe 'Actions' { if ($OwnerType -in ('repository', 'enterprise')) { $repo = $null } else { - $repo = Get-GitHubRepository -Owner $Owner -Name $repoName - if (-not $repo) { - throw "Shared test repository '$repoName' was not found for owner '$Owner' (OwnerType: '$OwnerType'). Ensure the repository was provisioned and the repository name is correct." - } + # Declarative get-or-create so partial reruns (issue #590) can rebuild + # the shared repository if AfterAll already tore it down. + $repo = Initialize-SharedTestRepository -Owner $Owner -OwnerType $OwnerType -Name $repoName Write-Host ($repo | Select-Object * | Out-String) } } diff --git a/tests/Environments.Tests.ps1 b/tests/Environments.Tests.ps1 index da51d23f5..788b0ac09 100644 --- a/tests/Environments.Tests.ps1 +++ b/tests/Environments.Tests.ps1 @@ -26,6 +26,7 @@ BeforeAll { if (-not $id) { throw 'GITHUB_RUN_ID is required for Environments tests.' } + . "$PSScriptRoot/Data/SharedTestRepositories.ps1" } Describe 'Environments' { @@ -48,9 +49,12 @@ Describe 'Environments' { $environmentName = "$testName-$os-$TokenType-$id" LogGroup "Using Repository - [$repoName]" { - $repo = Get-GitHubRepository -Owner $Owner -Name $repoName - if (($OwnerType -notin ('repository', 'enterprise')) -and (-not $repo)) { - throw "Shared test repository '$repoName' was not found for owner '$Owner'. Ensure the repository was created before running the environment tests." + if ($OwnerType -in ('repository', 'enterprise')) { + $repo = $null + } else { + # Declarative get-or-create so partial reruns (issue #590) can rebuild + # the shared repository if AfterAll already tore it down. + $repo = Initialize-SharedTestRepository -Owner $Owner -OwnerType $OwnerType -Name $repoName } Write-Host ($repo | Select-Object * | Out-String) } diff --git a/tests/Releases.Tests.ps1 b/tests/Releases.Tests.ps1 index bf3f36e2b..e3d05d129 100644 --- a/tests/Releases.Tests.ps1 +++ b/tests/Releases.Tests.ps1 @@ -26,6 +26,7 @@ BeforeAll { if (-not $id) { throw 'GITHUB_RUN_ID must be set for run-scoped release tests.' } + . "$PSScriptRoot/Data/SharedTestRepositories.ps1" } Describe 'Releases' { @@ -47,9 +48,12 @@ Describe 'Releases' { $repoName = "$repoPrefix-$id" LogGroup "Using Repository - [$repoName]" { - $repo = Get-GitHubRepository -Owner $Owner -Name $repoName - if (($OwnerType -notin ('repository', 'enterprise')) -and (-not $repo)) { - throw "Expected shared test repository '$Owner/$repoName' was not found. Get-GitHubRepository returned no result, so release tests cannot continue." + if ($OwnerType -in ('repository', 'enterprise')) { + $repo = $null + } else { + # Declarative get-or-create so partial reruns (issue #590) can rebuild + # the shared repository if AfterAll already tore it down. + $repo = Initialize-SharedTestRepository -Owner $Owner -OwnerType $OwnerType -Name $repoName } Write-Host ($repo | Select-Object * | Out-String) } diff --git a/tests/Secrets.Tests.ps1 b/tests/Secrets.Tests.ps1 index 9f8b43007..f0e8b9931 100644 --- a/tests/Secrets.Tests.ps1 +++ b/tests/Secrets.Tests.ps1 @@ -26,6 +26,7 @@ BeforeAll { if (-not $id) { throw 'GITHUB_RUN_ID is required for Secrets tests because secret cleanup uses run-scoped wildcard names.' } + . "$PSScriptRoot/Data/SharedTestRepositories.ps1" } Describe 'Secrets' { @@ -52,19 +53,16 @@ Describe 'Secrets' { switch ($OwnerType) { 'user' { - $repo = Get-GitHubRepository -Name $repoName - if (-not $repo) { - throw "Shared test repository '$repoName' was not found. Ensure BeforeAll.ps1 provisioned it." - } + # Declarative get-or-create so partial reruns (issue #590) can rebuild + # the shared repository if AfterAll already tore it down. + $repo = Initialize-SharedTestRepository -Owner $Owner -OwnerType 'user' -Name $repoName } 'organization' { Get-GitHubSecret -Owner $Owner | Where-Object { $_.Name -like "$secretName*" } | Remove-GitHubSecret -Confirm:$false - $repo = Get-GitHubRepository -Owner $Owner -Name $repoName - $repo2 = Get-GitHubRepository -Owner $Owner -Name "$repoName-2" - $repo3 = Get-GitHubRepository -Owner $Owner -Name "$repoName-3" - if (-not $repo -or -not $repo2 -or -not $repo3) { - throw "One or more shared test repositories ('$repoName', '$repoName-2', '$repoName-3') not found for owner '$Owner'. Ensure BeforeAll.ps1 provisioned them." - } + $repo = Initialize-SharedTestRepository -Owner $Owner -OwnerType 'organization' -Name $repoName + $extras = Initialize-SharedTestRepositoryExtras -Owner $Owner -BaseName $repoName + $repo2 = $extras[0] + $repo3 = $extras[1] LogGroup "Org secret - [$orgSecretName]" { $params = @{ Owner = $owner diff --git a/tests/Variables.Tests.ps1 b/tests/Variables.Tests.ps1 index 5aebd1fb5..cd5f1f236 100644 --- a/tests/Variables.Tests.ps1 +++ b/tests/Variables.Tests.ps1 @@ -26,6 +26,7 @@ BeforeAll { if (-not $id) { throw 'GITHUB_RUN_ID is not set. Variables tests refuse to run without a scoped run ID to avoid deleting variables from other runs.' } + . "$PSScriptRoot/Data/SharedTestRepositories.ps1" } Describe 'Variables' { @@ -52,19 +53,16 @@ Describe 'Variables' { switch ($OwnerType) { 'user' { - $repo = Get-GitHubRepository -Name $repoName - if (-not $repo) { - throw "Shared test repository '$repoName' was not found. Ensure BeforeAll.ps1 provisioned it." - } + # Declarative get-or-create so partial reruns (issue #590) can rebuild + # the shared repository if AfterAll already tore it down. + $repo = Initialize-SharedTestRepository -Owner $Owner -OwnerType 'user' -Name $repoName } 'organization' { Get-GitHubVariable -Owner $Owner | Where-Object { $_.Name -like "$variableName*" } | Remove-GitHubVariable -Confirm:$false - $repo = Get-GitHubRepository -Owner $Owner -Name $repoName - $repo2 = Get-GitHubRepository -Owner $Owner -Name "$repoName-2" - $repo3 = Get-GitHubRepository -Owner $Owner -Name "$repoName-3" - if (-not $repo -or -not $repo2 -or -not $repo3) { - throw "One or more shared test repositories ('$repoName', '$repoName-2', '$repoName-3') not found for owner '$Owner'. Ensure BeforeAll.ps1 provisioned them." - } + $repo = Initialize-SharedTestRepository -Owner $Owner -OwnerType 'organization' -Name $repoName + $extras = Initialize-SharedTestRepositoryExtras -Owner $Owner -BaseName $repoName + $repo2 = $extras[0] + $repo3 = $extras[1] LogGroup "Org variable - [$orgVariableName]" { $params = @{ Owner = $owner From 99a47f36e23b86f4a741b24e5d81cd70712bb2a1 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sat, 2 May 2026 14:57:35 +0200 Subject: [PATCH 04/12] Rewrite SharedTestRepositories docs in idiomatic comment-based help --- tests/Data/SharedTestRepositories.ps1 | 99 +++++++++++++++++---------- 1 file changed, 61 insertions(+), 38 deletions(-) diff --git a/tests/Data/SharedTestRepositories.ps1 b/tests/Data/SharedTestRepositories.ps1 index a18847f60..f0e81cc3b 100644 --- a/tests/Data/SharedTestRepositories.ps1 +++ b/tests/Data/SharedTestRepositories.ps1 @@ -1,59 +1,70 @@ -# Idempotent get-or-create helpers for the shared run-scoped test repositories. -# -# Background -# ---------- -# `tests/BeforeAll.ps1` provisions one repository per (OS, TokenType, RunID) for the -# happy-path workflow run. Several *.Tests.ps1 files then read that repository via -# their per-context `BeforeAll`. Before this helper existed, each test file simply -# threw if the repository was missing, which broke the GitHub Actions -# **Re-run failed jobs** path: `AfterAll-ModuleLocal` deletes the shared repository -# at the end of every attempt, and a partial rerun does not re-execute the -# successful `BeforeAll-ModuleLocal` job, so the leaf jobs landed on a non-existent -# repository (issue #590). -# -# These helpers move ownership of "ensure the shared repository exists" from a -# fragile precondition-check into a declarative get-or-create that any leaf job -# can call. On the happy path the repositories already exist (created by -# `BeforeAll.ps1`) and the helpers are a single `Get-GitHubRepository` call. On a -# partial rerun where the repositories were torn down, the helpers recreate them -# transparently so the leaf job can proceed. -# -# Functions defined here: -# - Initialize-SharedTestRepository primary `Test-{OS}-{TokenType}-{RunID}` repository (with readme/license/gitignore for release tests) -# - Initialize-SharedTestRepositoryExtras org-only `-2` and `-3` companion repositories used by Secrets/Variables tests +<# + .SYNOPSIS + Idempotent get-or-create helpers for the shared run-scoped test repositories. + + .DESCRIPTION + The integration test suite uses a small set of GitHub repositories whose names are + scoped to the current `GITHUB_RUN_ID` and shared across the leaf jobs in the + `Test-ModuleLocal` matrix. Any leaf job that depends on these repositories calls + the helpers in this file from its `BeforeAll` block to ensure the repositories + are present before the tests execute. + + The helpers are declarative: they fetch the repository if it already exists and + create it if it does not. This keeps test setup independent of the order in which + jobs run, makes individual tests safe to execute in isolation, and lets the + suite recover when shared infrastructure is missing for any reason. + + Naming and scope are owned by the caller. The helpers do not list, rename, or + delete repositories, and they do not widen ownership beyond the names they are + given. + + .NOTES + Functions: + - Initialize-SharedTestRepository Primary `Test-{OS}-{TokenType}-{RunID}` repository, initialized with a readme, license, and gitignore so a default branch with content is available. + - Initialize-SharedTestRepositoryExtras Companion `-2` and `-3` repositories used by organization-scoped Secrets/Variables `SelectedRepository` tests. +#> function Initialize-SharedTestRepository { + <# + .SYNOPSIS + Returns the named shared test repository, creating it if it does not exist. + + .DESCRIPTION + Looks up the repository by name in the given owner scope and returns it if + found. If it is not found, creates it with a readme, MIT license, and + VisualStudio gitignore so release-related tests have a default branch with + content to operate on. If creation races with a parallel caller, the + repository is re-fetched and returned. + #> [CmdletBinding()] [OutputType([object])] param( + # Login of the user or organization that owns the repository. [Parameter(Mandatory)] [string] $Owner, + # Whether $Owner is a user account or an organization. Determines which + # Get-/New-GitHubRepository parameter set is used. [Parameter(Mandatory)] [ValidateSet('user', 'organization')] [string] $OwnerType, + # Repository name within the owner scope. [Parameter(Mandatory)] [string] $Name ) $repo = switch ($OwnerType) { - 'user' { - Get-GitHubRepository -Name $Name -ErrorAction SilentlyContinue - } - 'organization' { - Get-GitHubRepository -Owner $Owner -Name $Name -ErrorAction SilentlyContinue - } + 'user' { Get-GitHubRepository -Name $Name -ErrorAction SilentlyContinue } + 'organization' { Get-GitHubRepository -Owner $Owner -Name $Name -ErrorAction SilentlyContinue } } if ($repo) { return $repo } - Write-Host "Shared test repository '$Name' not found for owner '$Owner' ($OwnerType). Creating it now (self-heal path for partial reruns / issue #590)." + Write-Host "Provisioning shared test repository '$Owner/$Name' ($OwnerType)." - # The primary shared repository is initialized with readme/license/gitignore so - # release tests have a default branch with content available for tag operations. $createParams = @{ Name = $Name AddReadme = $true @@ -67,9 +78,9 @@ function Initialize-SharedTestRepository { 'organization' { New-GitHubRepository @createParams -Organization $Owner } } } catch { - # Another leaf job in the same matrix may have created the repository - # between our Get and our New. Re-fetch to recover from that race. - Write-Host "Create attempt for '$Name' failed ($($_.Exception.Message)). Re-fetching in case a parallel job created it." + # A parallel caller may have created the repository between the lookup above + # and this create call. Re-fetch and treat the existing repository as success. + Write-Host "Create attempt for '$Name' failed ($($_.Exception.Message)). Re-fetching in case a parallel caller created it." $repo = switch ($OwnerType) { 'user' { Get-GitHubRepository -Name $Name -ErrorAction SilentlyContinue } 'organization' { Get-GitHubRepository -Owner $Owner -Name $Name -ErrorAction SilentlyContinue } @@ -83,27 +94,39 @@ function Initialize-SharedTestRepository { } function Initialize-SharedTestRepositoryExtras { + <# + .SYNOPSIS + Returns the `-2` and `-3` companion repositories for an organization-scoped base name, creating any that are missing. + + .DESCRIPTION + Some Secrets and Variables tests exercise `SelectedRepositories` visibility, + which requires more than one repository in the same organization. This helper + ensures the two companion repositories exist alongside the primary shared + repository and returns them in `-2`, `-3` order. + #> [CmdletBinding()] [OutputType([object[]])] param( + # Organization that owns the companion repositories. [Parameter(Mandatory)] [string] $Owner, + # Name of the primary shared repository. Companions are derived as + # "$BaseName-2" and "$BaseName-3". [Parameter(Mandatory)] [string] $BaseName ) - # Extras are only used for organization-scoped Secrets/Variables SelectedRepository tests. $extras = @() foreach ($suffix in 2, 3) { $extraName = "$BaseName-$suffix" $extra = Get-GitHubRepository -Owner $Owner -Name $extraName -ErrorAction SilentlyContinue if (-not $extra) { - Write-Host "Shared extra test repository '$extraName' not found for owner '$Owner'. Creating it now (self-heal path for partial reruns / issue #590)." + Write-Host "Provisioning shared test repository '$Owner/$extraName'." try { $extra = New-GitHubRepository -Organization $Owner -Name $extraName } catch { - Write-Host "Create attempt for '$extraName' failed ($($_.Exception.Message)). Re-fetching in case a parallel job created it." + Write-Host "Create attempt for '$extraName' failed ($($_.Exception.Message)). Re-fetching in case a parallel caller created it." $extra = Get-GitHubRepository -Owner $Owner -Name $extraName -ErrorAction SilentlyContinue if (-not $extra) { throw From 94bd43f25e64a2f5322259cc6247000632d68d9e Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sat, 2 May 2026 15:04:04 +0200 Subject: [PATCH 05/12] Replace custom helper with idempotent Set-GitHubRepository calls --- tests/Actions.Tests.ps1 | 14 ++- tests/BeforeAll.ps1 | 20 ++-- tests/Data/SharedTestRepositories.ps1 | 140 -------------------------- tests/Environments.Tests.ps1 | 14 ++- tests/Releases.Tests.ps1 | 14 ++- tests/Secrets.Tests.ps1 | 12 +-- tests/Variables.Tests.ps1 | 12 +-- 7 files changed, 52 insertions(+), 174 deletions(-) delete mode 100644 tests/Data/SharedTestRepositories.ps1 diff --git a/tests/Actions.Tests.ps1 b/tests/Actions.Tests.ps1 index 96126d448..3bd3223db 100644 --- a/tests/Actions.Tests.ps1 +++ b/tests/Actions.Tests.ps1 @@ -22,7 +22,6 @@ BeforeAll { if (-not $id) { throw 'GITHUB_RUN_ID is required for Actions tests because it is used to build repository-scoped names for OIDC operations.' } - . "$PSScriptRoot/Data/SharedTestRepositories.ps1" } Describe 'Actions' { @@ -62,9 +61,16 @@ Describe 'Actions' { if ($OwnerType -in ('repository', 'enterprise')) { $repo = $null } else { - # Declarative get-or-create so partial reruns (issue #590) can rebuild - # the shared repository if AfterAll already tore it down. - $repo = Initialize-SharedTestRepository -Owner $Owner -OwnerType $OwnerType -Name $repoName + $repoParams = @{ + Name = $repoName + AddReadme = $true + License = 'mit' + Gitignore = 'VisualStudio' + } + $repo = switch ($OwnerType) { + 'user' { Set-GitHubRepository @repoParams } + 'organization' { Set-GitHubRepository @repoParams -Organization $Owner } + } Write-Host ($repo | Select-Object * | Out-String) } } diff --git a/tests/BeforeAll.ps1 b/tests/BeforeAll.ps1 index 229795f59..d1f247999 100644 --- a/tests/BeforeAll.ps1 +++ b/tests/BeforeAll.ps1 @@ -3,7 +3,6 @@ param() LogGroup 'BeforeAll - Global Test Setup' { $authCases = . "$PSScriptRoot/Data/AuthCases.ps1" - . "$PSScriptRoot/Data/SharedTestRepositories.ps1" $id = $env:GITHUB_RUN_ID if (-not $id) { throw 'GITHUB_RUN_ID environment variable is not set. Refusing to create or clean up test repositories with a non-deterministic name.' @@ -68,15 +67,24 @@ LogGroup 'BeforeAll - Global Test Setup' { } } - # Provision the primary shared repository via the same idempotent helper that - # leaf jobs use, so the happy-path BeforeAll and the partial-rerun self-heal - # path (issue #590) follow the same code. - Initialize-SharedTestRepository -Owner $Owner -OwnerType $OwnerType -Name $repoName | Out-Null + # Provision the primary shared repository. + $repoParams = @{ + Name = $repoName + AddReadme = $true + License = 'mit' + Gitignore = 'VisualStudio' + } + switch ($OwnerType) { + 'user' { Set-GitHubRepository @repoParams } + 'organization' { Set-GitHubRepository @repoParams -Organization $Owner } + } # Provision extra repositories needed by Secrets/Variables SelectedRepository tests. # Only organization owners need them — those tests are skipped for user owners. if ($OwnerType -eq 'organization') { - Initialize-SharedTestRepositoryExtras -Owner $Owner -BaseName $repoName | Out-Null + foreach ($suffix in 2, 3) { + Set-GitHubRepository -Organization $Owner -Name "$repoName-$suffix" + } } } } diff --git a/tests/Data/SharedTestRepositories.ps1 b/tests/Data/SharedTestRepositories.ps1 deleted file mode 100644 index f0e81cc3b..000000000 --- a/tests/Data/SharedTestRepositories.ps1 +++ /dev/null @@ -1,140 +0,0 @@ -<# - .SYNOPSIS - Idempotent get-or-create helpers for the shared run-scoped test repositories. - - .DESCRIPTION - The integration test suite uses a small set of GitHub repositories whose names are - scoped to the current `GITHUB_RUN_ID` and shared across the leaf jobs in the - `Test-ModuleLocal` matrix. Any leaf job that depends on these repositories calls - the helpers in this file from its `BeforeAll` block to ensure the repositories - are present before the tests execute. - - The helpers are declarative: they fetch the repository if it already exists and - create it if it does not. This keeps test setup independent of the order in which - jobs run, makes individual tests safe to execute in isolation, and lets the - suite recover when shared infrastructure is missing for any reason. - - Naming and scope are owned by the caller. The helpers do not list, rename, or - delete repositories, and they do not widen ownership beyond the names they are - given. - - .NOTES - Functions: - - Initialize-SharedTestRepository Primary `Test-{OS}-{TokenType}-{RunID}` repository, initialized with a readme, license, and gitignore so a default branch with content is available. - - Initialize-SharedTestRepositoryExtras Companion `-2` and `-3` repositories used by organization-scoped Secrets/Variables `SelectedRepository` tests. -#> - -function Initialize-SharedTestRepository { - <# - .SYNOPSIS - Returns the named shared test repository, creating it if it does not exist. - - .DESCRIPTION - Looks up the repository by name in the given owner scope and returns it if - found. If it is not found, creates it with a readme, MIT license, and - VisualStudio gitignore so release-related tests have a default branch with - content to operate on. If creation races with a parallel caller, the - repository is re-fetched and returned. - #> - [CmdletBinding()] - [OutputType([object])] - param( - # Login of the user or organization that owns the repository. - [Parameter(Mandatory)] - [string] $Owner, - - # Whether $Owner is a user account or an organization. Determines which - # Get-/New-GitHubRepository parameter set is used. - [Parameter(Mandatory)] - [ValidateSet('user', 'organization')] - [string] $OwnerType, - - # Repository name within the owner scope. - [Parameter(Mandatory)] - [string] $Name - ) - - $repo = switch ($OwnerType) { - 'user' { Get-GitHubRepository -Name $Name -ErrorAction SilentlyContinue } - 'organization' { Get-GitHubRepository -Owner $Owner -Name $Name -ErrorAction SilentlyContinue } - } - - if ($repo) { - return $repo - } - - Write-Host "Provisioning shared test repository '$Owner/$Name' ($OwnerType)." - - $createParams = @{ - Name = $Name - AddReadme = $true - License = 'mit' - Gitignore = 'VisualStudio' - } - - try { - $repo = switch ($OwnerType) { - 'user' { New-GitHubRepository @createParams } - 'organization' { New-GitHubRepository @createParams -Organization $Owner } - } - } catch { - # A parallel caller may have created the repository between the lookup above - # and this create call. Re-fetch and treat the existing repository as success. - Write-Host "Create attempt for '$Name' failed ($($_.Exception.Message)). Re-fetching in case a parallel caller created it." - $repo = switch ($OwnerType) { - 'user' { Get-GitHubRepository -Name $Name -ErrorAction SilentlyContinue } - 'organization' { Get-GitHubRepository -Owner $Owner -Name $Name -ErrorAction SilentlyContinue } - } - if (-not $repo) { - throw - } - } - - return $repo -} - -function Initialize-SharedTestRepositoryExtras { - <# - .SYNOPSIS - Returns the `-2` and `-3` companion repositories for an organization-scoped base name, creating any that are missing. - - .DESCRIPTION - Some Secrets and Variables tests exercise `SelectedRepositories` visibility, - which requires more than one repository in the same organization. This helper - ensures the two companion repositories exist alongside the primary shared - repository and returns them in `-2`, `-3` order. - #> - [CmdletBinding()] - [OutputType([object[]])] - param( - # Organization that owns the companion repositories. - [Parameter(Mandatory)] - [string] $Owner, - - # Name of the primary shared repository. Companions are derived as - # "$BaseName-2" and "$BaseName-3". - [Parameter(Mandatory)] - [string] $BaseName - ) - - $extras = @() - foreach ($suffix in 2, 3) { - $extraName = "$BaseName-$suffix" - $extra = Get-GitHubRepository -Owner $Owner -Name $extraName -ErrorAction SilentlyContinue - if (-not $extra) { - Write-Host "Provisioning shared test repository '$Owner/$extraName'." - try { - $extra = New-GitHubRepository -Organization $Owner -Name $extraName - } catch { - Write-Host "Create attempt for '$extraName' failed ($($_.Exception.Message)). Re-fetching in case a parallel caller created it." - $extra = Get-GitHubRepository -Owner $Owner -Name $extraName -ErrorAction SilentlyContinue - if (-not $extra) { - throw - } - } - } - $extras += $extra - } - - return $extras -} diff --git a/tests/Environments.Tests.ps1 b/tests/Environments.Tests.ps1 index 788b0ac09..8735581ec 100644 --- a/tests/Environments.Tests.ps1 +++ b/tests/Environments.Tests.ps1 @@ -26,7 +26,6 @@ BeforeAll { if (-not $id) { throw 'GITHUB_RUN_ID is required for Environments tests.' } - . "$PSScriptRoot/Data/SharedTestRepositories.ps1" } Describe 'Environments' { @@ -52,9 +51,16 @@ Describe 'Environments' { if ($OwnerType -in ('repository', 'enterprise')) { $repo = $null } else { - # Declarative get-or-create so partial reruns (issue #590) can rebuild - # the shared repository if AfterAll already tore it down. - $repo = Initialize-SharedTestRepository -Owner $Owner -OwnerType $OwnerType -Name $repoName + $repoParams = @{ + Name = $repoName + AddReadme = $true + License = 'mit' + Gitignore = 'VisualStudio' + } + $repo = switch ($OwnerType) { + 'user' { Set-GitHubRepository @repoParams } + 'organization' { Set-GitHubRepository @repoParams -Organization $Owner } + } } Write-Host ($repo | Select-Object * | Out-String) } diff --git a/tests/Releases.Tests.ps1 b/tests/Releases.Tests.ps1 index e3d05d129..38cf8cbd1 100644 --- a/tests/Releases.Tests.ps1 +++ b/tests/Releases.Tests.ps1 @@ -26,7 +26,6 @@ BeforeAll { if (-not $id) { throw 'GITHUB_RUN_ID must be set for run-scoped release tests.' } - . "$PSScriptRoot/Data/SharedTestRepositories.ps1" } Describe 'Releases' { @@ -51,9 +50,16 @@ Describe 'Releases' { if ($OwnerType -in ('repository', 'enterprise')) { $repo = $null } else { - # Declarative get-or-create so partial reruns (issue #590) can rebuild - # the shared repository if AfterAll already tore it down. - $repo = Initialize-SharedTestRepository -Owner $Owner -OwnerType $OwnerType -Name $repoName + $repoParams = @{ + Name = $repoName + AddReadme = $true + License = 'mit' + Gitignore = 'VisualStudio' + } + $repo = switch ($OwnerType) { + 'user' { Set-GitHubRepository @repoParams } + 'organization' { Set-GitHubRepository @repoParams -Organization $Owner } + } } Write-Host ($repo | Select-Object * | Out-String) } diff --git a/tests/Secrets.Tests.ps1 b/tests/Secrets.Tests.ps1 index f0e8b9931..4c0c53b10 100644 --- a/tests/Secrets.Tests.ps1 +++ b/tests/Secrets.Tests.ps1 @@ -26,7 +26,6 @@ BeforeAll { if (-not $id) { throw 'GITHUB_RUN_ID is required for Secrets tests because secret cleanup uses run-scoped wildcard names.' } - . "$PSScriptRoot/Data/SharedTestRepositories.ps1" } Describe 'Secrets' { @@ -53,16 +52,13 @@ Describe 'Secrets' { switch ($OwnerType) { 'user' { - # Declarative get-or-create so partial reruns (issue #590) can rebuild - # the shared repository if AfterAll already tore it down. - $repo = Initialize-SharedTestRepository -Owner $Owner -OwnerType 'user' -Name $repoName + $repo = Set-GitHubRepository -Name $repoName -AddReadme -License 'mit' -Gitignore 'VisualStudio' } 'organization' { Get-GitHubSecret -Owner $Owner | Where-Object { $_.Name -like "$secretName*" } | Remove-GitHubSecret -Confirm:$false - $repo = Initialize-SharedTestRepository -Owner $Owner -OwnerType 'organization' -Name $repoName - $extras = Initialize-SharedTestRepositoryExtras -Owner $Owner -BaseName $repoName - $repo2 = $extras[0] - $repo3 = $extras[1] + $repo = Set-GitHubRepository -Organization $Owner -Name $repoName -AddReadme -License 'mit' -Gitignore 'VisualStudio' + $repo2 = Set-GitHubRepository -Organization $Owner -Name "$repoName-2" + $repo3 = Set-GitHubRepository -Organization $Owner -Name "$repoName-3" LogGroup "Org secret - [$orgSecretName]" { $params = @{ Owner = $owner diff --git a/tests/Variables.Tests.ps1 b/tests/Variables.Tests.ps1 index cd5f1f236..2b9cd1aab 100644 --- a/tests/Variables.Tests.ps1 +++ b/tests/Variables.Tests.ps1 @@ -26,7 +26,6 @@ BeforeAll { if (-not $id) { throw 'GITHUB_RUN_ID is not set. Variables tests refuse to run without a scoped run ID to avoid deleting variables from other runs.' } - . "$PSScriptRoot/Data/SharedTestRepositories.ps1" } Describe 'Variables' { @@ -53,16 +52,13 @@ Describe 'Variables' { switch ($OwnerType) { 'user' { - # Declarative get-or-create so partial reruns (issue #590) can rebuild - # the shared repository if AfterAll already tore it down. - $repo = Initialize-SharedTestRepository -Owner $Owner -OwnerType 'user' -Name $repoName + $repo = Set-GitHubRepository -Name $repoName -AddReadme -License 'mit' -Gitignore 'VisualStudio' } 'organization' { Get-GitHubVariable -Owner $Owner | Where-Object { $_.Name -like "$variableName*" } | Remove-GitHubVariable -Confirm:$false - $repo = Initialize-SharedTestRepository -Owner $Owner -OwnerType 'organization' -Name $repoName - $extras = Initialize-SharedTestRepositoryExtras -Owner $Owner -BaseName $repoName - $repo2 = $extras[0] - $repo3 = $extras[1] + $repo = Set-GitHubRepository -Organization $Owner -Name $repoName -AddReadme -License 'mit' -Gitignore 'VisualStudio' + $repo2 = Set-GitHubRepository -Organization $Owner -Name "$repoName-2" + $repo3 = Set-GitHubRepository -Organization $Owner -Name "$repoName-3" LogGroup "Org variable - [$orgVariableName]" { $params = @{ Owner = $owner From 11262caef2ad1e0c295e18706a8fa341e2082e08 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sat, 2 May 2026 15:08:31 +0200 Subject: [PATCH 06/12] Update tests README to document idempotent Set-GitHubRepository pattern --- tests/README.md | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/tests/README.md b/tests/README.md index a095b5bdd..7d14ccbc8 100644 --- a/tests/README.md +++ b/tests/README.md @@ -78,13 +78,15 @@ Runs once before all parallel test files. For each auth case (except `GITHUB_TOK 1. Connects using the auth case credentials 2. Removes any existing repositories for the deterministic names used by the run (`Test-{OS}-{TokenType}-{GITHUB_RUN_ID}` and, where applicable, the `-2`/`-3` variants) -3. Creates a primary shared repository per OS: `Test-{OS}-{TokenType}-{GITHUB_RUN_ID}` - - Includes `AddReadme`, `License` (`mit`), and `Gitignore` (VisualStudio) for release tests - - For `user` owners: `New-GitHubRepository -Name $repoName` - - For `organization` owners: `New-GitHubRepository -Organization $Owner -Name $repoName` -4. For `organization` owners only, creates two extra repositories per OS (`-2`, `-3` suffix) for Secrets/Variables `SelectedRepository` tests +3. Provisions a primary shared repository per OS using `Set-GitHubRepository`: `Test-{OS}-{TokenType}-{GITHUB_RUN_ID}` + - Includes `-AddReadme`, `-License 'mit'`, and `-Gitignore 'VisualStudio'` so release tests have a default branch with content + - For `user` owners: `Set-GitHubRepository -Name $repoName ...` + - For `organization` owners: `Set-GitHubRepository -Organization $Owner -Name $repoName ...` +4. For `organization` owners only, provisions two extra repositories per OS (`-2`, `-3` suffix) for Secrets/Variables `SelectedRepository` tests - These extras are not created for `user` owners because `SelectedRepository` contexts are skipped for user-owned cases +`Set-GitHubRepository` is idempotent — it returns the existing repository if it already exists, or creates it if it does not. This makes the global setup safe to re-run for the same `GITHUB_RUN_ID`. + ### `AfterAll.ps1` — global teardown Runs once after all parallel test files complete. For each auth case (except `GITHUB_TOKEN`): @@ -114,10 +116,19 @@ Describe 'TestName' { $context = Connect-GitHubApp @connectAppParams -PassThru -Default -Silent } - # Reference the shared repo (NOT New-GitHubRepository) + # Ensure the shared repo exists (idempotent — creates only if missing) $repoPrefix = "Test-$os-$TokenType" $repoName = "$repoPrefix-$id" - $repo = Get-GitHubRepository -Owner $Owner -Name $repoName + $repoParams = @{ + Name = $repoName + AddReadme = $true + License = 'mit' + Gitignore = 'VisualStudio' + } + $repo = switch ($OwnerType) { + 'user' { Set-GitHubRepository @repoParams } + 'organization' { Set-GitHubRepository @repoParams -Organization $Owner } + } } AfterAll { @@ -135,7 +146,11 @@ Describe 'TestName' { - **`$id = $env:GITHUB_RUN_ID`** — not `[guid]::NewGuid()` or `Get-Random`. This makes the repo name deterministic per workflow run so shared infrastructure can be referenced by name. -- **`Get-GitHubRepository`** — test files fetch the shared repo, they do not create repos. +- **`Set-GitHubRepository`** — test files ensure the shared repo exists using `Set-GitHubRepository`, which is + idempotent (returns an existing repo or creates one). This means each test file is self-sufficient: it works both + on the happy path (repo already provisioned by `BeforeAll.ps1`) and on partial reruns where the infrastructure was + torn down between attempts. **Do not** use `Get-GitHubRepository` with a throw guard — that breaks partial reruns. + **Do not** use `New-GitHubRepository` — that fails if the repo already exists. - **`-Skip:($OwnerType -in ('repository', 'enterprise'))`** — standard skip condition for repo-dependent tests. - **`Disconnect-GitHubAccount`** — every context disconnects all sessions in `AfterAll`. - Test-specific ephemeral resources (releases, secrets, variables, environments, teams) are still created and cleaned up From 77ace246300c108519d80fa8cd261a7b60a6869a Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sat, 2 May 2026 15:14:40 +0200 Subject: [PATCH 07/12] Add Copilot instructions for test file conventions --- .github/instructions/tests.instructions.md | 105 +++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 .github/instructions/tests.instructions.md diff --git a/.github/instructions/tests.instructions.md b/.github/instructions/tests.instructions.md new file mode 100644 index 000000000..ed3da06a0 --- /dev/null +++ b/.github/instructions/tests.instructions.md @@ -0,0 +1,105 @@ +--- +description: "Use when writing, editing, or reviewing Pester test files under the tests/ folder. Covers shared repository setup, auth case iteration, naming conventions, and skip patterns for the GitHub module integration tests." +applyTo: "tests/**" +--- +# Integration Test Conventions + +## Shared test repositories + +Each test file that depends on a GitHub repository must ensure it exists using `Set-GitHubRepository` +in its per-context `BeforeAll`. `Set-GitHubRepository` is idempotent — it returns the existing repository +if it already exists, or creates it if it does not. This makes every test file self-sufficient regardless +of whether the global `BeforeAll.ps1` already provisioned the repository. + +**Do not** use `Get-GitHubRepository` with a throw guard — that breaks partial reruns. +**Do not** use `New-GitHubRepository` — that fails if the repository already exists. + +Primary repositories use `-AddReadme`, `-License 'mit'`, and `-Gitignore 'VisualStudio'` so that +a default branch with content is available for tests that need commits (e.g., releases, tags). + +```powershell +$repoPrefix = "Test-$os-$TokenType" +$repoName = "$repoPrefix-$id" +$repoParams = @{ + Name = $repoName + AddReadme = $true + License = 'mit' + Gitignore = 'VisualStudio' +} +$repo = switch ($OwnerType) { + 'user' { Set-GitHubRepository @repoParams } + 'organization' { Set-GitHubRepository @repoParams -Organization $Owner } +} +``` + +For organization-scoped tests that need companion repositories (Secrets/Variables `SelectedRepository`), +provision `-2` and `-3` variants the same way: + +```powershell +$repo2 = Set-GitHubRepository -Organization $Owner -Name "$repoName-2" +$repo3 = Set-GitHubRepository -Organization $Owner -Name "$repoName-3" +``` + +## Test file structure + +```powershell +BeforeAll { + $testName = 'TestName' + $os = $env:RUNNER_OS + $id = $env:GITHUB_RUN_ID +} + +Describe 'TestName' { + $authCases = . "$PSScriptRoot/Data/AuthCases.ps1" + + Context 'As using on ' -ForEach $authCases { + BeforeAll { + $context = Connect-GitHubAccount @connectParams -PassThru -Silent + if ($AuthType -eq 'APP') { + $context = Connect-GitHubApp @connectAppParams -PassThru -Default -Silent + } + + $repoPrefix = "Test-$os-$TokenType" + $repoName = "$repoPrefix-$id" + $repoParams = @{ + Name = $repoName + AddReadme = $true + License = 'mit' + Gitignore = 'VisualStudio' + } + $repo = switch ($OwnerType) { + 'user' { Set-GitHubRepository @repoParams } + 'organization' { Set-GitHubRepository @repoParams -Organization $Owner } + } + } + + AfterAll { + Get-GitHubContext -ListAvailable | Disconnect-GitHubAccount -Silent + } + + It 'Should do something' -Skip:($OwnerType -in ('repository', 'enterprise')) { + # Test logic using $repo, $Owner, $repoName + } + } +} +``` + +## Naming conventions + +| Resource | Pattern | Example | +|------------|----------------------------------------------|--------------------------------| +| Repo | `Test-{OS}-{TokenType}-{RunID}` | `Test-Linux-USER_FG_PAT-1234` | +| Extra repo | `Test-{OS}-{TokenType}-{RunID}-{N}` | `Test-Linux-USER_FG_PAT-1234-2`| +| Secret | `{TestName}_{OS}_{TokenType}_{RunID}` | `Secrets_Linux_PAT_1234` | +| Variable | `{TestName}_{OS}_{TokenType}_{RunID}` | `Variables_Linux_PAT_1234` | +| Team | `{TestName}_{OS}_{TokenType}_{RunID}_{Name}` | `Teams_Linux_APP_ORG_1234_Pull`| +| Env | `{TestName}-{OS}-{TokenType}-{RunID}` | `Secrets-Linux-PAT-1234` | + +## Key rules + +- `$id` must always be `$env:GITHUB_RUN_ID` — never `[guid]::NewGuid()` or `Get-Random`. +- Skip repo-dependent tests with `-Skip:($OwnerType -in ('repository', 'enterprise'))`. +- Disconnect all sessions in `AfterAll`: `Get-GitHubContext -ListAvailable | Disconnect-GitHubAccount -Silent`. +- Test-specific ephemeral resources (releases, secrets, variables, environments, teams) are created and + cleaned up within each test file. Only repositories are shared. +- `Repositories.Tests.ps1` is the exception — it creates and deletes its own repos because it tests CRUD. From 181615b3d30dc5369d889448f981de29a2b13c3a Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sat, 2 May 2026 15:15:58 +0200 Subject: [PATCH 08/12] Consolidate tests README into Copilot instructions as single source of truth --- .github/instructions/tests.instructions.md | 93 +++++++++++- tests/README.md | 169 --------------------- 2 files changed, 85 insertions(+), 177 deletions(-) delete mode 100644 tests/README.md diff --git a/.github/instructions/tests.instructions.md b/.github/instructions/tests.instructions.md index ed3da06a0..2c05d172e 100644 --- a/.github/instructions/tests.instructions.md +++ b/.github/instructions/tests.instructions.md @@ -4,6 +4,83 @@ applyTo: "tests/**" --- # Integration Test Conventions +## Test infrastructure accounts + +### User + +Login: `psmodule-user` +Owner of: + +- [psmodule-user](https://github.com/psmodule-user) (standalone org) +- [psmodule-test-org2](https://github.com/orgs/psmodule-test-org2) (standalone org) + +Secrets: + +- `TEST_USER_PAT` → `psmodule-user` (user) +- `TEST_USER_USER_FG_PAT` → `psmodule-user` (user) +- `TEST_USER_ORG_FG_PAT` → `psmodule-test-org2` (org) + +### APP_ENT — PSModule Enterprise App + +Homed in `MSX`. ClientID: `Iv23lieHcDQDwVV3alK1`. +Installed on [psmodule-test-org3](https://github.com/orgs/psmodule-test-org3) (enterprise org) with all permissions and push events. + +Secrets: `TEST_APP_ENT_CLIENT_ID`, `TEST_APP_ENT_PRIVATE_KEY` + +### APP_ORG — PSModule Organization App + +Homed in `PSModule`. ClientID: `Iv23liYDnEbKlS9IVzHf`. +Installed on [psmodule-test-org](https://github.com/orgs/psmodule-test-org) (standalone org) with all permissions and push events. + +Secrets: `TEST_APP_ORG_CLIENT_ID`, `TEST_APP_ORG_PRIVATE_KEY` + +## Auth cases + +`AuthCases.ps1` defines 7 auth cases. Each test file iterates over all cases, skipping those +that don't apply (e.g., `repository` and `enterprise` owner types skip repo-dependent tests). + +| # | AuthType | TokenType | Owner | OwnerType | +|---|----------|---------------|--------------------|--------------| +| 1 | PAT | USER_FG_PAT | psmodule-user | user | +| 2 | PAT | ORG_FG_PAT | psmodule-test-org2 | organization | +| 3 | PAT | PAT | psmodule-user | user | +| 4 | IAT | GITHUB_TOKEN | PSModule | repository | +| 5 | App | APP_ORG | psmodule-test-org | organization | +| 6 | App | APP_ENT | psmodule-test-org3 | organization | +| 7 | App | APP_ENT | msx | enterprise | + +Cases 4 (`repository`) and 7 (`enterprise`) skip repo creation. Cases 1 and 3 share the same user owner +(`psmodule-user`) but have different `$TokenType` values, so repo names are unique. + +## Setup and teardown + +Shared test infrastructure is provisioned once per workflow run using `BeforeAll.ps1` and torn down using `AfterAll.ps1`. +For generic guidance on setup/teardown scripts, see the +[Process-PSModule documentation](https://github.com/PSModule/Process-PSModule#setup-and-teardown-scripts). + +### `BeforeAll.ps1` — global setup + +Runs once before all parallel test files. For each auth case (except `GITHUB_TOKEN`): + +1. Connects using the auth case credentials +2. Removes any existing repositories for the deterministic names used by the run +3. Provisions a primary shared repository per OS using `Set-GitHubRepository`: `Test-{OS}-{TokenType}-{GITHUB_RUN_ID}` + - Includes `-AddReadme`, `-License 'mit'`, and `-Gitignore 'VisualStudio'` so release tests have a default branch with content + - For `user` owners: `Set-GitHubRepository -Name $repoName ...` + - For `organization` owners: `Set-GitHubRepository -Organization $Owner -Name $repoName ...` +4. For `organization` owners only, provisions two extra repositories per OS (`-2`, `-3` suffix) for + Secrets/Variables `SelectedRepository` tests + +`Set-GitHubRepository` is idempotent — it returns the existing repository if it already exists, or creates +it if it does not. This makes the global setup safe to re-run for the same `GITHUB_RUN_ID`. + +### `AfterAll.ps1` — global teardown + +Runs once after all parallel test files complete. For each auth case (except `GITHUB_TOKEN`): + +1. Connects using the auth case credentials +2. Removes the run-scoped repositories by their known names + ## Shared test repositories Each test file that depends on a GitHub repository must ensure it exists using `Set-GitHubRepository` @@ -86,14 +163,14 @@ Describe 'TestName' { ## Naming conventions -| Resource | Pattern | Example | -|------------|----------------------------------------------|--------------------------------| -| Repo | `Test-{OS}-{TokenType}-{RunID}` | `Test-Linux-USER_FG_PAT-1234` | -| Extra repo | `Test-{OS}-{TokenType}-{RunID}-{N}` | `Test-Linux-USER_FG_PAT-1234-2`| -| Secret | `{TestName}_{OS}_{TokenType}_{RunID}` | `Secrets_Linux_PAT_1234` | -| Variable | `{TestName}_{OS}_{TokenType}_{RunID}` | `Variables_Linux_PAT_1234` | -| Team | `{TestName}_{OS}_{TokenType}_{RunID}_{Name}` | `Teams_Linux_APP_ORG_1234_Pull`| -| Env | `{TestName}-{OS}-{TokenType}-{RunID}` | `Secrets-Linux-PAT-1234` | +| Resource | Pattern | Example | +|------------|----------------------------------------------|----------------------------------| +| Repo | `Test-{OS}-{TokenType}-{RunID}` | `Test-Linux-USER_FG_PAT-1234` | +| Extra repo | `Test-{OS}-{TokenType}-{RunID}-{N}` | `Test-Linux-USER_FG_PAT-1234-2` | +| Secret | `{TestName}_{OS}_{TokenType}_{RunID}` | `Secrets_Linux_PAT_1234` | +| Variable | `{TestName}_{OS}_{TokenType}_{RunID}` | `Variables_Linux_PAT_1234` | +| Team | `{TestName}_{OS}_{TokenType}_{RunID}_{Name}` | `Teams_Linux_APP_ORG_1234_Pull` | +| Env | `{TestName}-{OS}-{TokenType}-{RunID}` | `Secrets-Linux-PAT-1234` | ## Key rules diff --git a/tests/README.md b/tests/README.md deleted file mode 100644 index 7d14ccbc8..000000000 --- a/tests/README.md +++ /dev/null @@ -1,169 +0,0 @@ -# Users and apps used for the testing framework - -## User - -Login: 'psmodule-user' -Owner of: - -- [psmodule-user](https://github.com/psmodule-user) (standalone org) -- [psmodule-test-org2](https://github.com/orgs/psmodule-test-org2) (standalone org) - -Secrets: - -- TEST_USER_PAT -> 'psmodule-user' (user) -- TEST_USER_USER_FG_PAT -> 'psmodule-user' (user) -- TEST_USER_ORG_FG_PAT -> 'psmodule-test-org2' (org) - -## APP_ENT - PSModule Enterprise App - -Homed in 'MSX' -ClientID: 'Iv23lieHcDQDwVV3alK1' -Installed on: - -- [psmodule-test-org3](https://github.com/orgs/psmodule-test-org3) (enterprise org) -Permissions: -- All -Events: -- Push - -Secrets: - -- TEST_APP_ENT_CLIENT_ID -- TEST_APP_ENT_PRIVATE_KEY - -## APP_ORG - PSModule Organization App - -Homed in PSModule -ClientID: 'Iv23liYDnEbKlS9IVzHf' -Installed on: - -- [psmodule-test-org](https://github.com/orgs/psmodule-test-org) (standalone org) -Permissions: -- All -Events: -- Push - -Secrets: - -- TEST_APP_ORG_CLIENT_ID -- TEST_APP_ORG_PRIVATE_KEY - -## Auth cases - -[AuthCases.ps1](../tests/Data/AuthCases.ps1) defines 7 auth cases. Each test file iterates over all cases, skipping those -that don't apply (e.g., `repository` and `enterprise` owner types skip repo-dependent tests). - -| # | AuthType | TokenType | Owner | OwnerType | -|---|----------|---------------|--------------------|--------------| -| 1 | PAT | USER_FG_PAT | psmodule-user | user | -| 2 | PAT | ORG_FG_PAT | psmodule-test-org2 | organization | -| 3 | PAT | PAT | psmodule-user | user | -| 4 | IAT | GITHUB_TOKEN | PSModule | repository | -| 5 | App | APP_ORG | psmodule-test-org | organization | -| 6 | App | APP_ENT | psmodule-test-org3 | organization | -| 7 | App | APP_ENT | msx | enterprise | - -Cases 4 (`repository`) and 7 (`enterprise`) skip repo creation. Cases 1 and 3 share the same user owner (`psmodule-user`) -but have different `$TokenType` values, so repo names are unique. - -## Setup and teardown - -Shared test infrastructure is provisioned once per workflow run using `BeforeAll.ps1` and torn down using `AfterAll.ps1`. -For generic guidance on setup/teardown scripts, see the -[Process-PSModule documentation](https://github.com/PSModule/Process-PSModule#setup-and-teardown-scripts). - -### `BeforeAll.ps1` — global setup - -Runs once before all parallel test files. For each auth case (except `GITHUB_TOKEN`): - -1. Connects using the auth case credentials -2. Removes any existing repositories for the deterministic names used by the run (`Test-{OS}-{TokenType}-{GITHUB_RUN_ID}` and, where applicable, the `-2`/`-3` variants) -3. Provisions a primary shared repository per OS using `Set-GitHubRepository`: `Test-{OS}-{TokenType}-{GITHUB_RUN_ID}` - - Includes `-AddReadme`, `-License 'mit'`, and `-Gitignore 'VisualStudio'` so release tests have a default branch with content - - For `user` owners: `Set-GitHubRepository -Name $repoName ...` - - For `organization` owners: `Set-GitHubRepository -Organization $Owner -Name $repoName ...` -4. For `organization` owners only, provisions two extra repositories per OS (`-2`, `-3` suffix) for Secrets/Variables `SelectedRepository` tests - - These extras are not created for `user` owners because `SelectedRepository` contexts are skipped for user-owned cases - -`Set-GitHubRepository` is idempotent — it returns the existing repository if it already exists, or creates it if it does not. This makes the global setup safe to re-run for the same `GITHUB_RUN_ID`. - -### `AfterAll.ps1` — global teardown - -Runs once after all parallel test files complete. For each auth case (except `GITHUB_TOKEN`): - -1. Connects using the auth case credentials -2. Removes the run-scoped repositories created by setup using their known names (`Test-{OS}-{TokenType}-{GITHUB_RUN_ID}` and, where applicable, the `-2`/`-3` variants) - -## Test file pattern - -Each test file follows this pattern: - -```powershell -BeforeAll { - $testName = 'TestName' - $os = $env:RUNNER_OS - $id = $env:GITHUB_RUN_ID -} - -Describe 'TestName' { - $authCases = . "$PSScriptRoot/Data/AuthCases.ps1" - - Context 'As using on ' -ForEach $authCases { - BeforeAll { - # Connect - $context = Connect-GitHubAccount @connectParams -PassThru -Silent - if ($AuthType -eq 'APP') { - $context = Connect-GitHubApp @connectAppParams -PassThru -Default -Silent - } - - # Ensure the shared repo exists (idempotent — creates only if missing) - $repoPrefix = "Test-$os-$TokenType" - $repoName = "$repoPrefix-$id" - $repoParams = @{ - Name = $repoName - AddReadme = $true - License = 'mit' - Gitignore = 'VisualStudio' - } - $repo = switch ($OwnerType) { - 'user' { Set-GitHubRepository @repoParams } - 'organization' { Set-GitHubRepository @repoParams -Organization $Owner } - } - } - - AfterAll { - Get-GitHubContext -ListAvailable | Disconnect-GitHubAccount -Silent - } - - It 'Should do something' -Skip:($OwnerType -in ('repository', 'enterprise')) { - # Test logic using $repo, $Owner, $repoName - } - } -} -``` - -### Key conventions - -- **`$id = $env:GITHUB_RUN_ID`** — not `[guid]::NewGuid()` or `Get-Random`. This makes the repo name deterministic - per workflow run so shared infrastructure can be referenced by name. -- **`Set-GitHubRepository`** — test files ensure the shared repo exists using `Set-GitHubRepository`, which is - idempotent (returns an existing repo or creates one). This means each test file is self-sufficient: it works both - on the happy path (repo already provisioned by `BeforeAll.ps1`) and on partial reruns where the infrastructure was - torn down between attempts. **Do not** use `Get-GitHubRepository` with a throw guard — that breaks partial reruns. - **Do not** use `New-GitHubRepository` — that fails if the repo already exists. -- **`-Skip:($OwnerType -in ('repository', 'enterprise'))`** — standard skip condition for repo-dependent tests. -- **`Disconnect-GitHubAccount`** — every context disconnects all sessions in `AfterAll`. -- Test-specific ephemeral resources (releases, secrets, variables, environments, teams) are still created and cleaned up - within each test file. Only repositories are shared. -- **Exception:** `Repositories.Tests.ps1` creates and deletes its own repos because it tests repository CRUD operations. - -## Naming conventions - -| Resource | Pattern | Example | -|------------|----------------------------------------------|----------------------------------| -| Repo | `Test-{OS}-{TokenType}-{RunID}` | `Test-Linux-USER_FG_PAT-1234` | -| Extra repo | `Test-{OS}-{TokenType}-{RunID}-{N}` | `Test-Linux-USER_FG_PAT-1234-2` | -| Secret | `{TestName}_{OS}_{TokenType}_{RunID}` | `Secrets_Linux_PAT_1234` | -| Variable | `{TestName}_{OS}_{TokenType}_{RunID}` | `Variables_Linux_PAT_1234` | -| Team | `{TestName}_{OS}_{TokenType}_{RunID}_{Name}` | `Teams_Linux_APP_ORG_1234_Pull` | -| Env | `{TestName}-{OS}-{TokenType}-{RunID}` | `Secrets-Linux-PAT-1234` | From a971b941090972b3ad184b1558e41a2736dca01d Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sat, 2 May 2026 15:23:41 +0200 Subject: [PATCH 09/12] Align TEMPLATE.ps1 with self-healing repo provisioning pattern --- tests/TEMPLATE.ps1 | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/tests/TEMPLATE.ps1 b/tests/TEMPLATE.ps1 index 9af0f57b2..c37530b1a 100644 --- a/tests/TEMPLATE.ps1 +++ b/tests/TEMPLATE.ps1 @@ -41,10 +41,19 @@ Describe 'Template' { } } - # Reference the shared repo (NOT New-GitHubRepository) + # Ensure the shared test repository exists. Set-GitHubRepository is idempotent. $repoPrefix = "Test-$os-$TokenType" $repoName = "$repoPrefix-$id" - $repo = Get-GitHubRepository -Owner $Owner -Name $repoName + $repoParams = @{ + Name = $repoName + AddReadme = $true + License = 'mit' + Gitignore = 'VisualStudio' + } + $repo = switch ($OwnerType) { + 'user' { Set-GitHubRepository @repoParams } + 'organization' { Set-GitHubRepository @repoParams -Organization $Owner } + } } AfterAll { Get-GitHubContext -ListAvailable | Disconnect-GitHubAccount -Silent From f1c850f1014fed1c116ab184c285491ed32859d6 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sat, 2 May 2026 15:34:35 +0200 Subject: [PATCH 10/12] Fix documentation accuracy: clarify Set-GitHubRepository issues PATCH on existing repos --- .github/instructions/tests.instructions.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/.github/instructions/tests.instructions.md b/.github/instructions/tests.instructions.md index 2c05d172e..3d3b5a529 100644 --- a/.github/instructions/tests.instructions.md +++ b/.github/instructions/tests.instructions.md @@ -71,8 +71,11 @@ Runs once before all parallel test files. For each auth case (except `GITHUB_TOK 4. For `organization` owners only, provisions two extra repositories per OS (`-2`, `-3` suffix) for Secrets/Variables `SelectedRepository` tests -`Set-GitHubRepository` is idempotent — it returns the existing repository if it already exists, or creates -it if it does not. This makes the global setup safe to re-run for the same `GITHUB_RUN_ID`. +`Set-GitHubRepository` is idempotent — if the repository already exists it updates it in place (issuing a +PATCH), and if it does not exist it creates it. Because the same parameters are passed each time, the +end-state is identical regardless of how many times the setup runs. The extra PATCH on the happy path is +a deliberate trade-off for simplicity: one call handles both first-run and partial-rerun scenarios without +branching logic. ### `AfterAll.ps1` — global teardown @@ -84,9 +87,10 @@ Runs once after all parallel test files complete. For each auth case (except `GI ## Shared test repositories Each test file that depends on a GitHub repository must ensure it exists using `Set-GitHubRepository` -in its per-context `BeforeAll`. `Set-GitHubRepository` is idempotent — it returns the existing repository -if it already exists, or creates it if it does not. This makes every test file self-sufficient regardless -of whether the global `BeforeAll.ps1` already provisioned the repository. +in its per-context `BeforeAll`. `Set-GitHubRepository` is idempotent — if the repository already exists +it updates it in place (PATCH), and if it does not exist it creates it. When the same parameters are +passed each time the end-state is identical. This makes every test file self-sufficient regardless of +whether the global `BeforeAll.ps1` already provisioned the repository. **Do not** use `Get-GitHubRepository` with a throw guard — that breaks partial reruns. **Do not** use `New-GitHubRepository` — that fails if the repository already exists. From c601697cb5f79ddbd70b019879dac72de793f9ae Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sat, 2 May 2026 19:03:14 +0200 Subject: [PATCH 11/12] Add repository/enterprise owner type guard to TEMPLATE and instructions docs TEMPLATE.ps1 and both code snippets in tests.instructions.md were missing the if-guard for OwnerType values 'repository' and 'enterprise' that all actual test files already use. Without it the template's switch falls through silently, returning without making it explicit, and the instructions guide readers toward a pattern that omits the guard. --- .github/instructions/tests.instructions.md | 48 ++++++++++++++-------- tests/TEMPLATE.ps1 | 22 ++++++---- 2 files changed, 43 insertions(+), 27 deletions(-) diff --git a/.github/instructions/tests.instructions.md b/.github/instructions/tests.instructions.md index 3d3b5a529..4b087383c 100644 --- a/.github/instructions/tests.instructions.md +++ b/.github/instructions/tests.instructions.md @@ -98,18 +98,26 @@ whether the global `BeforeAll.ps1` already provisioned the repository. Primary repositories use `-AddReadme`, `-License 'mit'`, and `-Gitignore 'VisualStudio'` so that a default branch with content is available for tests that need commits (e.g., releases, tags). +Some auth cases (e.g., `repository`, `enterprise`) do not operate on a user- or org-owned repository. +Skip provisioning for those owner types and set `$repo = $null` so that repo-dependent tests can +be skipped cleanly: + ```powershell $repoPrefix = "Test-$os-$TokenType" $repoName = "$repoPrefix-$id" -$repoParams = @{ - Name = $repoName - AddReadme = $true - License = 'mit' - Gitignore = 'VisualStudio' -} -$repo = switch ($OwnerType) { - 'user' { Set-GitHubRepository @repoParams } - 'organization' { Set-GitHubRepository @repoParams -Organization $Owner } +if ($OwnerType -in ('repository', 'enterprise')) { + $repo = $null +} else { + $repoParams = @{ + Name = $repoName + AddReadme = $true + License = 'mit' + Gitignore = 'VisualStudio' + } + $repo = switch ($OwnerType) { + 'user' { Set-GitHubRepository @repoParams } + 'organization' { Set-GitHubRepository @repoParams -Organization $Owner } + } } ``` @@ -142,15 +150,19 @@ Describe 'TestName' { $repoPrefix = "Test-$os-$TokenType" $repoName = "$repoPrefix-$id" - $repoParams = @{ - Name = $repoName - AddReadme = $true - License = 'mit' - Gitignore = 'VisualStudio' - } - $repo = switch ($OwnerType) { - 'user' { Set-GitHubRepository @repoParams } - 'organization' { Set-GitHubRepository @repoParams -Organization $Owner } + if ($OwnerType -in ('repository', 'enterprise')) { + $repo = $null + } else { + $repoParams = @{ + Name = $repoName + AddReadme = $true + License = 'mit' + Gitignore = 'VisualStudio' + } + $repo = switch ($OwnerType) { + 'user' { Set-GitHubRepository @repoParams } + 'organization' { Set-GitHubRepository @repoParams -Organization $Owner } + } } } diff --git a/tests/TEMPLATE.ps1 b/tests/TEMPLATE.ps1 index c37530b1a..afc9a6114 100644 --- a/tests/TEMPLATE.ps1 +++ b/tests/TEMPLATE.ps1 @@ -44,15 +44,19 @@ Describe 'Template' { # Ensure the shared test repository exists. Set-GitHubRepository is idempotent. $repoPrefix = "Test-$os-$TokenType" $repoName = "$repoPrefix-$id" - $repoParams = @{ - Name = $repoName - AddReadme = $true - License = 'mit' - Gitignore = 'VisualStudio' - } - $repo = switch ($OwnerType) { - 'user' { Set-GitHubRepository @repoParams } - 'organization' { Set-GitHubRepository @repoParams -Organization $Owner } + if ($OwnerType -in ('repository', 'enterprise')) { + $repo = $null + } else { + $repoParams = @{ + Name = $repoName + AddReadme = $true + License = 'mit' + Gitignore = 'VisualStudio' + } + $repo = switch ($OwnerType) { + 'user' { Set-GitHubRepository @repoParams } + 'organization' { Set-GitHubRepository @repoParams -Organization $Owner } + } } } AfterAll { From 0968d3f8e7a92bc685ce42e8c71b65f206da39c8 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sat, 2 May 2026 20:36:11 +0200 Subject: [PATCH 12/12] Add repository/enterprise owner type guard to Secrets and Variables tests; restore tests/README.md as redirect Secrets.Tests.ps1 and Variables.Tests.ps1 were missing the if (\ -in ('repository','enterprise')) guard that all other test files already have. Without it, \/\/\ were left unset for those auth cases, risking stale-value reuse. Re-add tests/README.md as a minimal pointer to the canonical .github/instructions/tests.instructions.md so contributors browsing tests/ still find the documentation entry point. --- tests/README.md | 4 ++++ tests/Secrets.Tests.ps1 | 44 ++++++++++++++++++++++----------------- tests/Variables.Tests.ps1 | 42 +++++++++++++++++++++---------------- 3 files changed, 53 insertions(+), 37 deletions(-) create mode 100644 tests/README.md diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 000000000..8f71eafdf --- /dev/null +++ b/tests/README.md @@ -0,0 +1,4 @@ +# Tests + +Test infrastructure documentation has moved to +[`.github/instructions/tests.instructions.md`](../.github/instructions/tests.instructions.md). diff --git a/tests/Secrets.Tests.ps1 b/tests/Secrets.Tests.ps1 index 4c0c53b10..fee918fd2 100644 --- a/tests/Secrets.Tests.ps1 +++ b/tests/Secrets.Tests.ps1 @@ -50,26 +50,32 @@ Describe 'Secrets' { $orgSecretName = "$secretName`_ORG" $environmentName = "$testName-$os-$TokenType-$id" - switch ($OwnerType) { - 'user' { - $repo = Set-GitHubRepository -Name $repoName -AddReadme -License 'mit' -Gitignore 'VisualStudio' - } - 'organization' { - Get-GitHubSecret -Owner $Owner | Where-Object { $_.Name -like "$secretName*" } | Remove-GitHubSecret -Confirm:$false - $repo = Set-GitHubRepository -Organization $Owner -Name $repoName -AddReadme -License 'mit' -Gitignore 'VisualStudio' - $repo2 = Set-GitHubRepository -Organization $Owner -Name "$repoName-2" - $repo3 = Set-GitHubRepository -Organization $Owner -Name "$repoName-3" - LogGroup "Org secret - [$orgSecretName]" { - $params = @{ - Owner = $owner - Name = $orgSecretName - Value = 'organization' - Visibility = 'selected' - SelectedRepositories = $repo.id - } + if ($OwnerType -in ('repository', 'enterprise')) { + $repo = $null + $repo2 = $null + $repo3 = $null + } else { + switch ($OwnerType) { + 'user' { + $repo = Set-GitHubRepository -Name $repoName -AddReadme -License 'mit' -Gitignore 'VisualStudio' + } + 'organization' { + Get-GitHubSecret -Owner $Owner | Where-Object { $_.Name -like "$secretName*" } | Remove-GitHubSecret -Confirm:$false + $repo = Set-GitHubRepository -Organization $Owner -Name $repoName -AddReadme -License 'mit' -Gitignore 'VisualStudio' + $repo2 = Set-GitHubRepository -Organization $Owner -Name "$repoName-2" + $repo3 = Set-GitHubRepository -Organization $Owner -Name "$repoName-3" + LogGroup "Org secret - [$orgSecretName]" { + $params = @{ + Owner = $owner + Name = $orgSecretName + Value = 'organization' + Visibility = 'selected' + SelectedRepositories = $repo.id + } - $orgSecret += Set-GitHubSecret @params -Debug - Write-Host ($orgSecret | Select-Object * | Out-String) + $orgSecret += Set-GitHubSecret @params -Debug + Write-Host ($orgSecret | Select-Object * | Out-String) + } } } } diff --git a/tests/Variables.Tests.ps1 b/tests/Variables.Tests.ps1 index 2b9cd1aab..904320d51 100644 --- a/tests/Variables.Tests.ps1 +++ b/tests/Variables.Tests.ps1 @@ -50,25 +50,31 @@ Describe 'Variables' { $orgVariableName = "$variableName`_ORG" $environmentName = "$testName-$os-$TokenType-$id" - switch ($OwnerType) { - 'user' { - $repo = Set-GitHubRepository -Name $repoName -AddReadme -License 'mit' -Gitignore 'VisualStudio' - } - 'organization' { - Get-GitHubVariable -Owner $Owner | Where-Object { $_.Name -like "$variableName*" } | Remove-GitHubVariable -Confirm:$false - $repo = Set-GitHubRepository -Organization $Owner -Name $repoName -AddReadme -License 'mit' -Gitignore 'VisualStudio' - $repo2 = Set-GitHubRepository -Organization $Owner -Name "$repoName-2" - $repo3 = Set-GitHubRepository -Organization $Owner -Name "$repoName-3" - LogGroup "Org variable - [$orgVariableName]" { - $params = @{ - Owner = $owner - Name = $orgVariableName - Value = 'organization' - Visibility = 'selected' - SelectedRepositories = $repo.id + if ($OwnerType -in ('repository', 'enterprise')) { + $repo = $null + $repo2 = $null + $repo3 = $null + } else { + switch ($OwnerType) { + 'user' { + $repo = Set-GitHubRepository -Name $repoName -AddReadme -License 'mit' -Gitignore 'VisualStudio' + } + 'organization' { + Get-GitHubVariable -Owner $Owner | Where-Object { $_.Name -like "$variableName*" } | Remove-GitHubVariable -Confirm:$false + $repo = Set-GitHubRepository -Organization $Owner -Name $repoName -AddReadme -License 'mit' -Gitignore 'VisualStudio' + $repo2 = Set-GitHubRepository -Organization $Owner -Name "$repoName-2" + $repo3 = Set-GitHubRepository -Organization $Owner -Name "$repoName-3" + LogGroup "Org variable - [$orgVariableName]" { + $params = @{ + Owner = $owner + Name = $orgVariableName + Value = 'organization' + Visibility = 'selected' + SelectedRepositories = $repo.id + } + $orgVariable = Set-GitHubVariable @params -Debug + Write-Host ($orgVariable | Select-Object * | Out-String) } - $orgVariable = Set-GitHubVariable @params -Debug - Write-Host ($orgVariable | Select-Object * | Out-String) } } }