From eaf0f72e9c6ff4c838a431ff27e810a1b68ab233 Mon Sep 17 00:00:00 2001 From: Serhii Snitsaruk Date: Fri, 6 Mar 2026 13:04:34 +0100 Subject: [PATCH 1/6] feat: Add Get-SentryEventAttachments API and test helper Add `Get-SentryEventAttachments` to the Sentry API client for retrieving event attachments, and `Get-SentryTestEventAttachments` polling helper to test utils for use in integration tests. Co-Authored-By: Claude Opus 4.6 --- .../Public/Get-SentryEventAttachments.ps1 | 29 +++++++++ sentry-api-client/SentryApiClient.psd1 | 1 + utils/Integration.TestUtils.psm1 | 61 ++++++++++++++++++- 3 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 sentry-api-client/Public/Get-SentryEventAttachments.ps1 diff --git a/sentry-api-client/Public/Get-SentryEventAttachments.ps1 b/sentry-api-client/Public/Get-SentryEventAttachments.ps1 new file mode 100644 index 0000000..8ff5c08 --- /dev/null +++ b/sentry-api-client/Public/Get-SentryEventAttachments.ps1 @@ -0,0 +1,29 @@ +function Get-SentryEventAttachments { + <# + .SYNOPSIS + Retrieves attachments for a specific event from Sentry. + + .DESCRIPTION + Fetches the list of attachments associated with a specific Sentry event by its ID. + Automatically removes hyphens from GUID-formatted event IDs. + + .PARAMETER EventId + The unique identifier of the event whose attachments to retrieve. + + .EXAMPLE + Get-SentryEventAttachments -EventId "abc123def456" + # Returns an array of attachment objects for the event + #> + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [string]$EventId + ) + + # Remove hyphens from GUID-formatted event IDs + $EventId = $EventId -replace '-', '' + + $Uri = Get-SentryProjectUrl -Resource "events/$EventId/attachments/" + + return Invoke-SentryApiRequest -Uri $Uri -Method 'GET' +} diff --git a/sentry-api-client/SentryApiClient.psd1 b/sentry-api-client/SentryApiClient.psd1 index d04ee5b..64f4a8c 100644 --- a/sentry-api-client/SentryApiClient.psd1 +++ b/sentry-api-client/SentryApiClient.psd1 @@ -39,6 +39,7 @@ 'Get-SentryLogsByAttribute', 'Get-SentryMetrics', 'Get-SentryMetricsByAttribute', + 'Get-SentryEventAttachments', 'Get-SentrySpans', 'Invoke-SentryCLI' ) diff --git a/utils/Integration.TestUtils.psm1 b/utils/Integration.TestUtils.psm1 index db0ea7e..79b3eda 100644 --- a/utils/Integration.TestUtils.psm1 +++ b/utils/Integration.TestUtils.psm1 @@ -455,5 +455,64 @@ function Get-SentryTestTransaction { throw "Transaction with trace $TraceId not found in Sentry within $TimeoutSeconds seconds: $lastError" } +function Get-SentryTestEventAttachments { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [string]$EventId, + + [Parameter()] + [int]$ExpectedCount = 1, + + [Parameter()] + [int]$TimeoutSeconds = 120 + ) + + Write-Host "Fetching Sentry event attachments for event: $EventId" -ForegroundColor Yellow + $progressActivity = "Waiting for attachments on event $EventId" + + $startTime = Get-Date + $endTime = $startTime.AddSeconds($TimeoutSeconds) + $lastError = $null + $elapsedSeconds = 0 + + try { + do { + $attachments = @() + $elapsedSeconds = [int]((Get-Date) - $startTime).TotalSeconds + $percentComplete = [math]::Min(100, ($elapsedSeconds / $TimeoutSeconds) * 100) + + Write-Progress -Activity $progressActivity -Status "Elapsed: $elapsedSeconds/$TimeoutSeconds seconds" -PercentComplete $percentComplete + + try { + $response = Get-SentryEventAttachments -EventId $EventId + if ($response -and $response.Count -ge $ExpectedCount) { + $attachments = $response + } + } catch { + $lastError = $_.Exception.Message + Write-Debug "Attachments for event $EventId not found yet: $lastError" + } + + if ($attachments.Count -ge $ExpectedCount) { + Write-Host "Found $($attachments.Count) attachment(s) for event $EventId" -ForegroundColor Green + + $attachmentsJson = $attachments | ConvertTo-Json -Depth 10 + $attachmentsJson | Out-File -FilePath (Get-OutputFilePath "attachments-$EventId.json") + + return , @($attachments) + } + + Start-Sleep -Milliseconds 500 + $currentTime = Get-Date + } while ($currentTime -lt $endTime) + } finally { + Write-Progress -Activity $progressActivity -Completed + } + + $foundCount = if ($attachments) { $attachments.Count } else { 0 } + throw "Expected at least $ExpectedCount attachment(s) for event $EventId but found $foundCount within $TimeoutSeconds seconds. Last error: $lastError" +} + # Export module functions -Export-ModuleMember -Function Invoke-CMakeConfigure, Invoke-CMakeBuild, Set-OutputDir, Get-OutputFilePath, Get-EventIds, Get-SentryTestEvent, Get-SentryTestLog, Get-SentryTestMetric, Get-SentryTestTransaction, Get-PackageAumid +Export-ModuleMember -Function Invoke-CMakeConfigure, Invoke-CMakeBuild, Set-OutputDir, Get-OutputFilePath, Get-EventIds, Get-SentryTestEvent, Get-SentryTestEventAttachments, Get-SentryTestLog, Get-SentryTestMetric, Get-SentryTestTransaction, Get-PackageAumid From 696b9326720ef852d28bf6cb9699db0a56e77d7b Mon Sep 17 00:00:00 2001 From: Serhii Snitsaruk Date: Fri, 6 Mar 2026 13:15:02 +0100 Subject: [PATCH 2/6] fix: Ensure Get-SentryEventAttachments returns an array The attachments API returns a top-level JSON array which ConvertFrom-Json -AsHashtable unwraps when it contains a single element. Wrap the result with , @() to guarantee array output. Co-Authored-By: Claude --- sentry-api-client/Public/Get-SentryEventAttachments.ps1 | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sentry-api-client/Public/Get-SentryEventAttachments.ps1 b/sentry-api-client/Public/Get-SentryEventAttachments.ps1 index 8ff5c08..1ed279f 100644 --- a/sentry-api-client/Public/Get-SentryEventAttachments.ps1 +++ b/sentry-api-client/Public/Get-SentryEventAttachments.ps1 @@ -25,5 +25,7 @@ function Get-SentryEventAttachments { $Uri = Get-SentryProjectUrl -Resource "events/$EventId/attachments/" - return Invoke-SentryApiRequest -Uri $Uri -Method 'GET' + # The API returns a top-level JSON array. ConvertFrom-Json -AsHashtable unwraps + # single-element arrays into a hashtable, so wrap in @() to ensure array output. + return , @(Invoke-SentryApiRequest -Uri $Uri -Method 'GET') } From e592101d121cba138316c4bf6fc4fd7e93ae9d15 Mon Sep 17 00:00:00 2001 From: Serhii Snitsaruk Date: Fri, 6 Mar 2026 13:23:46 +0100 Subject: [PATCH 3/6] ref: Align with codebase conventions Sort manifest export alphabetically and remove redundant null guard in polling helper since the API function now always returns an array. Co-Authored-By: Claude --- sentry-api-client/SentryApiClient.psd1 | 2 +- utils/Integration.TestUtils.psm1 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sentry-api-client/SentryApiClient.psd1 b/sentry-api-client/SentryApiClient.psd1 index 64f4a8c..cda7ffe 100644 --- a/sentry-api-client/SentryApiClient.psd1 +++ b/sentry-api-client/SentryApiClient.psd1 @@ -34,12 +34,12 @@ 'Find-SentryEventByTag', 'Get-SentryCLI', 'Get-SentryEvent', + 'Get-SentryEventAttachments', 'Get-SentryEventsByTag', 'Get-SentryLogs', 'Get-SentryLogsByAttribute', 'Get-SentryMetrics', 'Get-SentryMetricsByAttribute', - 'Get-SentryEventAttachments', 'Get-SentrySpans', 'Invoke-SentryCLI' ) diff --git a/utils/Integration.TestUtils.psm1 b/utils/Integration.TestUtils.psm1 index 79b3eda..6ef4dc6 100644 --- a/utils/Integration.TestUtils.psm1 +++ b/utils/Integration.TestUtils.psm1 @@ -486,7 +486,7 @@ function Get-SentryTestEventAttachments { try { $response = Get-SentryEventAttachments -EventId $EventId - if ($response -and $response.Count -ge $ExpectedCount) { + if ($response.Count -ge $ExpectedCount) { $attachments = $response } } catch { From a779d422666f03575800a37b3af0bc8d1a3e130b Mon Sep 17 00:00:00 2001 From: Serhii Snitsaruk Date: Fri, 6 Mar 2026 13:27:56 +0100 Subject: [PATCH 4/6] test: Add unit tests for Get-SentryEventAttachments Cover retrieval, GUID hyphen removal, URL construction, and single-element array unwrapping behavior. Co-Authored-By: Claude --- .../Tests/SentryApiClient.Tests.ps1 | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/sentry-api-client/Tests/SentryApiClient.Tests.ps1 b/sentry-api-client/Tests/SentryApiClient.Tests.ps1 index 63a4dc8..792208a 100644 --- a/sentry-api-client/Tests/SentryApiClient.Tests.ps1 +++ b/sentry-api-client/Tests/SentryApiClient.Tests.ps1 @@ -18,6 +18,7 @@ Describe 'SentryApiClient Module' { 'Connect-SentryApi', 'Disconnect-SentryApi', 'Get-SentryEvent', + 'Get-SentryEventAttachments', 'Find-SentryEventByTag', 'Get-SentryEventsByTag', 'Invoke-SentryCLI', @@ -91,6 +92,31 @@ Describe 'SentryApiClient Module' { # Invoke-WebRequest returns an object with a Content property containing JSON # Order matters - most specific patterns first switch -Regex ($Uri) { + '/events/\w+/attachments/' { + $responseData = @( + @{ + id = '111' + event_id = '12345678901234567890123456789012' + type = 'event.attachment' + name = 'hello.txt' + mimetype = 'text/plain' + size = 14 + headers = @{ 'Content-Type' = 'text/plain' } + sha1 = 'abc123' + }, + @{ + id = '222' + event_id = '12345678901234567890123456789012' + type = 'event.attachment' + name = 'config.txt' + mimetype = 'application/octet-stream' + size = 42 + headers = @{ 'Content-Type' = 'application/octet-stream' } + sha1 = 'def456' + } + ) + return @{ Content = ($responseData | ConvertTo-Json -Depth 10) } + } '/issues/[^/]+/events/' { # Handle issues/{id}/events/ endpoint - most specific first $responseData = @( @@ -271,6 +297,60 @@ Describe 'SentryApiClient Module' { Assert-MockCalled -ModuleName SentryApiClient Invoke-WebRequest -Times 3 } } + + Context 'Get-SentryEventAttachments' { + It 'Should retrieve attachments for an event' { + $eventId = '12345678901234567890123456789012' + $result = Get-SentryEventAttachments -EventId $eventId + + $result | Should -Not -BeNullOrEmpty + $result | Should -HaveCount 2 + $result[0].name | Should -Be 'hello.txt' + $result[1].name | Should -Be 'config.txt' + } + + It 'Should remove hyphens from GUID-formatted event ID' { + $guidEventId = '12345678-9012-3456-7890-123456789012' + + Get-SentryEventAttachments -EventId $guidEventId + + Assert-MockCalled -ModuleName SentryApiClient Invoke-WebRequest -ParameterFilter { + $Uri -like '*events/12345678901234567890123456789012/attachments/*' + } + } + + It 'Should construct correct API URL' { + $eventId = '12345678901234567890123456789012' + + Get-SentryEventAttachments -EventId $eventId + + Assert-MockCalled -ModuleName SentryApiClient Invoke-WebRequest -ParameterFilter { + $Uri -match 'https://sentry.io/api/0/projects/test-org/test-project/events/\w+/attachments/' + } + } + + It 'Should return array even with single attachment' { + Mock -ModuleName SentryApiClient Invoke-WebRequest { + $responseData = @( + @{ + id = '333' + event_id = 'single' + type = 'event.attachment' + name = 'only.txt' + size = 5 + headers = @{ 'Content-Type' = 'text/plain' } + } + ) + return @{ Content = ($responseData | ConvertTo-Json -Depth 10) } + } + + $result = Get-SentryEventAttachments -EventId 'single' + + $result -is [Array] | Should -BeTrue + $result | Should -HaveCount 1 + $result[0].name | Should -Be 'only.txt' + } + } } Context 'Error Handling' { From 0be0d98cdea7b766d7e14f22c98436f3d925b718 Mon Sep 17 00:00:00 2001 From: Serhii Snitsaruk Date: Fri, 6 Mar 2026 13:30:52 +0100 Subject: [PATCH 5/6] docs: Add Get-SentryEventAttachments to README Co-Authored-By: Claude --- sentry-api-client/README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/sentry-api-client/README.md b/sentry-api-client/README.md index 4fdbccd..1a4dd12 100644 --- a/sentry-api-client/README.md +++ b/sentry-api-client/README.md @@ -18,6 +18,9 @@ Connect-SentryApi -ApiToken "your-api-token" -Organization "your-org" -Project " # Get specific event Get-SentryEvent -EventId "123456" +# Get event attachments +Get-SentryEventAttachments -EventId "123456" + # Find events by tag Get-SentryEventsByTag -TagName 'environment' -TagValue 'production' @@ -74,6 +77,15 @@ Clears the current Sentry API connection and configuration. Retrieves a specific event from Sentry by its ID. +### Get-SentryEventAttachments + +Retrieves attachments for a specific event from Sentry. Returns an array of attachment objects. + +```powershell +# Get attachments for an event +Get-SentryEventAttachments -EventId "123456" +``` + ### Get-SentryEventsByTag Retrieves events filtered by a specific tag name and value. From d3fca3e905e8ba8782430cc6848156dfc5df8531 Mon Sep 17 00:00:00 2001 From: Serhii Snitsaruk Date: Fri, 6 Mar 2026 14:55:51 +0100 Subject: [PATCH 6/6] docs: Clarify that Get-SentryEventAttachments returns metadata Make it clear the function fetches attachment metadata (name, size, type, content type) and does not download attachment content. Co-Authored-By: Claude --- sentry-api-client/Public/Get-SentryEventAttachments.ps1 | 4 ++-- sentry-api-client/README.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/sentry-api-client/Public/Get-SentryEventAttachments.ps1 b/sentry-api-client/Public/Get-SentryEventAttachments.ps1 index 1ed279f..2d761c4 100644 --- a/sentry-api-client/Public/Get-SentryEventAttachments.ps1 +++ b/sentry-api-client/Public/Get-SentryEventAttachments.ps1 @@ -4,8 +4,8 @@ function Get-SentryEventAttachments { Retrieves attachments for a specific event from Sentry. .DESCRIPTION - Fetches the list of attachments associated with a specific Sentry event by its ID. - Automatically removes hyphens from GUID-formatted event IDs. + Fetches attachment metadata (name, size, type, content type) for a specific Sentry event. + Does not download attachment content. Automatically removes hyphens from GUID-formatted event IDs. .PARAMETER EventId The unique identifier of the event whose attachments to retrieve. diff --git a/sentry-api-client/README.md b/sentry-api-client/README.md index 1a4dd12..a2d2a76 100644 --- a/sentry-api-client/README.md +++ b/sentry-api-client/README.md @@ -79,7 +79,7 @@ Retrieves a specific event from Sentry by its ID. ### Get-SentryEventAttachments -Retrieves attachments for a specific event from Sentry. Returns an array of attachment objects. +Retrieves attachment metadata (name, size, type, content type) for a specific event. Does not download attachment content. Returns an array of attachment metadata objects. ```powershell # Get attachments for an event