Skip to content

Commit b592489

Browse files
Display "N/A" for tokens without expiration metadata (#539)
This release improves how token expiration information is handled and displayed in the GitHub context. The changes ensure more accurate and user-friendly reporting of token expiration properties, enhance formatting in table views, and add comprehensive tests to validate these behaviors. **Token Expiration Calculation & Representation:** * Updated the `TokenExpiresIn` and `RefreshTokenExpiresIn` script properties in `GitHubContext.Types.ps1xml` to only return a value when `TokenExpiresAt` or `RefreshTokenExpiresAt` is present, otherwise return nothing. This avoids returning a zero timespan when the expiration is not set. **Table Formatting & Display:** * Improved the table formatting in `GitHubContext.Format.ps1xml` to: - Display `TokenExpiresAt` as 'N/A' (in gray if supported) when not present, instead of omitting the column. - Show `TokenExpiresIn` as 'N/A' (with similar formatting) when not available, and color the value based on time remaining when present. **Testing Enhancements:** * Added and updated tests in `GitHub.Tests.ps1` and `Apps.Tests.ps1` to: - Validate both `TokenExpiresAt` and `TokenExpiresIn` properties for correctness, type, and logical values across different authentication types. - Check that installation tokens have the correct `AuthType` and that expiration properties behave as expected for both App and non-App contexts. These changes make token expiration handling more robust, user-friendly, and well-tested throughout the module. ### Test Coverage Summary: | Token Type | AuthType | Has Expiration? | Test Coverage | |------------|----------|-----------------|---------------| | Classic PAT (`ghp`) | PAT | No | ✓ | | Fine-grained PAT (`github_pat`) | PAT | No | ✓ | | GITHUB_TOKEN (in GitHub Actions) | IAT | No | ✓ | | GitHub App JWT | APP | Yes | ✓ | | GitHub App Installation | IAT | Yes | ✓ | #### Token Expiration Behavior: - **PAT tokens** (both classic `ghp` and fine-grained `github_pat`): No expiration metadata → displays "Unknown" - **GITHUB_TOKEN** (IAT in GitHub Actions): No expiration metadata → displays "Unknown" - **GitHub App Installation tokens** (IAT from Connect-GitHubApp): Has expiration metadata → displays time remaining or "Expired" - **UAT tokens** (device flow): Has expiration metadata → displays time remaining or "Expired" - **APP tokens** (JWT): Has expiration metadata → displays time remaining or "Expired" #### Example Output: **Before:** ``` * Name AuthType TokenType TokenExpiresAt TokenExpiresIn - ---- -------- --------- -------------- -------------- > github.com/psmodule-user PAT ghp Expired ``` **After:** ``` * Name AuthType TokenType TokenExpiresAt TokenExpiresIn - ---- -------- --------- -------------- -------------- > github.com/psmodule-user PAT ghp N/A N/A ``` - Fixes #538 --------- 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 a11a390 commit b592489

4 files changed

Lines changed: 86 additions & 43 deletions

File tree

src/formats/GitHubContext.Format.ps1xml

Lines changed: 41 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -49,39 +49,55 @@
4949
<TableColumnItem>
5050
<PropertyName>TokenType</PropertyName>
5151
</TableColumnItem>
52-
<TableColumnItem>
53-
<PropertyName>TokenExpiresAt</PropertyName>
54-
</TableColumnItem>
5552
<TableColumnItem>
5653
<ScriptBlock>
57-
if ($null -eq $_.TokenExpiresIn) {
58-
return
54+
if ($null -ne $_.TokenExpiresAt) {
55+
return $_.TokenExpiresAt
5956
}
6057

61-
if ($_.TokenExpiresIn -le 0) {
62-
$text = 'Expired'
63-
} else {
64-
$text = $_.TokenExpiresIn.ToString('hh\:mm\:ss')
58+
$text = 'N/A'
59+
if ($Host.UI.SupportsVirtualTerminal -and ($env:GITHUB_ACTIONS -ne 'true')) {
60+
$gray = "`e[90m"
61+
$reset = "`e[0m"
62+
return "$gray$text$reset"
6563
}
64+
return $text
65+
</ScriptBlock>
66+
</TableColumnItem>
67+
<TableColumnItem>
68+
<ScriptBlock>
69+
if ($null -ne $_.TokenExpiresIn) {
70+
if ($_.TokenExpiresIn -le 0) {
71+
$text = 'Expired'
72+
} else {
73+
$text = $_.TokenExpiresIn.ToString('hh\:mm\:ss')
74+
}
6675

67-
if ($Host.UI.SupportsVirtualTerminal -and
68-
($env:GITHUB_ACTIONS -ne 'true')) {
69-
switch ($_.AuthType) {
70-
'UAT' {
71-
$maxValue = [TimeSpan]::FromHours(8)
72-
}
73-
'IAT' {
74-
$maxValue = [TimeSpan]::FromHours(1)
75-
}
76-
'APP' {
77-
$maxValue = [TimeSpan]::FromMinutes(10)
76+
if ($Host.UI.SupportsVirtualTerminal -and ($env:GITHUB_ACTIONS -ne 'true')) {
77+
switch ($_.AuthType) {
78+
'UAT' {
79+
$maxValue = [TimeSpan]::FromHours(8)
80+
}
81+
'IAT' {
82+
$maxValue = [TimeSpan]::FromHours(1)
83+
}
84+
'APP' {
85+
$maxValue = [TimeSpan]::FromMinutes(10)
86+
}
87+
}
88+
$ratio = [Math]::Min(($_.TokenExpiresIn / $maxValue), 1)
89+
return [GitHubFormatter]::FormatColorByRatio($ratio, $text)
90+
}
91+
return $text
7892
}
93+
94+
$text = 'N/A'
95+
if ($Host.UI.SupportsVirtualTerminal -and ($env:GITHUB_ACTIONS -ne 'true')) {
96+
$gray = "`e[90m"
97+
$reset = "`e[0m"
98+
return "$gray$text$reset"
7999
}
80-
$ratio = [Math]::Min(($_.TokenExpiresIn / $maxValue), 1)
81-
[GitHubFormatter]::FormatColorByRatio($ratio, $text)
82-
} else {
83-
$text
84-
}
100+
return $text
85101
</ScriptBlock>
86102
</TableColumnItem>
87103
</TableColumnItems>

src/types/GitHubContext.Types.ps1xml

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,17 @@
66
<ScriptProperty>
77
<Name>TokenExpiresIn</Name>
88
<GetScriptBlock>
9-
if ($null -eq $this.TokenExpiresAt) { return [TimeSpan]::Zero }
10-
$this.TokenExpiresAt - [DateTime]::Now
9+
if ($null -ne $this.TokenExpiresAt) {
10+
return $this.TokenExpiresAt - [DateTime]::Now
11+
}
1112
</GetScriptBlock>
1213
</ScriptProperty>
1314
<ScriptProperty>
1415
<Name>RefreshTokenExpiresIn</Name>
1516
<GetScriptBlock>
16-
if ($null -eq $this.RefreshTokenExpiresAt) { return [TimeSpan]::Zero }
17-
$this.RefreshTokenExpiresAt - [DateTime]::Now
17+
if ($null -ne $this.RefreshTokenExpiresAt) {
18+
return $this.RefreshTokenExpiresAt - [DateTime]::Now
19+
}
1820
</GetScriptBlock>
1921
</ScriptProperty>
2022
</Members>
@@ -25,8 +27,9 @@
2527
<ScriptProperty>
2628
<Name>TokenExpiresIn</Name>
2729
<GetScriptBlock>
28-
if ($null -eq $this.TokenExpiresAt) { return [TimeSpan]::Zero }
29-
$this.TokenExpiresAt - [DateTime]::Now
30+
if ($null -ne $this.TokenExpiresAt) {
31+
return $this.TokenExpiresAt - [DateTime]::Now
32+
}
3033
</GetScriptBlock>
3134
</ScriptProperty>
3235
</Members>
@@ -37,8 +40,9 @@
3740
<ScriptProperty>
3841
<Name>TokenExpiresIn</Name>
3942
<GetScriptBlock>
40-
if ($null -eq $this.TokenExpiresAt) { return [TimeSpan]::Zero }
41-
$this.TokenExpiresAt - [DateTime]::Now
43+
if ($null -ne $this.TokenExpiresAt) {
44+
return $this.TokenExpiresAt - [DateTime]::Now
45+
}
4246
</GetScriptBlock>
4347
</ScriptProperty>
4448
</Members>

tests/Apps.Tests.ps1

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ Describe 'Apps' {
230230
LogGroup 'Config' {
231231
Write-Host "$($config | Format-List | Out-String)"
232232
}
233-
LogGroup "Installation" {
233+
LogGroup 'Installation' {
234234
Write-Host "$($installation | Format-List | Out-String)"
235235
}
236236
LogGroup 'Permissions' {
@@ -274,8 +274,11 @@ Describe 'Apps' {
274274
$installationContext.Events | Should -BeOfType 'string'
275275
}
276276

277-
It 'Connect-GitHubApp - TokenExpiresIn property should be calculated correctly' {
277+
It 'Connect-GitHubApp - TokenExpiresAt and TokenExpiresIn properties should be calculated correctly' {
278+
$installationContext.TokenExpiresAt | Should -BeOfType [DateTime]
279+
$installationContext.TokenExpiresAt | Should -BeGreaterThan ([DateTime]::Now)
278280
$installationContext.TokenExpiresIn | Should -BeOfType [TimeSpan]
281+
$installationContext.TokenExpiresIn.TotalSeconds | Should -BeGreaterThan 0
279282
$installationContext.TokenExpiresIn.TotalMinutes | Should -BeGreaterThan 0
280283
$installationContext.TokenExpiresIn.TotalMinutes | Should -BeLessOrEqual 60
281284
}

tests/GitHub.Tests.ps1

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,19 @@ Describe 'Auth' {
2323
$authCases = . "$PSScriptRoot/Data/AuthCases.ps1"
2424

2525
Context 'As <Type> using <Case> on <Target>' -ForEach $authCases {
26+
BeforeAll {
27+
$context = Connect-GitHubAccount @connectParams -PassThru -Silent
28+
LogGroup 'Context' {
29+
Write-Host ($context | Format-List | Out-String)
30+
}
31+
}
32+
2633
AfterAll {
2734
Get-GitHubContext -ListAvailable | Disconnect-GitHubAccount -Silent
2835
Write-Host ('-' * 60)
2936
}
3037

3138
It 'Connect-GitHubAccount - Connects using the provided credentials' {
32-
$context = Connect-GitHubAccount @connectParams -PassThru -Silent
3339
LogGroup 'Context - Standard' {
3440
Write-Host ($context | Out-String)
3541
}
@@ -45,6 +51,22 @@ Describe 'Auth' {
4551
$context | Should -Not -BeNullOrEmpty
4652
}
4753

54+
It 'Connect-GitHubAccount - TokenExpiresAt and TokenExpiresIn validation' {
55+
if ($AuthType -eq 'APP') {
56+
$context.TokenExpiresAt | Should -Not -BeNullOrEmpty
57+
$context.TokenExpiresAt | Should -BeOfType [DateTime]
58+
$context.TokenExpiresAt | Should -BeGreaterThan ([DateTime]::Now)
59+
60+
$context.TokenExpiresIn | Should -Not -BeNullOrEmpty
61+
$context.TokenExpiresIn | Should -BeOfType [TimeSpan]
62+
$context.TokenExpiresIn.TotalMinutes | Should -BeGreaterThan 0
63+
$context.TokenExpiresIn.TotalMinutes | Should -BeLessOrEqual 10
64+
} else {
65+
$context.TokenExpiresAt | Should -BeNullOrEmpty
66+
$context.TokenExpiresIn | Should -BeNullOrEmpty
67+
}
68+
}
69+
4870
It 'Connect-GitHubAccount - Connects using the provided credentials - Double' {
4971
$context = Connect-GitHubAccount @connectParams -PassThru -Silent
5072
$context = Connect-GitHubAccount @connectParams -PassThru -Silent
@@ -80,8 +102,6 @@ Describe 'Auth' {
80102
}
81103
$context | Should -Not -BeNullOrEmpty
82104
$context | Should -BeOfType [GitHubContext]
83-
$context.TokenExpiresAt | Should -BeOfType [DateTime]
84-
$context.TokenExpiresIn | Should -BeOfType [TimeSpan]
85105
}
86106

87107
It 'Connect-GitHubApp - Connects as a GitHub App to <Owner>' -Skip:($AuthType -ne 'APP') {
@@ -109,18 +129,18 @@ Describe 'Auth' {
109129
LogGroup 'Connect-GithubApp' {
110130
$context
111131
}
112-
$context.TokenExpiresAt | Should -BeOfType [DateTime]
113-
$context.TokenExpiresIn | Should -BeOfType [TimeSpan]
114132
LogGroup 'Context' {
115133
Write-Host ($context | Format-List | Out-String)
116134
}
117135
$context | Should -Not -BeNullOrEmpty
118136
}
119137

120-
# Tests for runners goes here
121-
if ($Type -eq 'GitHub Actions') {}
138+
It 'Connect-GitHubApp - Installation tokens (IAT) should have correct auth type' -Skip:($AuthType -ne 'APP') {
139+
$appContextToUse = Get-GitHubContext -ListAvailable | Where-Object { $_.AuthType -eq 'App' } | Select-Object -First 1
140+
$appContext = Connect-GitHubApp @connectAppParams -PassThru -Silent -Context $appContextToUse
141+
$appContext.AuthType | Should -Be 'IAT'
142+
}
122143

123-
# Tests for IAT UAT and PAT goes here
124144
It 'Connect-GitHubAccount - Connects to GitHub CLI on runners' {
125145
[string]::IsNullOrEmpty($(gh auth token)) | Should -Be $false
126146
}

0 commit comments

Comments
 (0)