diff --git a/sentry-api-client/Private/Invoke-SentryApiRequest.ps1 b/sentry-api-client/Private/Invoke-SentryApiRequest.ps1 index 40e3864..61b8a3f 100644 --- a/sentry-api-client/Private/Invoke-SentryApiRequest.ps1 +++ b/sentry-api-client/Private/Invoke-SentryApiRequest.ps1 @@ -13,9 +13,9 @@ function Invoke-SentryApiRequest { ) $RequestParams = @{ - Uri = $Uri - Method = $Method - Headers = $Script:SentryApiConfig.Headers + Uri = $Uri + Method = $Method + Headers = $Script:SentryApiConfig.Headers ContentType = 'application/json' } @@ -25,10 +25,17 @@ function Invoke-SentryApiRequest { try { Write-Debug "Making $Method request to: $Uri" - $Response = Invoke-RestMethod @RequestParams + + # Use Invoke-WebRequest instead of Invoke-RestMethod to get explicit control over JSON parsing + # Invoke-RestMethod silently returns strings when JSON parsing fails (e.g., with empty string keys) + $WebResponse = Invoke-WebRequest @RequestParams + + # Explicitly parse JSON with error handling + # Use -AsHashtable to gracefully handle JSON with empty string keys (common in Sentry API responses) + $Response = $WebResponse.Content | ConvertFrom-Json -AsHashtable + return $Response - } - catch { + } catch { $ErrorMessage = "Sentry API request ($Method $Uri) failed: $($_.Exception.Message)" if ($_.Exception.Response) { $StatusCode = $_.Exception.Response.StatusCode diff --git a/sentry-api-client/Tests/SentryApiClient.Fixtures.Tests.ps1 b/sentry-api-client/Tests/SentryApiClient.Fixtures.Tests.ps1 index efb5c03..49ff1bb 100644 --- a/sentry-api-client/Tests/SentryApiClient.Fixtures.Tests.ps1 +++ b/sentry-api-client/Tests/SentryApiClient.Fixtures.Tests.ps1 @@ -14,23 +14,23 @@ AfterAll { Describe 'SentryApiClient Tests with Real API Response Fixtures' { Context 'Event Operations with Realistic Data' { BeforeAll { - # Mock Invoke-RestMethod to return fixture data - Mock -ModuleName SentryApiClient Invoke-RestMethod { - param($Uri, $Method, $Headers, $ContentType, $Body) - + # Mock Invoke-WebRequest to return fixture data + Mock -ModuleName SentryApiClient Invoke-WebRequest { + param($Uri) + switch -Regex ($Uri) { '/events/4f1a9f7e7f7b4f9a8c8d9e0f1a2b3c4d/' { - return $script:Fixtures.event_detail + return @{ Content = ($script:Fixtures.event_detail | ConvertTo-Json -Depth 20) } } '/events/.*query=' { - return $script:Fixtures.event_list + return @{ Content = ($script:Fixtures.event_list | ConvertTo-Json -Depth 20) } } default { - throw [System.Net.WebException]::new("404 Not Found") + throw [System.Net.WebException]::new('404 Not Found') } } } - + Connect-SentryApi -ApiToken 'test-token' -Organization 'my-org' -Project 'my-app' } @@ -90,36 +90,37 @@ Describe 'SentryApiClient Tests with Real API Response Fixtures' { Context 'Issue Operations with Realistic Data' { BeforeAll { - Mock -ModuleName SentryApiClient Invoke-RestMethod { + Mock -ModuleName SentryApiClient Invoke-WebRequest { param($Uri) - + switch -Regex ($Uri) { '/events/4f1a9f7e7f7b4f9a8c8d9e0f1a2b3c4d/' { # Handle Get-SentryEvent calls - most specific first - return $script:Fixtures.event_detail + return @{ Content = ($script:Fixtures.event_detail | ConvertTo-Json -Depth 20) } } '/issues/[^/]+/events/' { # Handle issues/{id}/events/ endpoint - return event summaries - return @( + $responseData = @( @{ eventID = '4f1a9f7e7f7b4f9a8c8d9e0f1a2b3c4d' - id = 'summary-id' + id = 'summary-id' message = 'Event summary' } ) + return @{ Content = ($responseData | ConvertTo-Json -Depth 20) } } '/issues/.*query=' { - return $script:Fixtures.issue_list + return @{ Content = ($script:Fixtures.issue_list | ConvertTo-Json -Depth 20) } } '/issues/1234567890/' { - return $script:Fixtures.issue_detail + return @{ Content = ($script:Fixtures.issue_detail | ConvertTo-Json -Depth 20) } } default { - throw [System.Net.WebException]::new("404 Not Found") + throw [System.Net.WebException]::new('404 Not Found') } } } - + Connect-SentryApi -ApiToken 'test-token' -Organization 'my-org' -Project 'my-app' } @@ -149,68 +150,71 @@ Describe 'SentryApiClient Tests with Real API Response Fixtures' { Context 'Error Response Handling' { It 'Should handle 401 Unauthorized correctly' { - Mock -ModuleName SentryApiClient Invoke-RestMethod { + Mock -ModuleName SentryApiClient Invoke-WebRequest { $response = New-Object System.Net.HttpWebResponse $exception = [System.Net.WebException]::new( - "401 Unauthorized", + '401 Unauthorized', $null, [System.Net.WebExceptionStatus]::ProtocolError, $response ) throw $exception } - + Connect-SentryApi -ApiToken 'invalid-token' -Organization 'my-org' -Project 'my-app' - - { Get-SentryEvent -EventId 'test' } | Should -Throw "*401 Unauthorized*" + + { Get-SentryEvent -EventId 'test' } | Should -Throw '*401 Unauthorized*' } It 'Should handle 403 Forbidden correctly' { - Mock -ModuleName SentryApiClient Invoke-RestMethod { + Mock -ModuleName SentryApiClient Invoke-WebRequest { $response = New-Object System.Net.HttpWebResponse $exception = [System.Net.WebException]::new( - "403 Forbidden", + '403 Forbidden', $null, [System.Net.WebExceptionStatus]::ProtocolError, $response ) throw $exception } - + Connect-SentryApi -ApiToken 'test-token' -Organization 'my-org' -Project 'my-app' - - { Get-SentryEvent -EventId 'forbidden-event' } | Should -Throw "*403 Forbidden*" + + { Get-SentryEvent -EventId 'forbidden-event' } | Should -Throw '*403 Forbidden*' } It 'Should handle 429 Rate Limit correctly' { - Mock -ModuleName SentryApiClient Invoke-RestMethod { + Mock -ModuleName SentryApiClient Invoke-WebRequest { $response = New-Object System.Net.HttpWebResponse $exception = [System.Net.WebException]::new( - "429 Too Many Requests", + '429 Too Many Requests', $null, [System.Net.WebExceptionStatus]::ProtocolError, $response ) throw $exception } - + Connect-SentryApi -ApiToken 'test-token' -Organization 'my-org' -Project 'my-app' - - { Get-SentryEventsByTag -TagName 'test' -TagValue 'value' } | Should -Throw "*429 Too Many Requests*" + + { Get-SentryEventsByTag -TagName 'test' -TagValue 'value' } | Should -Throw '*429 Too Many Requests*' } } Context 'Pagination Handling' { BeforeAll { - Mock -ModuleName SentryApiClient Invoke-RestMethod { - param($Uri, $Method, $Headers) - - # Return headers with Link header for pagination - $Headers['Link'] = '; rel="next"; results="true"; cursor="1234:100:0"' - - return $script:Fixtures.event_list + Mock -ModuleName SentryApiClient Invoke-WebRequest { + param($Uri) + + # Return response with headers and content + return @{ + Headers = @{ + Link = '; rel="next"; results="true"; cursor="1234:100:0"' + } + Content = ($script:Fixtures.event_list | ConvertTo-Json -Depth 20) + } } - + Connect-SentryApi -ApiToken 'test-token' -Organization 'my-org' -Project 'my-app' } diff --git a/sentry-api-client/Tests/SentryApiClient.Integration.Tests.ps1 b/sentry-api-client/Tests/SentryApiClient.Integration.Tests.ps1 index 85da320..fd114b5 100644 --- a/sentry-api-client/Tests/SentryApiClient.Integration.Tests.ps1 +++ b/sentry-api-client/Tests/SentryApiClient.Integration.Tests.ps1 @@ -43,9 +43,14 @@ Describe 'SentryApiClient Integration Tests' { Events = $testEvents Issues = $testIssues } - - Mock -ModuleName SentryApiClient Invoke-RestMethod $mockResponder - + + # Wrap mock responder to return Invoke-WebRequest format + Mock -ModuleName SentryApiClient Invoke-WebRequest { + param($Uri) + $result = & $mockResponder -Uri $Uri + return @{ Content = ($result | ConvertTo-Json -Depth 20) } + } + # Setup connection Connect-SentryApi -ApiToken 'test-token' -Organization 'test-org' -Project 'test-project' } @@ -74,34 +79,33 @@ Describe 'SentryApiClient Integration Tests' { It 'Should find issues by tag and retrieve associated events' { # Create a new mock that handles both issues and events - Mock -ModuleName SentryApiClient Invoke-RestMethod { + Mock -ModuleName SentryApiClient Invoke-WebRequest { param($Uri) - - if ($Uri -match '/events/[^/]+/') { + + $result = if ($Uri -match '/events/[^/]+/') { # Handle Get-SentryEvent calls - most specific first - return $testEvents | Where-Object { $_.id -eq 'event001' } - } - elseif ($Uri -match '/issues/[^/]+/events/') { + $testEvents | Where-Object { $_.id -eq 'event001' } + } elseif ($Uri -match '/issues/[^/]+/events/') { # Return event summaries for the issue - return @( + @( @{ eventID = 'event001' - id = 'summary-id' + id = 'summary-id' message = 'Event summary' } ) - } - elseif ($Uri -match '/issues/.*query=') { + } elseif ($Uri -match '/issues/.*query=') { # Return issues that match the tag - return $testIssues | Where-Object { $_.title -match 'Database' } - } - else { - throw "Unexpected URI: $Uri" + $testIssues | Where-Object { $_.title -match 'Database' } + } else { + throw 'Unexpected URI: $Uri' } + + return @{ Content = ($result | ConvertTo-Json -Depth 20) } } - + $result = Find-SentryEventByTag -TagName 'severity' -TagValue 'high' - + $result | Should -Not -BeNullOrEmpty # Function now returns events array directly if ($result.Count -gt 0) { @@ -119,9 +123,9 @@ Describe 'SentryApiClient Integration Tests' { ) } - Mock -ModuleName SentryApiClient Invoke-RestMethod { + Mock -ModuleName SentryApiClient Invoke-WebRequest { param($Uri) - + $queryParams = @{} if ($Uri -match '\?(.+)$') { $Matches[1] -split '&' | ForEach-Object { @@ -129,12 +133,13 @@ Describe 'SentryApiClient Integration Tests' { $queryParams[$key] = [System.Web.HttpUtility]::UrlDecode($value) } } - + $limit = if ($queryParams['limit']) { [int]$queryParams['limit'] } else { 100 } - - return $largeEventSet | Select-Object -First $limit + + $result = $largeEventSet | Select-Object -First $limit + return @{ Content = ($result | ConvertTo-Json -Depth 20) } } - + Connect-SentryApi -ApiToken 'test-token' -Organization 'test-org' -Project 'test-project' } @@ -164,8 +169,14 @@ Describe 'SentryApiClient Integration Tests' { } $mockResponder = New-MockSentryApiResponder -TestData $testData -SimulateRateLimit -RateLimitAfterCalls 2 - Mock -ModuleName SentryApiClient Invoke-RestMethod $mockResponder - + + # Wrap mock responder to return Invoke-WebRequest format + Mock -ModuleName SentryApiClient Invoke-WebRequest { + param($Uri) + $result = & $mockResponder -Uri $Uri + return @{ Content = ($result | ConvertTo-Json -Depth 20) } + } + Connect-SentryApi -ApiToken 'test-token' -Organization 'test-org' -Project 'test-project' # First two calls should succeed @@ -177,15 +188,14 @@ Describe 'SentryApiClient Integration Tests' { } It 'Should provide meaningful error for malformed responses' { - Mock -ModuleName SentryApiClient Invoke-RestMethod { - return "Not a valid JSON response" + Mock -ModuleName SentryApiClient Invoke-WebRequest { + return @{ Content = 'Not a valid JSON response' } } - + Connect-SentryApi -ApiToken 'test-token' -Organization 'test-org' -Project 'test-project' - - # The function should handle the malformed response gracefully - $result = Get-SentryEvent -EventId 'test' - $result | Should -Be "Not a valid JSON response" + + # The function should now throw an error for malformed JSON + { Get-SentryEvent -EventId 'test' } | Should -Throw } } @@ -212,22 +222,24 @@ Describe 'SentryApiClient Integration Tests' { ) ) - Mock -ModuleName SentryApiClient Invoke-RestMethod { + Mock -ModuleName SentryApiClient Invoke-WebRequest { param($Uri) - + # Simple tag filtering logic for testing - if ($Uri -match 'query=(\w+)%3A(\w+)') { + $result = if ($Uri -match 'query=(\w+)%3A(\w+)') { $tagName = $Matches[1] $tagValue = $Matches[2] - - return $complexEvents | Where-Object { + + $complexEvents | Where-Object { $_.tags | Where-Object { $_.key -eq $tagName -and $_.value -eq $tagValue } } + } else { + $complexEvents } - - return $complexEvents + + return @{ Content = ($result | ConvertTo-Json -Depth 20) } } - + Connect-SentryApi -ApiToken 'test-token' -Organization 'test-org' -Project 'test-project' } diff --git a/sentry-api-client/Tests/SentryApiClient.Tests.ps1 b/sentry-api-client/Tests/SentryApiClient.Tests.ps1 index e53f133..63a4dc8 100644 --- a/sentry-api-client/Tests/SentryApiClient.Tests.ps1 +++ b/sentry-api-client/Tests/SentryApiClient.Tests.ps1 @@ -12,7 +12,7 @@ Describe 'SentryApiClient Module' { It 'Should import the module successfully' { Get-Module SentryApiClient | Should -Not -BeNullOrEmpty } - + It 'Should export the expected functions' { $ExpectedFunctions = @( 'Connect-SentryApi', @@ -23,272 +23,278 @@ Describe 'SentryApiClient Module' { 'Invoke-SentryCLI', 'Get-SentryCLI' ) - + $ExportedFunctions = (Get-Module SentryApiClient).ExportedFunctions.Keys foreach ($Function in $ExpectedFunctions) { $ExportedFunctions | Should -Contain $Function } } } - + Context 'Connect-SentryApi' { BeforeEach { if (Get-Command Disconnect-SentryApi -ErrorAction SilentlyContinue) { Disconnect-SentryApi } } - + It 'Should accept manual parameters' { { Connect-SentryApi -ApiToken 'test-token' -Organization 'test-org' -Project 'test-project' } | Should -Not -Throw } - + It 'Should accept DSN parameter' { $testDSN = 'https://testkey@o12345.ingest.us.sentry.io/67890' { Connect-SentryApi -ApiToken 'test-token' -DSN $testDSN } | Should -Not -Throw } - + It 'Should parse DSN with different host formats' { $testDSN = 'https://testkey@o99999.sentry.io/11111' { Connect-SentryApi -ApiToken 'test-token' -DSN $testDSN } | Should -Not -Throw } - + It 'Should throw error for invalid DSN format' { $invalidDSN = 'https://testkey@invalid-host/12345' { Connect-SentryApi -ApiToken 'test-token' -DSN $invalidDSN } | Should -Throw } - + It 'Should throw error for DSN without organization ID' { $invalidDSN = 'https://testkey@sentry.io/12345' { Connect-SentryApi -ApiToken 'test-token' -DSN $invalidDSN } | Should -Throw } - + It 'Should set BaseUrl correctly for sentry.io hosted DSN' { $testDSN = 'https://testkey@o12345.ingest.us.sentry.io/67890' { Connect-SentryApi -ApiToken 'test-token' -DSN $testDSN } | Should -Not -Throw } - + It 'Should set BaseUrl correctly for self-hosted DSN' { $testDSN = 'https://testkey@o12345.mycompany.com/67890' { Connect-SentryApi -ApiToken 'test-token' -DSN $testDSN } | Should -Not -Throw } } - + Context 'Disconnect-SentryApi' { It 'Should clear the configuration' { Connect-SentryApi -ApiToken 'test-token' -Organization 'test-org' -Project 'test-project' { Disconnect-SentryApi } | Should -Not -Throw } } - + Context 'API Request Functions with Mocked HTTP' { BeforeAll { - # Mock only the external HTTP call, not our internal functions - Mock -ModuleName SentryApiClient Invoke-RestMethod { - param($Uri, $Method, $Headers, $ContentType, $Body) - + # Mock Invoke-WebRequest instead of Invoke-RestMethod + # Our implementation uses Invoke-WebRequest for explicit JSON parsing control + Mock -ModuleName SentryApiClient Invoke-WebRequest { + param($Uri) + # Return different responses based on the URI pattern + # Invoke-WebRequest returns an object with a Content property containing JSON # Order matters - most specific patterns first switch -Regex ($Uri) { '/issues/[^/]+/events/' { # Handle issues/{id}/events/ endpoint - most specific first - return @( + $responseData = @( @{ eventID = 'event123' - id = 'some-other-id' + id = 'some-other-id' message = 'Event summary' } ) + return @{ Content = ($responseData | ConvertTo-Json -Depth 10) } } '/events/\w+/' { - return @{ - id = '12345678901234567890123456789012' - message = 'Test error message' + $responseData = @{ + id = '12345678901234567890123456789012' + message = 'Test error message' timestamp = '2023-01-01T00:00:00Z' - tags = @( + tags = @( @{ key = 'environment'; value = 'production' } @{ key = 'release'; value = '1.0.0' } ) } + return @{ Content = ($responseData | ConvertTo-Json -Depth 10) } } '/events.*query=' { - return @( + $responseData = @( @{ - id = 'event1' + id = 'event1' message = 'Error 1' - tags = @( + tags = @( @{ key = 'environment'; value = 'production' } ) }, @{ - id = 'event2' + id = 'event2' message = 'Error 2' - tags = @( + tags = @( @{ key = 'environment'; value = 'production' } ) } ) + return @{ Content = ($responseData | ConvertTo-Json -Depth 10) } } '/issues.*query=' { - return @( + $responseData = @( @{ - id = 'issue1' - title = 'Test Issue 1' - culprit = 'app.js' + id = 'issue1' + title = 'Test Issue 1' + culprit = 'app.js' permalink = 'https://sentry.io/issues/1/' firstSeen = '2023-01-01T00:00:00Z' - lastSeen = '2023-01-02T00:00:00Z' - count = 10 - metadata = @{ - type = 'Error' + lastSeen = '2023-01-02T00:00:00Z' + count = 10 + metadata = @{ + type = 'Error' value = 'Test error' } } ) + return @{ Content = ($responseData | ConvertTo-Json -Depth 10) } } default { throw "Unexpected URI: $Uri" } } } - + # Setup a connection for testing Connect-SentryApi -ApiToken 'test-token' -Organization 'test-org' -Project 'test-project' } - + Context 'Get-SentryEvent' { It 'Should retrieve event with properly formatted ID' { $eventId = '12345678901234567890123456789012' $result = Get-SentryEvent -EventId $eventId - + $result | Should -Not -BeNullOrEmpty $result.id | Should -Be $eventId $result.message | Should -Be 'Test error message' } - + It 'Should remove hyphens from GUID-formatted event ID' { $guidEventId = '12345678-9012-3456-7890-123456789012' - + # Verify the function is called with the correct parameters - $result = Get-SentryEvent -EventId $guidEventId - + Get-SentryEvent -EventId $guidEventId + # The mock should have been called with the cleaned event ID - Assert-MockCalled -ModuleName SentryApiClient Invoke-RestMethod -ParameterFilter { + Assert-MockCalled -ModuleName SentryApiClient Invoke-WebRequest -ParameterFilter { $Uri -like '*events/12345678901234567890123456789012/*' } } - + It 'Should construct correct API URL' { $eventId = '12345678901234567890123456789012' - + Get-SentryEvent -EventId $eventId - - # Verify Invoke-RestMethod was called with correct URL structure - Assert-MockCalled -ModuleName SentryApiClient Invoke-RestMethod -ParameterFilter { + + # Verify Invoke-WebRequest was called with correct URL structure + Assert-MockCalled -ModuleName SentryApiClient Invoke-WebRequest -ParameterFilter { $Uri -match 'https://sentry.io/api/0/projects/test-org/test-project/events/\w+/' } } } - + Context 'Get-SentryEventsByTag' { It 'Should query events with tag filter' { $result = Get-SentryEventsByTag -TagName 'environment' -TagValue 'production' - + $result | Should -Not -BeNullOrEmpty $result | Should -HaveCount 2 $result[0].message | Should -Be 'Error 1' } - + It 'Should include query parameters in URL' { Get-SentryEventsByTag -TagName 'environment' -TagValue 'production' -Limit 50 - + # Verify the query parameters were included - Assert-MockCalled -ModuleName SentryApiClient Invoke-RestMethod -ParameterFilter { + Assert-MockCalled -ModuleName SentryApiClient Invoke-WebRequest -ParameterFilter { $Uri -match 'query=environment%3Aproduction' -and $Uri -match 'limit=50' } } - + It 'Should include cursor parameter when provided' { Get-SentryEventsByTag -TagName 'environment' -TagValue 'production' -Cursor 'next123' - - Assert-MockCalled -ModuleName SentryApiClient Invoke-RestMethod -ParameterFilter { + + Assert-MockCalled -ModuleName SentryApiClient Invoke-WebRequest -ParameterFilter { $Uri -match 'cursor=next123' } } - + It 'Should include full parameter when switch is provided' { Get-SentryEventsByTag -TagName 'environment' -TagValue 'production' -Full - - Assert-MockCalled -ModuleName SentryApiClient Invoke-RestMethod -ParameterFilter { + + Assert-MockCalled -ModuleName SentryApiClient Invoke-WebRequest -ParameterFilter { $Uri -match 'full=true' } } } - + Context 'Find-SentryEventByTag' { It 'Should query issues endpoint and return events array' { $result = Find-SentryEventByTag -TagName 'environment' -TagValue 'production' - + $result | Should -Not -BeNullOrEmpty # Check if it's an array using Count property instead of type check $result.Count | Should -BeGreaterThan 0 $result[0].id | Should -Be '12345678901234567890123456789012' } - + It 'Should make correct API call to issues endpoint' { Find-SentryEventByTag -TagName 'release' -TagValue '1.0.0' - - Assert-MockCalled -ModuleName SentryApiClient Invoke-RestMethod -ParameterFilter { + + Assert-MockCalled -ModuleName SentryApiClient Invoke-WebRequest -ParameterFilter { $Uri -match '/issues/' -and $Uri -match 'query=release%3A1\.0\.0' } } - + It 'Should include sort parameter when provided' { Find-SentryEventByTag -TagName 'release' -TagValue '1.0.0' -Sort 'date' - - Assert-MockCalled -ModuleName SentryApiClient Invoke-RestMethod -ParameterFilter { + + Assert-MockCalled -ModuleName SentryApiClient Invoke-WebRequest -ParameterFilter { $Uri -match 'sort=date' } } - + It 'Should retrieve associated events when issues are found' { # Use the default mock setup - it already handles both endpoints correctly $result = Find-SentryEventByTag -TagName 'environment' -TagValue 'production' - + # Should return events that can be accessed by index $result.Count | Should -BeGreaterThan 0 $result[0].id | Should -Be '12345678901234567890123456789012' $result[0].message | Should -Be 'Test error message' - + # Should have made 3 calls - one for issues, one for events list, one for actual event content - Assert-MockCalled -ModuleName SentryApiClient Invoke-RestMethod -Times 3 + Assert-MockCalled -ModuleName SentryApiClient Invoke-WebRequest -Times 3 } } } - + Context 'Error Handling' { BeforeAll { Connect-SentryApi -ApiToken 'test-token' -Organization 'test-org' -Project 'test-project' } - + It 'Should handle API errors gracefully' { - Mock -ModuleName SentryApiClient Invoke-RestMethod { - throw [System.Net.WebException]::new("404 Not Found") + Mock -ModuleName SentryApiClient Invoke-WebRequest { + throw [System.Net.WebException]::new('404 Not Found') } - - { Get-SentryEvent -EventId 'nonexistent' } | Should -Throw "*Sentry API request*failed*" + + { Get-SentryEvent -EventId 'nonexistent' } | Should -Throw '*Sentry API request*failed*' } - + It 'Should handle rate limiting' { Mock -ModuleName SentryApiClient Invoke-RestMethod { $response = [System.Net.HttpWebResponse]::new() - $exception = [System.Net.WebException]::new("429 Too Many Requests", $null, [System.Net.WebExceptionStatus]::ProtocolError, $response) + $exception = [System.Net.WebException]::new('429 Too Many Requests', $null, [System.Net.WebExceptionStatus]::ProtocolError, $response) throw $exception } - - { Get-SentryEventsByTag -TagName 'test' -TagValue 'value' } | Should -Throw "*Sentry API request*failed*" + + { Get-SentryEventsByTag -TagName 'test' -TagValue 'value' } | Should -Throw '*Sentry API request*failed*' } } - -} \ No newline at end of file + +}