Skip to content

Commit bf4d7fd

Browse files
⚙️ [Maintenance]: Test infrastructure consolidated with shared repos and run-scoped cleanup (#541)
No changes to the module's public API or behavior. All changes are in `tests/`. - Fixes #534 ## New: `tests/BeforeAll.ps1` and `tests/AfterAll.ps1` Two new global scripts run once before and after all parallel test jobs per workflow run. `BeforeAll.ps1` — for each auth case except `GITHUB_TOKEN`: - Guards against missing `GITHUB_RUN_ID` and `Settings` env vars (prevents unscoped wildcard cleanup from affecting concurrent runs) - Removes any repos matching `Test-{OS}-{TokenType}-{RunID}*` from a previous attempt of the same run (re-run safety) - Creates one primary shared repo per OS: `Test-{OS}-{TokenType}-{RunID}` — includes `AddReadme`, MIT license, and VisualStudio gitignore to satisfy release asset test requirements - Creates two extra repos (`-2`, `-3`) per OS for organization owners only — user owners never enter the `SelectedRepository` test contexts so the extras are not needed `AfterAll.ps1` — for each auth case except `GITHUB_TOKEN`: - Guards against missing `GITHUB_RUN_ID` and `Settings` env vars - Removes all repos matching `Test-{OS}-{TokenType}-{RunID}*` ## New: `tests/README.md` Documents the shared-repo pattern, naming conventions, and the per-file test structure for contributors. ## Modified: test files that previously created their own repositories The following files previously called `New-GitHubRepository` in their per-context `BeforeAll` and removed repos in their `AfterAll`. They now reference the shared repo via `Get-GitHubRepository` and contain no repo lifecycle code. | File | Changes | |---|---| | `Actions.Tests.ps1` | `[guid]::NewGuid()` → `$env:GITHUB_RUN_ID`; `New-GitHubRepository` → `Get-GitHubRepository`; repo cleanup removed from `BeforeAll`/`AfterAll` | | `Environments.Tests.ps1` | Same pattern | | `Releases.Tests.ps1` | Same pattern | | `Secrets.Tests.ps1` | Same pattern; `$repo2`/`$repo3` only fetched for `organization` owners; stale-secret cleanup filter tightened from `$secretPrefix*` to `$secretName*` (run-scoped) | | `Variables.Tests.ps1` | Same as Secrets | ## Modified: test files that keep their own repo lifecycle (CRUD tests) `Repositories.Tests.ps1` — still creates and deletes its own repos because that is what it tests. Change: `[guid]::NewGuid()` → `$env:GITHUB_RUN_ID`; cleanup filter tightened from `$repoPrefix*` to `$repoName*` (run-scoped, prevents collateral deletion across concurrent runs). ## Modified: naming alignment only | File | Change | |---|---| | `Teams.Tests.ps1` | `[guid]::NewGuid()` → `$env:GITHUB_RUN_ID`; cleanup filter tightened from `$teamPrefix*` to `$teamName*` | | `Organizations.Tests.ps1` | `Get-Random` → `$env:GITHUB_RUN_ID`; pre-test cleanup filter tightened from `$orgPrefix*` to `$orgName*` | ## Modified: top-level `BeforeAll` added The following files had no top-level `BeforeAll` block. One was added to each to establish `$testName`, `$os`, and `$id` variables consistently with the rest of the test suite. `Apps.Tests.ps1`, `Artifacts.Tests.ps1`, `Permissions.Tests.ps1` ## Modified: top-level `BeforeAll` added + minor fixes | File | Additional fixes | |---|---| | `Emojis.Tests.ps1` | `Connect-GitHubApp` moved from an inline `if ($AuthType -eq 'APP')` block into the context `BeforeAll`; dead placeholder `if ($Type -eq 'GitHub Actions') {}` removed | | `Enterprise.Tests.ps1` | Placeholder `# DEFAULTS ACROSS ALL TESTS` comment removed; `Describe 'Template'` corrected to `Describe 'Enterprise'` | | `GitHub.Tests.ps1` | `Connect-GitHubApp` in the `Invoke-GitHubAPI` context moved into context `BeforeAll`; inline `if ($AuthType -eq 'APP') { It ... }` converted to `It ... -Skip:($AuthType -ne 'APP')` | | `Users.Tests.ps1` | `if ($OwnerType -eq 'user') { It ... }` converted to `Context 'Authenticated user' -Skip:($OwnerType -ne 'user') { It ... }` | ## Modified: `tests/TEMPLATE.ps1` Rewritten to demonstrate the shared-repo pattern: `Get-GitHubRepository` instead of `New-GitHubRepository`, `$id = $env:GITHUB_RUN_ID`, `-Skip:($OwnerType -in ('repository', 'enterprise'))`. ## Repository creation budget | Source | Creates per OS run | |---|---| | `BeforeAll.ps1` — 3 org cases × 3 repos | 9 | | `BeforeAll.ps1` — 2 user cases × 1 repo | 2 | | `Repositories.Tests` CRUD — 5 cases × ~7 | ~35 | | All other test files | 0 | | **Total** | **~46** | `psmodule-user` owner budget: **16/hr** (was ~20/hr before this PR, now 4 under the secondary rate limit threshold). --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: MariusStorhaug <17722253+MariusStorhaug@users.noreply.github.com> Co-authored-by: Marius Storhaug <marstor@hotmail.com>
1 parent 88154b0 commit bf4d7fd

19 files changed

Lines changed: 484 additions & 220 deletions

tests/Actions.Tests.ps1

Lines changed: 16 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,12 @@
1616
param()
1717

1818
BeforeAll {
19-
$testName = 'ActionsTests'
19+
$testName = 'Actions'
2020
$os = $env:RUNNER_OS
21-
$guid = [guid]::NewGuid().ToString()
21+
$id = $env:GITHUB_RUN_ID
22+
if (-not $id) {
23+
throw 'GITHUB_RUN_ID is required for Actions tests because it is used to build repository-scoped names for OIDC operations.'
24+
}
2225
}
2326

2427
Describe 'Actions' {
@@ -51,37 +54,23 @@ Describe 'Actions' {
5154
Write-Host ($context | Format-List | Out-String)
5255
}
5356
}
54-
$repoPrefix = "$testName-$os-$TokenType"
55-
$repoName = "$repoPrefix-$guid"
57+
$repoPrefix = "Test-$os-$TokenType"
58+
$repoName = "$repoPrefix-$id"
5659

57-
switch ($OwnerType) {
58-
'user' {
59-
Get-GitHubRepository | Where-Object { $_.Name -like "$repoPrefix*" } |
60-
Remove-GitHubRepository -Confirm:$false
61-
$repo = New-GitHubRepository -Name $repoName -Confirm:$false
62-
}
63-
'organization' {
64-
Get-GitHubRepository -Organization $Owner | Where-Object { $_.Name -like "$repoPrefix*" } |
65-
Remove-GitHubRepository -Confirm:$false
66-
$repo = New-GitHubRepository -Organization $Owner -Name $repoName -Confirm:$false
60+
LogGroup "Using Repository - [$repoName]" {
61+
if ($OwnerType -in ('repository', 'enterprise')) {
62+
$repo = $null
63+
} else {
64+
$repo = Get-GitHubRepository -Owner $Owner -Name $repoName
65+
if (-not $repo) {
66+
throw "Shared test repository '$repoName' was not found for owner '$Owner' (OwnerType: '$OwnerType'). Ensure the repository was provisioned and the repository name is correct."
67+
}
68+
Write-Host ($repo | Select-Object * | Out-String)
6769
}
6870
}
69-
LogGroup "Repository - [$repoName]" {
70-
Write-Host ($repo | Select-Object * | Out-String)
71-
}
7271
}
7372

7473
AfterAll {
75-
switch ($OwnerType) {
76-
'user' {
77-
Get-GitHubRepository | Where-Object { $_.Name -like "$repoPrefix*" } |
78-
Remove-GitHubRepository -Confirm:$false
79-
}
80-
'organization' {
81-
Get-GitHubRepository -Organization $Owner | Where-Object { $_.Name -like "$repoPrefix*" } |
82-
Remove-GitHubRepository -Confirm:$false
83-
}
84-
}
8574
Get-GitHubContext -ListAvailable | Disconnect-GitHubAccount -Silent
8675
Write-Host ('-' * 60)
8776
}

tests/AfterAll.ps1

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
[CmdletBinding()]
2+
param()
3+
4+
LogGroup 'AfterAll - Global Test Teardown' {
5+
$authCases = . "$PSScriptRoot/Data/AuthCases.ps1"
6+
7+
$id = $env:GITHUB_RUN_ID
8+
if (-not $id) {
9+
throw 'GITHUB_RUN_ID environment variable is not set. Refusing to clean up test repositories with an unscoped wildcard (would impact concurrent runs).'
10+
}
11+
if (-not $env:Settings) {
12+
throw 'Settings environment variable is not set. Process-PSModule must populate it with the test suite configuration.'
13+
}
14+
$prefix = 'Test'
15+
16+
# Derive the list of OS names from the Settings JSON provided by Process-PSModule.
17+
try {
18+
$settings = $env:Settings | ConvertFrom-Json
19+
} catch {
20+
throw "Settings environment variable contains invalid JSON. Expected TestSuites.Module.OSName to be present. $_"
21+
}
22+
23+
$osNames = @($settings.TestSuites.Module.OSName | Sort-Object -Unique)
24+
if (-not $osNames) {
25+
throw 'Settings JSON must include at least one non-empty TestSuites.Module.OSName value.'
26+
}
27+
$invalidOsNames = @($osNames | Where-Object { -not $_ -or -not $_.ToString().Trim() })
28+
if ($invalidOsNames.Count -gt 0) {
29+
throw 'Settings JSON contains one or more null or empty TestSuites.Module.OSName values.'
30+
}
31+
Write-Host "Cleaning up test repositories for OSes: $($osNames -join ', ')"
32+
33+
foreach ($authCase in $authCases) {
34+
$authCase.GetEnumerator() | ForEach-Object { Set-Variable -Name $_.Key -Value $_.Value }
35+
36+
if ($TokenType -eq 'GITHUB_TOKEN') {
37+
Write-Host "Skipping teardown for $AuthType-$TokenType (uses existing repository)"
38+
continue
39+
}
40+
41+
LogGroup "Teardown - $AuthType-$TokenType" {
42+
$context = Connect-GitHubAccount @connectParams -PassThru -Silent
43+
if ($AuthType -eq 'APP') {
44+
$context = Connect-GitHubApp @connectAppParams -PassThru -Default -Silent
45+
}
46+
Write-Host ($context | Format-List | Out-String)
47+
48+
foreach ($os in $osNames) {
49+
$repoPrefix = "$prefix-$os-$TokenType"
50+
$repoName = "$repoPrefix-$id"
51+
52+
LogGroup "Repository cleanup - $AuthType-$TokenType - $os" {
53+
# Use deterministic name lookups instead of listing all repos to reduce API calls.
54+
$cleanupRepoNames = @($repoName)
55+
if ($OwnerType -eq 'organization') {
56+
$cleanupRepoNames += "$repoName-2", "$repoName-3"
57+
}
58+
59+
foreach ($cleanupRepoName in $cleanupRepoNames) {
60+
switch ($OwnerType) {
61+
'user' {
62+
Get-GitHubRepository -Name $cleanupRepoName -ErrorAction SilentlyContinue |
63+
Remove-GitHubRepository -Confirm:$false
64+
}
65+
'organization' {
66+
Get-GitHubRepository -Owner $Owner -Name $cleanupRepoName -ErrorAction SilentlyContinue |
67+
Remove-GitHubRepository -Confirm:$false
68+
}
69+
}
70+
}
71+
}
72+
}
73+
74+
Get-GitHubContext -ListAvailable | Disconnect-GitHubAccount -Silent
75+
}
76+
}
77+
}

tests/Apps.Tests.ps1

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@
1919
[CmdletBinding()]
2020
param()
2121

22+
BeforeAll {
23+
$testName = 'Apps'
24+
$os = $env:RUNNER_OS
25+
$id = $env:GITHUB_RUN_ID
26+
}
27+
2228
Describe 'Apps' {
2329
$authCases = . "$PSScriptRoot/Data/AuthCases.ps1"
2430

tests/Artifacts.Tests.ps1

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@
1919
[CmdletBinding()]
2020
param()
2121

22+
BeforeAll {
23+
$testName = 'Artifacts'
24+
$os = $env:RUNNER_OS
25+
$id = $env:GITHUB_RUN_ID
26+
}
27+
2228
Describe 'Artifacts' {
2329
$authCases = . "$PSScriptRoot/Data/AuthCases.ps1"
2430

tests/BeforeAll.ps1

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
[CmdletBinding()]
2+
param()
3+
4+
LogGroup 'BeforeAll - Global Test Setup' {
5+
$authCases = . "$PSScriptRoot/Data/AuthCases.ps1"
6+
$id = $env:GITHUB_RUN_ID
7+
if (-not $id) {
8+
throw 'GITHUB_RUN_ID environment variable is not set. Refusing to create or clean up test repositories with a non-deterministic name.'
9+
}
10+
if (-not $env:Settings) {
11+
throw 'Settings environment variable is not set. Process-PSModule must populate it with the test suite configuration.'
12+
}
13+
14+
# Derive the list of OS names from the Settings JSON provided by Process-PSModule.
15+
try {
16+
$settings = $env:Settings | ConvertFrom-Json
17+
} catch {
18+
throw "Settings environment variable does not contain valid JSON. Process-PSModule must populate it with a valid test suite configuration. $_"
19+
}
20+
21+
$osNames = @($settings.TestSuites.Module.OSName | Sort-Object -Unique)
22+
if (-not $osNames) {
23+
throw 'Settings JSON must contain TestSuites.Module.OSName with at least one OS name.'
24+
}
25+
$invalidOsNames = @($osNames | Where-Object { -not $_ -or -not $_.ToString().Trim() })
26+
if ($invalidOsNames.Count -gt 0) {
27+
throw 'Settings JSON contains one or more null or empty values in TestSuites.Module.OSName.'
28+
}
29+
Write-Host "Creating test repositories for OSes: $($osNames -join ', ')"
30+
31+
foreach ($authCase in $authCases) {
32+
$authCase.GetEnumerator() | ForEach-Object { Set-Variable -Name $_.Key -Value $_.Value }
33+
34+
if ($TokenType -eq 'GITHUB_TOKEN') {
35+
Write-Host "Skipping setup for $AuthType-$TokenType (uses existing repository)"
36+
continue
37+
}
38+
39+
$context = Connect-GitHubAccount @connectParams -PassThru -Silent
40+
if ($AuthType -eq 'APP') {
41+
$context = Connect-GitHubApp @connectAppParams -PassThru -Default -Silent
42+
}
43+
Write-Host ($context | Format-List | Out-String)
44+
45+
foreach ($os in $osNames) {
46+
$repoPrefix = "Test-$os-$TokenType"
47+
$repoName = "$repoPrefix-$id"
48+
49+
LogGroup "Repository setup - $AuthType-$TokenType - $os" {
50+
# Clean up repos from a previous attempt of the same run (re-runs).
51+
# Use deterministic name lookups instead of listing all repos to reduce API calls.
52+
$cleanupRepoNames = @($repoName)
53+
if ($OwnerType -eq 'organization') {
54+
$cleanupRepoNames += "$repoName-2", "$repoName-3"
55+
}
56+
57+
foreach ($cleanupRepoName in $cleanupRepoNames) {
58+
switch ($OwnerType) {
59+
'user' {
60+
Get-GitHubRepository -Name $cleanupRepoName -ErrorAction SilentlyContinue |
61+
Remove-GitHubRepository -Confirm:$false
62+
}
63+
'organization' {
64+
Get-GitHubRepository -Owner $Owner -Name $cleanupRepoName -ErrorAction SilentlyContinue |
65+
Remove-GitHubRepository -Confirm:$false
66+
}
67+
}
68+
}
69+
70+
# Create the primary shared repository (with readme, license, gitignore for release tests).
71+
$repoParams = @{
72+
Name = $repoName
73+
AddReadme = $true
74+
License = 'mit'
75+
Gitignore = 'VisualStudio'
76+
}
77+
switch ($OwnerType) {
78+
'user' {
79+
New-GitHubRepository @repoParams
80+
}
81+
'organization' {
82+
New-GitHubRepository @repoParams -Organization $Owner
83+
}
84+
}
85+
86+
# Create extra repositories needed by Secrets/Variables SelectedRepository tests.
87+
# Only organization owners need them — those tests are skipped for user owners.
88+
if ($OwnerType -eq 'organization') {
89+
foreach ($suffix in 2, 3) {
90+
$extraName = "$repoName-$suffix"
91+
New-GitHubRepository -Organization $Owner -Name $extraName
92+
}
93+
}
94+
}
95+
}
96+
97+
Get-GitHubContext -ListAvailable | Disconnect-GitHubAccount -Silent
98+
}
99+
}

tests/Emojis.Tests.ps1

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@
1919
[CmdletBinding()]
2020
param()
2121

22+
BeforeAll {
23+
$testName = 'Emojis'
24+
$os = $env:RUNNER_OS
25+
$id = $env:GITHUB_RUN_ID
26+
}
27+
2228
Describe 'Emojis' {
2329
$authCases = . "$PSScriptRoot/Data/AuthCases.ps1"
2430

@@ -28,26 +34,18 @@ Describe 'Emojis' {
2834
LogGroup 'Context' {
2935
Write-Host ($context | Format-List | Out-String)
3036
}
31-
}
32-
AfterAll {
33-
Get-GitHubContext -ListAvailable | Disconnect-GitHubAccount -Silent
34-
Write-Host ('-' * 60)
35-
}
36-
37-
# Tests for APP goes here
38-
if ($AuthType -eq 'APP') {
39-
It 'Connect-GitHubApp - Connects as a GitHub App to <Owner>' {
37+
if ($AuthType -eq 'APP') {
4038
$context = Connect-GitHubApp @connectAppParams -PassThru -Default -Silent
4139
LogGroup 'Context' {
4240
Write-Host ($context | Format-List | Out-String)
4341
}
4442
}
4543
}
44+
AfterAll {
45+
Get-GitHubContext -ListAvailable | Disconnect-GitHubAccount -Silent
46+
Write-Host ('-' * 60)
47+
}
4648

47-
# Tests for runners goes here
48-
if ($Type -eq 'GitHub Actions') {}
49-
50-
# Tests for IAT UAT and PAT goes here
5149
It 'Get-GitHubEmoji - Gets a list of all emojis' {
5250
$emojis = Get-GitHubEmoji
5351
LogGroup 'emojis' {

tests/Enterprise.Tests.ps1

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,12 @@
2020
param()
2121

2222
BeforeAll {
23-
# DEFAULTS ACROSS ALL TESTS
23+
$testName = 'Enterprise'
24+
$os = $env:RUNNER_OS
25+
$id = $env:GITHUB_RUN_ID
2426
}
2527

26-
Describe 'Template' {
28+
Describe 'Enterprise' {
2729
$authCases = . "$PSScriptRoot/Data/AuthCases.ps1"
2830

2931
Context 'As <Type> using <Case> on <Target>' -ForEach $authCases {

tests/Environments.Tests.ps1

Lines changed: 12 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,12 @@
2020
param()
2121

2222
BeforeAll {
23-
$testName = 'EnvironmentsTests'
23+
$testName = 'Environments'
2424
$os = $env:RUNNER_OS
25-
$guid = [guid]::NewGuid().ToString()
25+
$id = $env:GITHUB_RUN_ID
26+
if (-not $id) {
27+
throw 'GITHUB_RUN_ID is required for Environments tests.'
28+
}
2629
}
2730

2831
Describe 'Environments' {
@@ -40,34 +43,20 @@ Describe 'Environments' {
4043
Write-Host ($context | Format-List | Out-String)
4144
}
4245
}
43-
$repoPrefix = "$testName-$os-$TokenType"
44-
$repoName = "$repoPrefix-$guid"
45-
$environmentName = "$testName-$os-$TokenType-$guid"
46+
$repoPrefix = "Test-$os-$TokenType"
47+
$repoName = "$repoPrefix-$id"
48+
$environmentName = "$testName-$os-$TokenType-$id"
4649

47-
switch ($OwnerType) {
48-
'user' {
49-
Get-GitHubRepository | Where-Object { $_.Name -like "$repoPrefix*" } | Remove-GitHubRepository -Confirm:$false
50-
$repo = New-GitHubRepository -Name $repoName -Confirm:$false
51-
}
52-
'organization' {
53-
Get-GitHubRepository -Organization $Owner | Where-Object { $_.Name -like "$repoPrefix*" } | Remove-GitHubRepository -Confirm:$false
54-
$repo = New-GitHubRepository -Organization $owner -Name $repoName -Confirm:$false
50+
LogGroup "Using Repository - [$repoName]" {
51+
$repo = Get-GitHubRepository -Owner $Owner -Name $repoName
52+
if (($OwnerType -notin ('repository', 'enterprise')) -and (-not $repo)) {
53+
throw "Shared test repository '$repoName' was not found for owner '$Owner'. Ensure the repository was created before running the environment tests."
5554
}
56-
}
57-
LogGroup "Repository - [$repoName]" {
5855
Write-Host ($repo | Select-Object * | Out-String)
5956
}
6057
}
6158

6259
AfterAll {
63-
switch ($OwnerType) {
64-
'user' {
65-
Get-GitHubRepository | Where-Object { $_.Name -like "$repoPrefix*" } | Remove-GitHubRepository -Confirm:$false
66-
}
67-
'organization' {
68-
Get-GitHubRepository -Organization $Owner | Where-Object { $_.Name -like "$repoPrefix*" } | Remove-GitHubRepository -Confirm:$false
69-
}
70-
}
7160
Get-GitHubContext -ListAvailable | Disconnect-GitHubAccount -Silent
7261
Write-Host ('-' * 60)
7362
}

0 commit comments

Comments
 (0)