131 lines
5.1 KiB
PowerShell
131 lines
5.1 KiB
PowerShell
# Tests the installer's durable install record.
|
|
#
|
|
# The record answers one question: "is this a re-run of MY install?". Getting it
|
|
# wrong is destructive in one direction - answering "first provisioning" for a
|
|
# server holding real data lets prune-schema --force loose on it - and a dead
|
|
# end in the other, which is what it was built to fix.
|
|
#
|
|
# Run directly: pwsh -File test-install-state.ps1
|
|
# pytest runs it through tests/test_installer_state.py wherever pwsh exists.
|
|
|
|
Set-StrictMode -Version 2.0
|
|
$ErrorActionPreference = 'Stop'
|
|
|
|
$Here = Split-Path -Parent $MyInvocation.MyCommand.Path
|
|
$Installer = Join-Path (Split-Path -Parent $Here) 'shopdb-install.ps1'
|
|
if (-not (Test-Path $Installer)) { throw "installer not found at $Installer" }
|
|
|
|
$Sandbox = Join-Path ([System.IO.Path]::GetTempPath()) ('shopdb-state-' + [System.Guid]::NewGuid().ToString('N'))
|
|
|
|
# Load ONLY the state block, so the rest of the installer does not run. Bounded
|
|
# by the same markers the installer uses, and it fails loudly if either moves
|
|
# rather than silently testing nothing.
|
|
$lines = Get-Content $Installer
|
|
$start = ($lines | Select-String -Pattern '^\$script:StateFile ' | Select-Object -First 1)
|
|
$end = ($lines | Select-String -Pattern '^function Invoke-Native' | Select-Object -First 1)
|
|
if (-not $start -or -not $end) { throw 'could not locate the install-record block in shopdb-install.ps1' }
|
|
$block = $lines[($start.LineNumber - 1)..($end.LineNumber - 2)] -join "`n"
|
|
if ($block -notmatch 'function Test-FirstProvisioning') { throw 'extracted block does not contain the state functions' }
|
|
|
|
# Stand-ins for the surrounding script.
|
|
$script:Created = New-Object System.Collections.ArrayList
|
|
function Write-Log { param($m, $l = 'INFO') }
|
|
function Protect-File { param([string] $Path, [switch] $Directory) }
|
|
function Test-SamePath {
|
|
param([string] $A, [string] $B)
|
|
if (-not $A) { return -not $B }
|
|
return ($A.TrimEnd('\', '/').ToLowerInvariant() -eq $B.TrimEnd('\', '/').ToLowerInvariant())
|
|
}
|
|
if (-not $env:ProgramData) { $env:ProgramData = [System.IO.Path]::GetTempPath() }
|
|
|
|
Invoke-Expression $block
|
|
|
|
$script:Failures = 0
|
|
function Check {
|
|
param([string] $Name, $Expected, $Actual)
|
|
if ($Expected -eq $Actual) {
|
|
Write-Host (" PASS " + $Name)
|
|
} else {
|
|
Write-Host (" FAIL {0}: expected [{1}], got [{2}]" -f $Name, $Expected, $Actual)
|
|
$script:Failures++
|
|
}
|
|
}
|
|
|
|
function Reset-Case {
|
|
param([switch] $Stamped)
|
|
if (Test-Path $Sandbox) { Remove-Item $Sandbox -Recurse -Force }
|
|
New-Item -ItemType Directory -Path $Sandbox -Force | Out-Null
|
|
$script:AppRoot = $Sandbox
|
|
$script:StateFile = Join-Path $Sandbox 'install-state.json'
|
|
$script:InstallState = $null
|
|
if ($Stamped) { Set-Content (Join-Path $Sandbox '.installed-version') '0.7.0' }
|
|
}
|
|
|
|
Write-Host 'brand new server'
|
|
Reset-Case
|
|
Initialize-InstallState
|
|
Check 'first provisioning' $true (Test-FirstProvisioning)
|
|
Check 'record written' $true (Test-Path $script:StateFile)
|
|
|
|
Write-Host 'retry of a failed first install'
|
|
$script:InstallState = $null
|
|
Initialize-InstallState
|
|
Check 'still first provisioning' $true (Test-FirstProvisioning)
|
|
|
|
Write-Host 'genuine upgrade after a completed install'
|
|
Complete-InstallState
|
|
$script:InstallState = $null
|
|
Initialize-InstallState
|
|
Check 'no longer first provisioning' $false (Test-FirstProvisioning)
|
|
|
|
Write-Host 'install predating the record - must take the safe side'
|
|
Reset-Case -Stamped
|
|
Initialize-InstallState
|
|
Check 'treated as established' $false (Test-FirstProvisioning)
|
|
|
|
Write-Host 'record naming a different directory'
|
|
Reset-Case
|
|
Initialize-InstallState
|
|
$other = Get-Content $script:StateFile -Raw | ConvertFrom-Json
|
|
$other.approot = 'C:\somewhere-else'
|
|
($other | ConvertTo-Json -Depth 6) | Set-Content $script:StateFile
|
|
$script:InstallState = $null
|
|
Check 'foreign record rejected' $null (Get-InstallState)
|
|
|
|
Write-Host 'corrupt record'
|
|
Reset-Case
|
|
Set-Content $script:StateFile '{ this is not json'
|
|
$script:InstallState = $null
|
|
Check 'corrupt record rejected' $null (Get-InstallState)
|
|
Reset-Case
|
|
Set-Content $script:StateFile '{ this is not json'
|
|
Initialize-InstallState
|
|
Check 'recovers and rewrites' $true ($null -ne $script:InstallState)
|
|
|
|
Write-Host 'created items survive a crash'
|
|
Reset-Case
|
|
Initialize-InstallState
|
|
Track 'site' 'shopdb-flask'
|
|
Track 'apppool' 'shopdbflask'
|
|
Track 'site' 'shopdb-flask'
|
|
$script:InstallState = $null
|
|
Initialize-InstallState
|
|
Check 'site remembered' 'shopdb-flask' ((Get-CreatedItems 'site') -join ',')
|
|
Check 'apppool remembered' 'shopdbflask' ((Get-CreatedItems 'apppool') -join ',')
|
|
# A zero-length array returned from a function unrolls to $null, and $null.Count
|
|
# is fatal under StrictMode. This is the check that catches losing the comma.
|
|
Check 'unknown kind is an empty array' 0 ((Get-CreatedItems 'firewall')).Count
|
|
|
|
Write-Host 'no record at all'
|
|
$script:InstallState = $null
|
|
Check 'empty list, not null' 0 ((Get-CreatedItems 'site')).Count
|
|
Check 'not first provisioning' $false (Test-FirstProvisioning)
|
|
|
|
if (Test-Path $Sandbox) { Remove-Item $Sandbox -Recurse -Force }
|
|
if ($script:Failures -gt 0) {
|
|
Write-Host ("{0} check(s) failed" -f $script:Failures)
|
|
exit 1
|
|
}
|
|
Write-Host 'all checks passed'
|
|
exit 0
|