返回 DeepSeek-Reasonix
test-windows-upgrade-startup.test.ps1
根目录 / scripts / test-windows-upgrade-startup.test.ps1
1 $ErrorActionPreference = 'Stop'
2 . (Join-Path $PSScriptRoot 'test-windows-upgrade-startup.ps1')
3 . (Join-Path $PSScriptRoot 'windows-upgrade-ui-evidence.ps1')
4
5 function Assert-True($value, [string]$message) { if (-not $value) { throw $message } }
6 function Assert-Throws([scriptblock]$action, [string]$message) {
7 $caught = $false
8 try { & $action } catch { $caught = $true }
9 Assert-True $caught $message
10 }
11
12 $testRoot = Join-Path ([IO.Path]::GetTempPath()) ('reasonix-upgrade-contract-' + [guid]::NewGuid().ToString('N'))
13 $savedHome = $HOME
14 try {
15 $release = Join-Path $testRoot 'install/releases/test/app/resources'
16 New-Item -ItemType Directory -Path $release -Force | Out-Null
17 $application = Join-Path $testRoot 'install/Reasonix.exe'
18 $builder = Join-Path $testRoot 'fixture.exe'
19 Set-Content -LiteralPath $application -Value 'mock launcher'
20 Set-Content -LiteralPath $builder -Value 'mock builder'
21 $version = 'v1.38.9-5'
22 $commit = '0123456789abcdef0123456789abcdef01234567'
23 @{activeVersion=$version; activeDir='releases/test'} | ConvertTo-Json | Set-Content -LiteralPath (Join-Path $testRoot 'install/current.json')
24 $buildPath = Join-Path $release 'build.json'
25 @{version=$version; commit=$commit} | ConvertTo-Json | Set-Content -LiteralPath $buildPath
26 $script:calls = [Collections.Generic.List[string]]::new()
27 $script:failPhase = ''
28 # Only replace native process boundaries; run the real evidence orchestration.
29 function Invoke-UpgradeFixture([string]$builder, [string[]]$arguments) {
30 $mode = $arguments[1]
31 $fixtureHome = $arguments[3]
32 $report = $arguments[5]
33 Assert-True ($fixtureHome -ne $HOME -and $fixtureHome.Contains('home # %20 中文')) 'Fixture must use a disposable home.'
34 if ($mode -eq 'create') {
35 $script:calls.Add('create')
36 @{visibleText='assistant-only marker'} | ConvertTo-Json | Set-Content -LiteralPath $report
37 } else {
38 $phase = $arguments[7]
39 $script:calls.Add("verify-$phase")
40 if ($script:failPhase -eq $phase) { throw 'Injected verification failure' }
41 }
42 }
43 function Invoke-UpgradeStartup([string]$installRoot, [string]$version, [string]$fixtureHome, [string]$text, [string]$evidence, [bool]$prepareHistorical) {
44 Assert-True ($text -eq 'assistant-only marker') 'Startup must require the assistant marker.'
45 Assert-True ($prepareHistorical -eq ((Split-Path $evidence -Leaf) -eq 'restart')) 'The first launch must show legacy history unprepared; only the restart may import it.'
46 $script:calls.Add('startup-' + (Split-Path $evidence -Leaf))
47 }
48 $evidence = Join-Path $testRoot 'success'
49 Invoke-WindowsUpgradeAcceptance $application $builder $version $evidence
50 $result = Get-Content -LiteralPath (Join-Path $evidence 'result.json') -Raw | ConvertFrom-Json
51 Assert-True ($HOME -eq $savedHome) 'Acceptance changed the PowerShell home.'
52 Assert-True ($result.sourceSha -eq $commit) 'Evidence must read build.json.commit.'
53 Assert-True ($result.applicationSha256 -eq (Get-FileHash -Algorithm SHA256 -LiteralPath $application).Hash) 'Evidence must hash the tested application.'
54 Assert-True (($script:calls -join ',') -eq 'create,startup-first,verify-first,startup-restart,verify-restart') 'Upgrade and restart must both be checked in order.'
55
56 foreach ($invalidCommit in @('', 'unknown', '0123456')) {
57 @{version=$version; commit=$invalidCommit; sourceSha=$commit} | ConvertTo-Json | Set-Content -LiteralPath $buildPath
58 $badEvidence = Join-Path $testRoot ('invalid-' + [guid]::NewGuid().ToString('N'))
59 Assert-Throws { Invoke-WindowsUpgradeAcceptance $application $builder $version $badEvidence } 'Missing/invalid commit must fail closed.'
60 Assert-True (-not (Test-Path -LiteralPath $badEvidence)) 'Invalid package must not get acceptance evidence.'
61 }
62 @{version=$version; commit=$commit} | ConvertTo-Json | Set-Content -LiteralPath $buildPath
63 foreach ($phase in @('first', 'restart')) {
64 $script:failPhase = $phase
65 $badEvidence = Join-Path $testRoot "failure-$phase"
66 Assert-Throws { Invoke-WindowsUpgradeAcceptance $application $builder $version $badEvidence } 'Data verification failure must propagate.'
67 Assert-True (-not (Test-Path -LiteralPath (Join-Path $badEvidence 'result.json'))) 'Failed verification cannot publish a pass.'
68 }
69
70 # Model the native tree enumeration boundary. Selection and visibility checks
71 # below use the same function as packaged Windows acceptance.
72 function Get-UpgradeUIDescendants($element) {
73 foreach ($child in $element.Children) { $child; Get-UpgradeUIDescendants $child }
74 }
75 function New-UIElement([string]$name, [string]$id='', [bool]$offscreen=$false, [object[]]$children=@()) {
76 return [pscustomobject]@{
77 Current=[pscustomobject]@{Name=$name; AutomationId=$id; IsOffscreen=$offscreen; BoundingRectangle=[pscustomobject]@{Width=200; Height=20}}
78 Children=$children
79 }
80 }
81 $marker = 'assistant-only marker'
82 $script:clicked = 0
83 function Invoke-UpgradeUIButton($element) { $script:clicked++ }
84 $pending = New-UIElement 'Import and open' 'reasonix-prepare-restored-session'
85 $pendingRoot = New-UIElement '' '' $false @($pending)
86 $pending.Current.IsOffscreen = $true
87 Assert-True (-not (Invoke-PendingHistoricalSession $pendingRoot)) 'Hidden preparation actions must not be clicked.'
88 $pending.Current.IsOffscreen = $false
89 Assert-True (Invoke-PendingHistoricalSession $pendingRoot) 'Explicit preparation must invoke the actual pending-session action.'
90 Assert-True ($script:clicked -eq 1) 'Invoke only the visible action.'
91 $body = New-UIElement $marker
92 $transcript = New-UIElement '' 'reasonix-chat-transcript-upgrade-tab' $false @($body)
93 $sidebar = New-UIElement $marker 'sidebar-topic'
94 $root = New-UIElement '' '' $false @($sidebar)
95 Assert-True (-not (Test-VisibleUpgradeHistory $root $marker)) 'A matching sidebar title must not pass.'
96 $root.Children = @($sidebar, $transcript)
97 Assert-True (Test-VisibleUpgradeHistory $root $marker) 'Visible transcript assistant body should pass.'
98 $root.Children = @($sidebar, $transcript, $pending)
99 Assert-True (-not (Test-VisibleUpgradeHistory $root $marker)) 'Prepared content must not retain the import action.'
100 Assert-True (Test-VisibleUpgradeHistory $root $marker -AllowPendingSource) 'Unprepared legacy history may show the import action beside its body.'
101 $root.Children = @($sidebar, $pending)
102 Assert-True (-not (Test-VisibleUpgradeHistory $root $marker -AllowPendingSource)) 'The import action alone is not visible history.'
103 $root.Children = @($sidebar, $transcript)
104 $failure = New-UIElement 'Failed to load conversation history. Previous content was kept when available — retry to try again.'
105 $transcript.Children = @($body, $failure)
106 Assert-True (-not (Test-VisibleUpgradeHistory $root $marker)) 'Visible history with an obsolete failure notice must not pass.'
107 $transcript.Children = @($body)
108 $recovery = New-UIElement 'Loading history' 'reasonix-session-recovery-test'
109 $root.Children = @($sidebar, $transcript, $recovery)
110 Assert-True (-not (Test-VisibleUpgradeHistory $root $marker)) 'History behind an unresolved recovery banner must not pass.'
111 $recovery.Current.IsOffscreen = $true
112 Assert-True (Test-VisibleUpgradeHistory $root $marker) 'An offscreen recovery surface does not block the visible session.'
113 $root.Children = @($sidebar, $transcript)
114 $body.Current.IsOffscreen = $true
115 Assert-True (-not (Test-VisibleUpgradeHistory $root $marker)) 'Offscreen history must not pass.'
116 $body.Current.IsOffscreen = $false
117 $transcript.Current.IsOffscreen = $true
118 Assert-True (-not (Test-VisibleUpgradeHistory $root $marker)) 'Hidden transcript must not pass.'
119 $transcript.Current.IsOffscreen = $false
120 $body.Current.BoundingRectangle.Width = 0
121 Assert-True (-not (Test-VisibleUpgradeHistory $root $marker)) 'Zero-size history must not pass.'
122 $body.Current.BoundingRectangle.Width = 200
123 $body.Current.Name = 'unrelated body'
124 Assert-True (-not (Test-VisibleUpgradeHistory $root $marker)) 'Matching text outside the transcript must not pass.'
125
126 # Exercise the real wait owner with a virtual monotonic clock. Slow import
127 # and lazy body exposure are allowed without slowing this contract test.
128 $script:upgradeWaitNow = 0
129 function Get-UpgradeUITimeMilliseconds { return $script:upgradeWaitNow }
130 function Wait-UpgradeUIPoll { $script:upgradeWaitNow += 250 }
131 $script:upgradeWaitRoot = $root
132 $slowBody = {
133 $script:upgradeWaitRoot.Children = if ($script:clicked -eq 0) { @($pending) } else { @($transcript) }
134 $body.Current.Name = if ($script:upgradeWaitNow -ge 30000) { $marker } else { 'body pending' }
135 return $script:upgradeWaitRoot
136 }
137 $script:clicked = 0
138 $oldBudget = Wait-VisibleUpgradeHistory -ReadRoot $slowBody -Text $marker -PrepareHistoricalSession -TimeoutSeconds 20
139 Assert-True (-not $oldBudget.Found) 'The old fixed budget rejects a correct slow import.'
140 $script:upgradeWaitNow = 0
141 $script:clicked = 0
142 $observed = Wait-VisibleUpgradeHistory -ReadRoot $slowBody -Text $marker -PrepareHistoricalSession
143 Assert-True ($observed.Found -and $observed.ElapsedMilliseconds -eq 30000) 'Functional acceptance must observe the completed slow import.'
144 Assert-True ($script:clicked -eq 1) 'Polling must never execute import twice.'
145
146 $script:upgradeWaitNow = 0
147 $atDeadline = Wait-VisibleUpgradeHistory -Text $marker -TimeoutSeconds 20 -ReadRoot {
148 $script:upgradeWaitRoot.Children = @($transcript)
149 $body.Current.Name = if ($script:upgradeWaitNow -ge 20000) { $marker } else { 'body pending' }
150 return $script:upgradeWaitRoot
151 }
152 Assert-True ($atDeadline.Found -and $atDeadline.ElapsedMilliseconds -eq 20000) 'The deadline must use a final fresh observation, not the previous empty sample.'
153
154 $script:upgradeWaitNow = 0
155 $body.Current.Name = 'never restored'
156 $missing = Wait-VisibleUpgradeHistory -ReadRoot { $script:upgradeWaitRoot } -Text $marker -TimeoutSeconds 1
157 Assert-True (-not $missing.Found -and $missing.ElapsedMilliseconds -eq 1000) 'Missing history still fails within the bounded wait.'
158 $script:upgradeWaitNow = 0
159 $script:clicked = 0
160 $body.Current.Name = $marker
161 $script:upgradeWaitRoot.Children = @($transcript)
162 $alreadyReadable = Wait-VisibleUpgradeHistory -ReadRoot { $script:upgradeWaitRoot } -Text $marker -PrepareHistoricalSession -TimeoutSeconds 1
163 Assert-True ($alreadyReadable.Found -and $alreadyReadable.Prepared -and $script:clicked -eq 0) 'Readable history must pass without forcing runtime activation.'
164 $script:upgradeWaitRoot.Children = @($transcript, $pending)
165 $unprepared = Wait-VisibleUpgradeHistory -ReadRoot { $script:upgradeWaitRoot } -Text $marker -PrepareHistoricalSession -TimeoutSeconds 1
166 Assert-True (-not $unprepared.Found) 'A matching body cannot bypass a still-pending preparation action.'
167 $script:clicked = 0
168 $legacyView = Wait-VisibleUpgradeHistory -ReadRoot { $script:upgradeWaitRoot } -Text $marker -TimeoutSeconds 1
169 Assert-True ($legacyView.Found -and $script:clicked -eq 0) 'Without preparation the legacy body passes beside the import action and nothing is imported.'
170 Write-Host 'Windows upgrade orchestration and UI evidence contracts passed (mocked native boundaries).'
171 } finally {
172 Remove-Item -LiteralPath $testRoot -Recurse -Force -ErrorAction SilentlyContinue
173 }
174
174 lines Plain Text