diff --git a/.github/scripts/CiScanReconcile.Core.Tests.ps1 b/.github/scripts/CiScanReconcile.Core.Tests.ps1 new file mode 100644 index 000000000000..eb918f1141b3 --- /dev/null +++ b/.github/scripts/CiScanReconcile.Core.Tests.ps1 @@ -0,0 +1,2257 @@ +#!/usr/bin/env pwsh +#Requires -Modules Pester +<# +.SYNOPSIS + Pester tests for CiScanReconcile.Core.ps1 — the pure decision core. + +.DESCRIPTION + Every test here is fully offline and deterministic: no network, no `gh`, no AzDO. + Nothing in this file can touch a real GitHub issue. + + It does read from disk, and deliberately. The dot-source below and the static + invariants further down open CiScanReconcile.Core.ps1 and Invoke-CiScanReconcile.ps1 + with Get-Content to assert structural properties of the production source -- those + reads are the strongest tests in this file, not an incidental dependency. The + guarantee is "reaches no network and mutates nothing, anywhere", not "performs no + I/O". Both halves of that sentence are asserted below rather than asked for on trust, + because this suite is safety evidence for a workflow that can write to issues. + +.EXAMPLE + Invoke-Pester ./CiScanReconcile.Core.Tests.ps1 -Output Detailed +#> + +BeforeAll { + . (Join-Path $PSScriptRoot 'CiScanReconcile.Core.ps1') + + $script:Net11 = Get-CiScanTwinConfig -Label 'ci-scan-net11' + $script:Main = Get-CiScanTwinConfig -Label 'ci-scan' + $script:Now = [datetime]::Parse('2026-08-01T00:00:00Z').ToUniversalTime() + + $script:GoodFingerprint = 'ci-scan-net11|net11.0|maui-pr-uitests|issue32983 bottomsheetdetentheight|system.timeoutexception|controls (v18.5) collectionview' + + function New-TestIssue { + param( + [int]$Number = 100, + [string]$Title = '[ci-scan-net11] UI test times out', + [string]$Body = '', + [string[]]$Labels = @('ci-scan-net11'), + [string]$Creator = 'app/github-actions', + [string]$CreatedAt = '2026-06-01T00:00:00Z', + [object]$Milestone = $null, + [object[]]$Assignees = @() + ) + return [pscustomobject]@{ + number = $Number + title = $Title + body = $Body + labels = @($Labels | ForEach-Object { [pscustomobject]@{ name = $_ } }) + user = [pscustomobject]@{ login = $Creator } + created_at = $CreatedAt + milestone = $Milestone + assignees = @($Assignees) + state = 'open' + } + } + + function New-CanonicalBody { + param( + [string]$Fingerprint = $script:GoodFingerprint, + [string]$Pipeline = 'maui-pr-uitests', + [int]$BuildId = 1517702, + [string]$Occurrences = '3 in last 10 builds', + [string[]]$Legs = @('`Controls (v18.5) CollectionView` — iOS v18.5 simulator'), + [string]$StateJson = $null + ) + $legLines = ($Legs | ForEach-Object { "- $_" }) -join "`n" + $body = @" + + +## Summary +Something failed. + +## Build Information +- **Pipeline**: $Pipeline (ID 313) +- **Build ID**: $BuildId +- **Branch**: net11.0 +- **Occurrences**: $Occurrences + +## Affected Legs +$legLines + +## Error Message +boom +"@ + if ($StateJson) { $body += "`n`n" } + return $body + } + + function New-StateJson { + param( + [int[]]$Absent = @(), + [int[]]$Present = @(), + [string]$Label = 'ci-scan-net11', + [string]$Branch = 'net11.0', + [string]$Pipeline = 'maui-pr-uitests', + [string]$ClockStart = '2026-06-01T00:00:00Z', + [string]$LastPresent = $null, + [int]$Version = 1 + ) + $o = [ordered]@{ + v = $Version; label = $Label; branch = $Branch; pipeline = $Pipeline + absent_builds = @($Absent); present_builds = @($Present) + clock_start_at = $ClockStart; candidate_notified = $false; runs = 5 + } + if ($LastPresent) { $o.last_present_at = $LastPresent } + return ($o | ConvertTo-Json -Compress) + } + + function New-Coverage { + param([int[]]$Verified = @(), [switch]$Unverifiable, [string]$Reason = '') + return @{ VerifiedAbsentBuilds = @($Verified); Unverifiable = [bool]$Unverifiable; Reason = $Reason } + } +} + +Describe 'Get-CiScanTwinConfig' { + It 'binds each label to exactly one branch' { + (Get-CiScanTwinConfig -Label 'ci-scan').Branch | Should -Be 'main' + (Get-CiScanTwinConfig -Label 'ci-scan-net11').Branch | Should -Be 'net11.0' + } + It 'rejects an unknown label rather than inventing a config' { + # Pinned to the message, not just to throwing. A bare `-Throw` is satisfied by any + # refusal, including a StrictMode property error from a half-broken lookup -- which + # is the failure this test exists to distinguish from a deliberate rejection. + { Get-CiScanTwinConfig -Label 'ci-scan-evil' } | + Should -Throw -ExpectedMessage '*Unknown ci-scan twin label*' + } + It 'returns a copy so callers cannot poison the shared table' { + $a = Get-CiScanTwinConfig -Label 'ci-scan' + $a.Branch = 'attacker-branch' + (Get-CiScanTwinConfig -Label 'ci-scan').Branch | Should -Be 'main' + } +} + +Describe 'Test-CiScanFingerprint' { + It 'accepts a well-formed fingerprint for the matching twin' { + $r = Test-CiScanFingerprint -Fingerprint $script:GoodFingerprint -Config $script:Net11 + $r | Should -Not -BeNullOrEmpty + $r.Pipeline | Should -Be 'maui-pr-uitests' + } + It 'rejects a net11 fingerprint when reconciling the main twin' { + Test-CiScanFingerprint -Fingerprint $script:GoodFingerprint -Config $script:Main | Should -BeNullOrEmpty + } + It 'rejects a branch that does not belong to the label' { + $fp = 'ci-scan-net11|main|maui-pr|a|b|c' + Test-CiScanFingerprint -Fingerprint $fp -Config $script:Net11 | Should -BeNullOrEmpty + } + It 'rejects an unconfigured pipeline' { + Test-CiScanFingerprint -Fingerprint 'ci-scan-net11|net11.0|maui-evil|a|b|c' -Config $script:Net11 | Should -BeNullOrEmpty + } + It 'rejects the wrong number of fields' { + Test-CiScanFingerprint -Fingerprint 'ci-scan-net11|net11.0|maui-pr|a|b' -Config $script:Net11 | Should -BeNullOrEmpty + Test-CiScanFingerprint -Fingerprint 'ci-scan-net11|net11.0|maui-pr|a|b|c|d' -Config $script:Net11 | Should -BeNullOrEmpty + } + It 'rejects empty fields' { + Test-CiScanFingerprint -Fingerprint 'ci-scan-net11|net11.0|maui-pr||b|c' -Config $script:Net11 | Should -BeNullOrEmpty + } + It 'rejects a Cyrillic homoglyph label (charset is ASCII-only by design)' { + # U+0441 CYRILLIC SMALL LETTER ES looks identical to ASCII 'c'. + $spoofed = ([char]0x0441) + 'i-scan-net11|net11.0|maui-pr|a|b|c' + Test-CiScanFingerprint -Fingerprint $spoofed -Config $script:Net11 | Should -BeNullOrEmpty + } + It 'rejects uppercase (fingerprints are lowercase by construction)' { + Test-CiScanFingerprint -Fingerprint 'CI-SCAN-NET11|net11.0|maui-pr|a|b|c' -Config $script:Net11 | Should -BeNullOrEmpty + } + It 'rejects markup that could break out of the HTML comment' { + Test-CiScanFingerprint -Fingerprint 'ci-scan-net11|net11.0|maui-pr|a|b|