This commit is contained in:
Simon Daniel Streich committed 2026-10-21 12:06:08 +02:00
1 parent 6c1c651839
commit ee6d5b9b1e
5 files changed
+364 -2

No files matched your search

+154
View File
@@ -0,0 +1,154 @@
#requires -Version 5.1
# Isolated behavioural tests: no real downloads, Scoop updates or scheduled tasks.
$ErrorActionPreference = 'Stop'
$projectRoot = Split-Path $PSScriptRoot -Parent
$fixtureRoot = Join-Path $PSScriptRoot ('.auto-update-test-' + [guid]::NewGuid())
$oldScoop = $env:SCOOP
$oldPath = $env:PATH
$oldFixture = $env:SCOOP_AUTO_UPDATE_TEST_ROOT
$oldFailure = $env:SCOOP_AUTO_UPDATE_TEST_FAILURE
function Assert-True($Condition, [string] $Message) {
if (-not $Condition) { throw $Message }
}
# These stand-ins capture scheduling settings without changing Windows.
function New-ScheduledTaskAction { param($Execute, $Argument) [pscustomobject]$PSBoundParameters }
function New-ScheduledTaskTrigger { param([switch]$Daily, $At) [pscustomobject]$PSBoundParameters }
function New-ScheduledTaskPrincipal { param($UserId, $LogonType, $RunLevel) [pscustomobject]$PSBoundParameters }
function New-ScheduledTaskSettingsSet {
param([switch]$StartWhenAvailable, [switch]$RunOnlyIfNetworkAvailable,
[switch]$AllowStartIfOnBatteries, [switch]$DontStopIfGoingOnBatteries,
$MultipleInstances, $ExecutionTimeLimit)
[pscustomobject]$PSBoundParameters
}
function Register-ScheduledTask {
param($TaskName, $Action, $Trigger, $Principal, $Settings, [switch]$Force, $Description)
$global:AutoUpdateTestTask = [pscustomobject]$PSBoundParameters
}
function Read-Host {
param($Prompt)
$global:AutoUpdateTestAnswers.Dequeue()
}
function Invoke-WebRequest {
param($Uri, $OutFile, [switch]$UseBasicParsing)
Assert-True ($Uri -in @('https://example.test/scoop/raw/branch/master/scripts/auto-update.ps1',
'https://gitea.medelexis.ch/public/scoop/raw/branch/master/scripts/auto-update.ps1')) 'Unexpected download URL.'
if ($global:AutoUpdateTestInvalidDownload) {
'This is not the setup script.' | Set-Content -LiteralPath $OutFile
} else {
@'
param([string] $ScoopRoot)
throw 'The downloaded runner must not execute during setup.'
'@ | Set-Content -LiteralPath $OutFile
}
}
try {
# Spaces in the installation path exercise the scheduled command's quoting.
$scoopRoot = Join-Path $fixtureRoot 'scoop with spaces'
$shims = Join-Path $scoopRoot 'shims'
New-Item -ItemType Directory -Path $shims -Force | Out-Null
$scriptPath = Join-Path $fixtureRoot 'auto-update.ps1'
Copy-Item -LiteralPath (Join-Path $projectRoot 'scripts\auto-update.ps1') -Destination $scriptPath
$env:SCOOP_AUTO_UPDATE_TEST_ROOT = $fixtureRoot
$commandLog = Join-Path $fixtureRoot 'commands.log'
@'
param([Parameter(ValueFromRemainingArguments = $true)][string[]] $ScoopArguments)
Add-Content -LiteralPath (Join-Path $env:SCOOP_AUTO_UPDATE_TEST_ROOT 'commands.log') -Value ($ScoopArguments -join ' ')
if ($env:SCOOP_AUTO_UPDATE_TEST_FAILURE -eq ($ScoopArguments -join ' ')) { exit 1 }
if ($ScoopArguments[0] -eq 'list') {
[pscustomobject]@{ Name = 'elexis'; Info = '' }
[pscustomobject]@{ Name = 'Elexis-test'; Info = 'Held package' }
[pscustomobject]@{ Name = 'myelexis-cli'; Info = '' }
[pscustomobject]@{ Name = 'git'; Info = '' }
[pscustomobject]@{ Name = 'elexis-global'; Info = 'Global install' }
[pscustomobject]@{ Name = 'elexis-broken'; Info = 'Install failed' }
}
exit 0
'@ | Set-Content -LiteralPath (Join-Path $shims 'scoop.ps1') -Encoding UTF8
& $scriptPath -ScoopRoot $scoopRoot
Assert-True ($LASTEXITCODE -eq 0) 'Expected successful update.'
Assert-True (((Get-Content $commandLog) -join '|') -eq 'list|hold elexis|hold Elexis-test|update|update *') 'Expected only local elexis* held before updates.'
Assert-True (Test-Path (Join-Path $fixtureRoot 'auto-update.log')) 'Expected update log.'
foreach ($failure in @('list', 'hold elexis', 'update', 'update *')) {
Remove-Item -LiteralPath $commandLog
$env:SCOOP_AUTO_UPDATE_TEST_FAILURE = $failure
# Expected errors are logged; do not let test ErrorActionPreference terminate the caller.
$ErrorActionPreference = 'Continue'
& $scriptPath -ScoopRoot $scoopRoot 2>$null
$result = $LASTEXITCODE
$ErrorActionPreference = 'Stop'
Assert-True ($result -eq 1) "Expected failure status for $failure."
$commands = @(Get-Content $commandLog)
Assert-True ($commands[-1] -eq $failure) "Must abort immediately after $failure."
}
# Exercise the actual opt-in/download block without running the bucket migration.
$tokens = $null
$errors = $null
$bucketAst = [Management.Automation.Language.Parser]::ParseFile((Join-Path $projectRoot 'scripts\update-bucket.ps1'), [ref]$tokens, [ref]$errors)
Assert-True ($errors.Count -eq 0) 'Invalid bucket script syntax.'
$optInBlock = [scriptblock]::Create((($bucketAst.EndBlock.Statements | Select-Object -Last 2 | ForEach-Object { $_.Extent.Text }) -join "`n"))
$repository = 'https://example.test/scoop'
Remove-Item -LiteralPath $commandLog
$global:AutoUpdateTestAnswers = [Collections.Generic.Queue[string]]::new()
$global:AutoUpdateTestAnswers.Enqueue('n')
& $optInBlock
$downloadDirectory = Join-Path $scoopRoot 'scripts\medelexis'
Assert-True (-not (Test-Path $downloadDirectory)) 'Declining must not download or set up anything.'
$global:AutoUpdateTestAnswers.Enqueue('invalid')
$global:AutoUpdateTestAnswers.Enqueue('Y')
& $optInBlock
$downloadedPath = Join-Path $downloadDirectory 'auto-update.ps1'
Assert-True (Test-Path $downloadedPath) 'Expected downloaded runner.'
$task = $global:AutoUpdateTestTask
Assert-True ($task.Trigger.Daily -and $task.Trigger.At -eq '03:00') 'Expected daily 03:00 schedule.'
Assert-True ($task.Settings.StartWhenAvailable -and $task.Settings.RunOnlyIfNetworkAvailable) 'Expected catch-up and network settings.'
Assert-True ($task.Settings.MultipleInstances -eq 'IgnoreNew') 'Expected overlap protection.'
Assert-True ($task.Principal.LogonType -eq 'Interactive' -and $task.Principal.RunLevel -eq 'Limited') 'Expected normal user session.'
Assert-True ($task.Action.Argument.Contains('-WindowStyle Hidden') -and $task.Action.Argument.Contains('-NonInteractive')) 'Expected background execution.'
Assert-True ($task.Action.Argument.Contains('-File "' + $downloadedPath + '"') -and $task.Action.Argument.Contains('-ScoopRoot "' + $scoopRoot + '"')) 'Expected quoted paths pointing to the downloaded runner.'
Assert-True (-not (Test-Path $commandLog)) 'Setup must not execute Scoop.'
$taskName = $task.TaskName
$global:AutoUpdateTestAnswers.Enqueue('y')
& $optInBlock
Assert-True ($global:AutoUpdateTestTask.TaskName -eq $taskName -and $global:AutoUpdateTestTask.Force) 'Repeated setup must replace the same task.'
$originalDownload = [IO.File]::ReadAllText($downloadedPath)
Remove-Variable AutoUpdateTestTask -Scope Global
$global:AutoUpdateTestInvalidDownload = $true
$global:AutoUpdateTestAnswers.Enqueue('y')
$rejected = $false
try { & $optInBlock } catch { $rejected = $true }
Assert-True $rejected 'Invalid download must be rejected.'
Assert-True ([IO.File]::ReadAllText($downloadedPath) -ceq $originalDownload) 'Invalid download must preserve the existing script.'
Assert-True ($null -eq (Get-Variable AutoUpdateTestTask -Scope Global -ErrorAction SilentlyContinue)) 'Invalid download must not register a task.'
Assert-True (@(Get-ChildItem $downloadDirectory -Filter '*.download').Count -eq 0) 'Temporary downloads must be removed.'
# The additional standalone installer must offer the same setup without migration.
$standaloneSetup = Join-Path $projectRoot 'scripts\setup-auto-update.ps1'
$global:AutoUpdateTestAnswers.Enqueue('n')
& $standaloneSetup -ScoopRoot $scoopRoot
Assert-True ($null -eq (Get-Variable AutoUpdateTestTask -Scope Global -ErrorAction SilentlyContinue)) 'Standalone n must not register a task.'
$global:AutoUpdateTestInvalidDownload = $false
$global:AutoUpdateTestAnswers.Enqueue('y')
& $standaloneSetup -ScoopRoot $scoopRoot
$task = $global:AutoUpdateTestTask
Assert-True ($task.TaskName -eq $taskName -and $task.Force) 'Standalone setup must use the same task.'
Assert-True ($task.Trigger.Daily -and $task.Trigger.At -eq '03:00' -and $task.Settings.StartWhenAvailable) 'Standalone setup must schedule daily 03:00 with catch-up.'
Assert-True ($task.Action.Argument.Contains('-WindowStyle Hidden') -and $task.Action.Argument.Contains('-File "' + $downloadedPath + '"')) 'Standalone task must invoke the downloaded runner hidden.'
Assert-True (-not (Test-Path $commandLog)) 'Standalone setup must not run Scoop or migrations.'
Write-Host 'PASS: schedule, repeat setup, hold filtering, command order, logging, failures, opt-in and download setup.'
} finally {
$env:SCOOP = $oldScoop
$env:PATH = $oldPath
$env:SCOOP_AUTO_UPDATE_TEST_ROOT = $oldFixture
$env:SCOOP_AUTO_UPDATE_TEST_FAILURE = $oldFailure
Remove-Variable AutoUpdateTestTask, AutoUpdateTestAnswers, AutoUpdateTestInvalidDownload -Scope Global -ErrorAction SilentlyContinue
# Delete only this test's unique directory, after checking its absolute parent.
if ((Test-Path -LiteralPath $fixtureRoot) -and
[IO.Path]::GetDirectoryName([IO.Path]::GetFullPath($fixtureRoot)) -eq [IO.Path]::GetFullPath($PSScriptRoot) -and
[IO.Path]::GetFileName($fixtureRoot).StartsWith('.auto-update-test-')) {
Remove-Item -LiteralPath $fixtureRoot -Recurse -Force
}
}