107 lines
4.4 KiB
Python
107 lines
4.4 KiB
Python
"""Guards on the installer's durable install record.
|
|
|
|
The record replaced a set of guesses. "Is this a re-run of MY install?" used to
|
|
be answered from whatever the machine looked like - a MySQL service exists, the
|
|
database has tables, the site exists - and none of those say who created them.
|
|
A retry after a failed first install was therefore taken for an upgrade of a
|
|
working system, which dead-ended the retry the wizard invites the operator to
|
|
run.
|
|
|
|
Getting it wrong is asymmetric. Answering "still first provisioning" for a
|
|
server holding real data would let `prune-schema --force` drop live tables. The
|
|
behavioural checks live in the PowerShell harness next to the installer, run
|
|
here when pwsh is available; the text assertions below hold everywhere, because
|
|
CI does not necessarily have pwsh and these are the invariants worth failing a
|
|
build over.
|
|
"""
|
|
|
|
import shutil
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
REPO = Path(__file__).resolve().parent.parent
|
|
INSTALLER = REPO / 'deploy' / 'windows' / 'installer' / 'shopdb-install.ps1'
|
|
HARNESS = REPO / 'deploy' / 'windows' / 'installer' / 'tests' / 'test-install-state.ps1'
|
|
|
|
pytestmark = pytest.mark.skipif(
|
|
not INSTALLER.is_file(),
|
|
reason='installer not in this checkout')
|
|
|
|
|
|
def installer_text():
|
|
return INSTALLER.read_text(encoding='utf-8', errors='replace')
|
|
|
|
|
|
def test_prune_force_is_gated_on_first_provisioning():
|
|
"""--force drops tables that contain rows.
|
|
|
|
It is correct on a first install, where core migrations seed a few plugin
|
|
reference tables, and destructive on an upgrade, where those rows are the
|
|
site's data. The gate must therefore test BOTH "database was empty" and the
|
|
install record, never the database alone.
|
|
"""
|
|
text = installer_text()
|
|
assert "'prune-schema','--yes','--force'" in text, 'the forcing call moved or was renamed'
|
|
guard = "if ((-not $script:DbWasEmpty) -and (-not $firstRun)) {"
|
|
assert guard in text, (
|
|
'the prune gate no longer consults the install record. Forcing on the '
|
|
'strength of an empty database alone is what made every retry of a '
|
|
'failed first install fail at stage 3.')
|
|
|
|
|
|
def test_first_provisioning_comes_from_the_record_not_the_database():
|
|
text = installer_text()
|
|
assert 'function Test-FirstProvisioning' in text
|
|
assert '$firstRun = Test-FirstProvisioning' in text, (
|
|
'stage 3 must ask the record, not infer from table counts')
|
|
|
|
|
|
def test_missing_record_takes_the_safe_side():
|
|
"""An install predating the record has a version stamp and probably data.
|
|
|
|
Treating that as a first run would arm prune --force against it.
|
|
"""
|
|
text = installer_text()
|
|
assert "'predates-this-record'" in text, (
|
|
'the fallback for an unrecorded but stamped install is gone; without it '
|
|
'an established server can be classified as first provisioning')
|
|
|
|
|
|
def test_created_items_returns_an_array_when_empty():
|
|
"""A zero-length array returned from a PowerShell function unrolls to $null.
|
|
|
|
$null.Count is then a terminating error under Set-StrictMode -Version 2.0 -
|
|
the same fault that made bundle verification fail on every install. The
|
|
leading comma is what prevents it.
|
|
"""
|
|
text = installer_text()
|
|
assert 'return ,@()' in text, 'Get-CreatedItems lost its comma protection'
|
|
assert 'return ,@(Get-StateValue $created $Kind' in text, (
|
|
'the populated return path lost its comma protection')
|
|
|
|
|
|
def test_record_is_written_before_any_stage_runs():
|
|
"""Written at the START of provisioning, not the end.
|
|
|
|
A run that dies halfway is exactly the run whose retry needs the record.
|
|
"""
|
|
text = installer_text()
|
|
assert "if ($Stage -ne 'uninstall') { Initialize-InstallState }" in text
|
|
start = text.index('Initialize-InstallState }')
|
|
switch = text.index('switch ($Stage)', start - 400)
|
|
assert start < switch, 'the record must be initialised before the stage switch'
|
|
|
|
|
|
@pytest.mark.skipif(shutil.which('pwsh') is None, reason='pwsh not installed')
|
|
def test_install_state_behaviour():
|
|
"""Run the PowerShell harness: retries, upgrades, corrupt and foreign records."""
|
|
assert HARNESS.is_file(), 'the PowerShell harness is missing'
|
|
result = subprocess.run(
|
|
['pwsh', '-NoProfile', '-File', str(HARNESS)],
|
|
capture_output=True, text=True, timeout=300)
|
|
assert result.returncode == 0, (
|
|
'install-record harness failed:\n' + result.stdout + result.stderr)
|
|
assert 'all checks passed' in result.stdout
|