# 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