functions/github/Invoke-GitHubRestMethod.Tests.ps1
$here = Split-Path -Parent $MyInvocation.MyCommand.Path $sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path) -replace '\.Tests\.', '.' . "$here\$sut" Describe "Invoke-GitHubRestMethod" { $mockSplat = @{ uri = "https://api.github.com/repos/does/notexist" verb = "GET" body = @{} token = "MOCK_TOKEN" } Context "When invoking with valid parameters" { Mock Invoke-RestMethod { return @{ StatusCode = 200 } } It "Should return a successful response" { $result = Invoke-GitHubRestMethod @mockSplat Assert-MockCalled Invoke-RestMethod -Exactly 1 $result | Should -Not -BeNullOrEmpty $result.StatusCode | Should -Be 200 } } Context "When encountering an error status code" { Mock Invoke-RestMethod { $errorDetails = '{"code": 1, "message": "BadRequest", "more_info": "", "status": 400}' $statusCode = 400 $response = New-Object System.Net.Http.HttpResponseMessage $statusCode $exception = New-Object Microsoft.PowerShell.Commands.HttpResponseException "$statusCode ($($response.ReasonPhrase))", $response $errorCategory = [System.Management.Automation.ErrorCategory]::InvalidOperation $errorID = 'WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeWebRequestCommand' $targetObject = $null $errorRecord = New-Object Management.Automation.ErrorRecord $exception, $errorID, $errorCategory, $targetObject $errorRecord.ErrorDetails = $errorDetails throw $errorRecord } It "Should throw an exception" { { Invoke-GitHubRestMethod @mockSplat } | Should -Throw } } Context "When encountering an ignored error status code" { Mock Invoke-RestMethod { $errorDetails = '{"code": 1, "message": "BadRequest", "more_info": "", "status": 400}' $statusCode = 400 $response = New-Object System.Net.Http.HttpResponseMessage $statusCode $exception = New-Object Microsoft.PowerShell.Commands.HttpResponseException "$statusCode ($($response.ReasonPhrase))", $response $errorCategory = [System.Management.Automation.ErrorCategory]::InvalidOperation $errorID = 'WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeWebRequestCommand' $targetObject = $null $errorRecord = New-Object Management.Automation.ErrorRecord $exception, $errorID, $errorCategory, $targetObject $errorRecord.ErrorDetails = $errorDetails throw $errorRecord } It "Should not throw an exception" { { Invoke-GitHubRestMethod @mockSplat -HttpErrorStatusCodesToIgnore @(400) } | Should -Not -Throw } It "Should return null" { $result = Invoke-GitHubRestMethod @mockSplat -HttpErrorStatusCodesToIgnore @(400) $result | Should -BeNullOrEmpty } } Context "When rate limit is exceeded and given a 'Retry-After' response header" { $script:pesterHasRetried = $false Mock Invoke-RestMethod { try { if (!$pesterHasRetried) { $errorDetails = '{"code": 1, "message": "BadRequest", "more_info": "", "status": 429}' $statusCode = 429 $response = New-Object System.Net.Http.HttpResponseMessage $statusCode $response.Headers.Add('Retry-After', '1') $exception = New-Object Microsoft.PowerShell.Commands.HttpResponseException "$statusCode ($($response.ReasonPhrase))", $response $errorCategory = [System.Management.Automation.ErrorCategory]::InvalidOperation $errorID = 'WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeWebRequestCommand' $targetObject = $null $errorRecord = New-Object Management.Automation.ErrorRecord $exception, $errorID, $errorCategory, $targetObject $errorRecord.ErrorDetails = $errorDetails throw $errorRecord } else { return @{ StatusCode = 200 } } } finally { $script:pesterHasRetried = $true } } It "Should wait for the period specified in the Retry-After header" { # Act $result = Invoke-GitHubRestMethod @mockSplat # Assert $result | Should -Not -BeNullOrEmpty $result.StatusCode | Should -Be 200 Assert-MockCalled Invoke-RestMethod -Exactly 2 } } Context "When a rate limit quota is exhausted" { $script:pesterHasRetried = $false Mock Invoke-RestMethod { try { if (!$pesterHasRetried) { $errorDetails = '{"code": 1, "message": "BadRequest", "more_info": "", "status": 429}' $statusCode = 429 $response = New-Object System.Net.Http.HttpResponseMessage $statusCode $response.Headers.Add('X-RateLimit-Reset', [datetime]::UtcNow.AddSeconds(1).ToFileTimeUtc()) $response.Headers.Add('X-RateLimit-Remaining', "0") $exception = New-Object Microsoft.PowerShell.Commands.HttpResponseException "$statusCode ($($response.ReasonPhrase))", $response $errorCategory = [System.Management.Automation.ErrorCategory]::InvalidOperation $errorID = 'WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeWebRequestCommand' $targetObject = $null $errorRecord = New-Object Management.Automation.ErrorRecord $exception, $errorID, $errorCategory, $targetObject $errorRecord.ErrorDetails = $errorDetails throw $errorRecord } else { return @{ StatusCode = 200 } } } finally { $script:pesterHasRetried = $true } } It "Should wait until the time speciied in the 'X-RateLimit-Reset' response header" { # Act $result = Invoke-GitHubRestMethod @mockSplat # Assert $result | Should -Not -BeNullOrEmpty $result.StatusCode | Should -Be 200 Assert-MockCalled Invoke-RestMethod -Exactly 2 } } Context "When rate limit is exceeded with no retry context" { $script:pesterRetryCount = 0 Mock Invoke-RestMethod { try { if ($pesterRetryCount -lt 3) { $errorDetails = '{"code": 1, "message": "BadRequest", "more_info": "", "status": 429}' $statusCode = 429 $response = New-Object System.Net.Http.HttpResponseMessage $statusCode $exception = New-Object Microsoft.PowerShell.Commands.HttpResponseException "$statusCode ($($response.ReasonPhrase))", $response $errorCategory = [System.Management.Automation.ErrorCategory]::InvalidOperation $errorID = 'WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeWebRequestCommand' $targetObject = $null $errorRecord = New-Object Management.Automation.ErrorRecord $exception, $errorID, $errorCategory, $targetObject $errorRecord.ErrorDetails = $errorDetails throw $errorRecord } else { return @{ StatusCode = 200 } } } finally { $script:pesterRetryCount++ } } It "Should implement an exponential back-off strategy" { # Act $startTime = Get-Date $result = Invoke-GitHubRestMethod @mockSplat -InitialBackOffSeconds 1 # Assert $result | Should -Not -BeNullOrEmpty $result.StatusCode | Should -Be 200 $stopTime = Get-Date $elapsedTime = $stopTime - $startTime $elapsedTime.TotalSeconds | Should -BeGreaterThan 4 } } } |