"""The installer wizard's pre-ticked feature list must match the plugin manifests. The wizard cannot read manifest.json - Inno's Pascal Script has no JSON parser - so the default set is a hardcoded list in ShopDBFlask.iss. It drifted: two plugins that ship "default_enabled": false were pre-ticked, so every site taking the defaults installed and enabled them against their own manifests. A hardcoded list is fine; a hardcoded list nobody checks is not. """ import json import re from pathlib import Path import pytest REPO = Path(__file__).resolve().parents[1] ISS = REPO / 'deploy' / 'windows' / 'installer' / 'ShopDBFlask.iss' PLUGINS = REPO / 'plugins' pytestmark = pytest.mark.skipif(not ISS.exists(), reason='installer not in this tree') def manifests_defaulting_off(): off = set() for manifest in PLUGINS.glob('*/manifest.json'): try: data = json.loads(manifest.read_text()) except (ValueError, OSError): continue if data.get('default_enabled') is False: off.add(manifest.parent.name) return off def wizard_excludes(): """The names PluginDefault returns False for.""" body = re.search(r'function PluginDefault.*?\nend;', ISS.read_text(), re.S) assert body, 'PluginDefault not found in ShopDBFlask.iss' return set(re.findall(r"Name <> '([a-z_]+)'", body.group(0))) def test_wizard_defaults_match_the_manifests(): off = manifests_defaulting_off() assert off, 'no plugin declares default_enabled false - has the field moved?' assert wizard_excludes() == off, ( 'ShopDBFlask.iss PluginDefault disagrees with the manifests.\n' ' manifests default_enabled=false: %s\n' ' wizard leaves unticked: %s' % (sorted(off), sorted(wizard_excludes()))) def test_every_wizard_exclusion_is_a_real_plugin(): """A typo in the .iss list silently pre-ticks the plugin it meant to exclude.""" for name in wizard_excludes(): assert (PLUGINS / name / 'manifest.json').exists(), ( '%s is excluded by the wizard but has no manifest' % name) # The preflight blocks the wizard on any FAIL. That is correct for something the # operator must go and fix, and wrong for anything the installer carries in its # own bundle - there, blocking stops the wizard over something it was about to do # itself, with no way forward. BUNDLE_SUPPLIED = [ 'HttpPlatformHandler', # httpplatformhandler\*.msi, installed by stage 4 'URL Rewrite', # urlrewrite\*.msi, installed by stage 4 ] PREFLIGHT = REPO / 'deploy' / 'windows' / 'installer' / 'shopdb-preflight.ps1' def test_nothing_the_bundle_supplies_is_a_blocker(): """Regression: HttpPlatformHandler was a FAIL, so a server without it could not get past the preflight page - to install the very thing that was missing.""" text = PREFLIGHT.read_text(encoding='utf-8', errors='replace') offenders = [] for line in text.splitlines(): if "'FAIL'" not in line: continue for component in BUNDLE_SUPPLIED: if component.lower() in line.lower(): offenders.append(line.strip()) assert not offenders, ( 'the installer supplies these, so they must not block the wizard:\n %s' % '\n '.join(offenders))