|
| 1 | +Describe "Error sanitization" { |
| 2 | + BeforeAll { |
| 3 | + $here = $PSScriptRoot |
| 4 | + $srcRoot = Join-Path $here "../../src/powershell" |
| 5 | + |
| 6 | + . (Join-Path $srcRoot "private/core/Protect-ZtReportText.ps1") |
| 7 | + . (Join-Path $srcRoot "private/core/Get-ZtHttpStatusCode.ps1") |
| 8 | + . (Join-Path $srcRoot "private/core/Get-ZtTestStatus.ps1") |
| 9 | + . (Join-Path $srcRoot "private/tests/Get-ZtSafeErrorMessage.ps1") |
| 10 | + . (Join-Path $srcRoot "private/tests/New-ZtSafeErrorRecord.ps1") |
| 11 | + . (Join-Path $srcRoot "private/tests/Format-ZtTestErrorDetail.ps1") |
| 12 | + . (Join-Path $srcRoot "private/tests/Write-ZtTestError.ps1") |
| 13 | + . (Join-Path $srcRoot "private/tests/Write-ZtTestLog.ps1") |
| 14 | + . (Join-Path $srcRoot "private/core/Add-ZtTestResultDetail.ps1") |
| 15 | + |
| 16 | + function New-CanaryErrorRecord { |
| 17 | + $script:canaryToken = 'eyJhbGciOiJub25lIn0.eyJhcHBpZCI6InJlZGFjdGlvbi10ZXN0In0.signature' |
| 18 | + $request = [System.Net.Http.HttpRequestMessage]::new([System.Net.Http.HttpMethod]::Get, "https://graph.microsoft.com/beta/example?access_token=$script:canaryToken") |
| 19 | + $request.Headers.Authorization = [System.Net.Http.Headers.AuthenticationHeaderValue]::new('Bearer', $script:canaryToken) |
| 20 | + $exception = [System.Exception]::new(@" |
| 21 | +Exception calling ""InvokeGlobal"" with ""1"" argument(s): ""GET https://graph.microsoft.com/beta/example?access_token=$script:canaryToken |
| 22 | +HTTP/1.1 401 Unauthorized |
| 23 | +request-id: 11111111-1111-1111-1111-111111111111 |
| 24 | +client-request-id: 22222222-2222-2222-2222-222222222222 |
| 25 | +Authorization: Bearer $script:canaryToken |
| 26 | +Cookie: session=example-cookie |
| 27 | +{"error":{"code":"UnknownError","message":"example response body"}} |
| 28 | +"@) |
| 29 | + |
| 30 | + return [System.Management.Automation.ErrorRecord]::new( |
| 31 | + $exception, |
| 32 | + 'GraphRequestFailed', |
| 33 | + [System.Management.Automation.ErrorCategory]::PermissionDenied, |
| 34 | + $request |
| 35 | + ) |
| 36 | + } |
| 37 | + } |
| 38 | + |
| 39 | + BeforeEach { |
| 40 | + $script:__ZtSession = @{ |
| 41 | + TestResultDetail = [PSCustomObject]@{ Value = @{} } |
| 42 | + } |
| 43 | + $script:loggedErrorRecord = $null |
| 44 | + $script:addedResult = $null |
| 45 | + |
| 46 | + function Write-PSFMessage { |
| 47 | + param($Level, $Message, $StringValues, $Target, $ErrorRecord, $Tag) |
| 48 | + $script:loggedErrorRecord = $ErrorRecord |
| 49 | + } |
| 50 | + |
| 51 | + function Update-ZtProgressState {} |
| 52 | + function Write-ZtProgress {} |
| 53 | + |
| 54 | + function Get-ZtTest { |
| 55 | + param([switch] $Current, $Tests) |
| 56 | + return [PSCustomObject]@{ |
| 57 | + TestId = '99999' |
| 58 | + Title = 'Canary test' |
| 59 | + Pillar = 'Identity' |
| 60 | + SfiPillar = $null |
| 61 | + MinimumLicense = $null |
| 62 | + CompatibleLicense = $null |
| 63 | + } |
| 64 | + } |
| 65 | + } |
| 66 | + |
| 67 | + It "formats Graph failures with safe troubleshooting details only" { |
| 68 | + $errorRecord = New-CanaryErrorRecord |
| 69 | + $test = [PSCustomObject]@{ TestID = '99999' } |
| 70 | + |
| 71 | + $result = Format-ZtTestErrorDetail -Test $test -ErrorRecord $errorRecord |
| 72 | + |
| 73 | + $result | Should -Match 'Graph request failed: GET https://graph.microsoft.com/beta/example' |
| 74 | + $result | Should -Match 'HTTP status: 401 Unauthorized' |
| 75 | + $result | Should -Match 'Graph error code: UnknownError' |
| 76 | + $result | Should -Match 'Request ID: 11111111-1111-1111-1111-111111111111' |
| 77 | + $result | Should -Match 'Client request ID: 22222222-2222-2222-2222-222222222222' |
| 78 | + $result | Should -Match 'HTTP Status Code: 401' |
| 79 | + $result | Should -Match 'GraphRequestFailed' |
| 80 | + $result | Should -Not -Match [regex]::Escape($script:canaryToken) |
| 81 | + $result | Should -Not -Match 'Authorization:|Cookie:|example response body|access_token=' |
| 82 | + } |
| 83 | + |
| 84 | + It "stores and logs only a sanitized error record for parallel failures" { |
| 85 | + $errorRecord = New-CanaryErrorRecord |
| 86 | + $test = [PSCustomObject]@{ TestID = '99999'; Title = 'Canary test' } |
| 87 | + $executionResult = [PSCustomObject]@{ Success = $true; Error = $null; DisplayName = 'Canary test' } |
| 88 | + |
| 89 | + Mock Add-ZtTestResultDetail { |
| 90 | + param($TestId, $Title, $Status, $Result, $CustomStatus) |
| 91 | + $script:addedResult = $Result |
| 92 | + } |
| 93 | + |
| 94 | + Write-ZtTestError -Test $test -Result $executionResult -ErrorRecord $errorRecord |
| 95 | + |
| 96 | + $executionResult.Error.TargetObject | Should -BeNullOrEmpty |
| 97 | + $script:loggedErrorRecord.TargetObject | Should -BeNullOrEmpty |
| 98 | + $script:addedResult | Should -Not -Match [regex]::Escape($script:canaryToken) |
| 99 | + $script:addedResult | Should -Not -Match 'Authorization:' |
| 100 | + } |
| 101 | + |
| 102 | + It "redacts credential headers at the TestResult persistence boundary" { |
| 103 | + $unsafeResult = @( |
| 104 | + 'Authorization: Bearer example-canary-token' |
| 105 | + 'Cookie: session=example-cookie' |
| 106 | + 'X-Api-Key: example-api-key' |
| 107 | + 'https://example.test/callback?access_token=example-access-token&client_secret=example-client-secret&sig=example-signature' |
| 108 | + ) -join "`n" |
| 109 | + |
| 110 | + Add-ZtTestResultDetail -TestId '99999' -Title 'Canary test' -Description 'Canary description' -Status $false -Result $unsafeResult -CustomStatus Error |
| 111 | + |
| 112 | + $storedResult = $script:__ZtSession.TestResultDetail.Value['99999'].TestResult |
| 113 | + $storedResult | Should -Not -Match 'example-canary-token|example-cookie|example-api-key|example-access-token|example-client-secret|example-signature' |
| 114 | + $storedResult | Should -Match '<redacted>' |
| 115 | + } |
| 116 | + |
| 117 | + It "writes only the safe error summary to optional test logs" { |
| 118 | + $errorRecord = New-CanaryErrorRecord |
| 119 | + $safeError = New-ZtSafeErrorRecord -ErrorRecord $errorRecord |
| 120 | + $logsPath = Join-Path $TestDrive 'logs' |
| 121 | + $test = [PSCustomObject]@{ TestID = '99999'; Title = 'Canary test' } |
| 122 | + $executionResult = [PSCustomObject]@{ |
| 123 | + TestID = '99999' |
| 124 | + Test = $test |
| 125 | + Success = $false |
| 126 | + TimedOut = $false |
| 127 | + Duration = [TimeSpan]::Zero |
| 128 | + Start = Get-Date |
| 129 | + End = Get-Date |
| 130 | + Error = $safeError |
| 131 | + Messages = @() |
| 132 | + } |
| 133 | + |
| 134 | + Write-ZtTestLog -Result $executionResult -LogsPath $logsPath |
| 135 | + |
| 136 | + $logContent = Get-Content (Join-Path $logsPath '2-Tests/99999.md') -Raw |
| 137 | + $logContent | Should -Not -Match [regex]::Escape($script:canaryToken) |
| 138 | + $logContent | Should -Not -Match 'Authorization:' |
| 139 | + } |
| 140 | +} |
0 commit comments