Files

46 lines
4.6 KiB
Python
Raw Permalink Normal View History

import hashlib,json,os,shutil,subprocess,sys,tempfile,zipfile
from pathlib import Path
from application_harness import Application,PHP,ROOT
checks=[]
def check(value,message):
if not value:raise AssertionError(message)
checks.append(message);print('PASS: '+message)
app=Application()
try:
before=app.fixture('fingerprints')
for _ in range(2):subprocess.run(PHP+['bin/migrate-production.php'],cwd=ROOT,env=app.env,check=True,capture_output=True)
check(app.fixture('fingerprints')==before,'Production migration can be rerun without changing existing rows')
result=subprocess.run(PHP+['bin/check-health.php'],cwd=ROOT,env=app.env,capture_output=True)
check(result.returncode==0,'CLI health check confirms database and required schema')
with tempfile.TemporaryDirectory(prefix='hcr-release-check-') as temporary:
output=Path(temporary)/'release.zip'
subprocess.run([sys.executable,'bin/build-release.py',str(output)],cwd=ROOT,check=True,capture_output=True)
with zipfile.ZipFile(output) as archive:
names=archive.namelist();manifest=json.loads(archive.read('release-manifest.json'))
check(not any(name.startswith(('.tools/','.git/','tests/','nbproject/')) or name.endswith('settings.local.xml') for name in names),'Release excludes local credentials, backups, Git metadata, and tests')
check(all(name in names for name in ['classes/LedgerService.php','classes/EmailVerification.php','bin/migrate-production.php','migrations/003_production.sql','js/jquery-3.7.1.min.js']),'Release includes new classes, migration, and updated jQuery')
2026-10-21 20:41:57 -04:00
check(all(name in names for name in ['classes/ClubMemberNotes.php','classes/MemberSummary.php','classes/ClubFormModals.php','js/club-modals.js','classes/MfaSecurityNotifications.php','classes/MfaSetupQr.php','classes/MfaService.php','classes/MfaClock.php','classes/BoundedList.php','classes/ClubResources.php','bin/migrate-security-capacity.php','migrations/005_security_capacity.sql','classes/DatabaseTransaction.php','js/tickets.js','classes/TenantAccess.php','classes/ClubInvitations.php','classes/ClubTickets.php','bin/migrate-tenants.php','migrations/004_tenants.sql','classes/MaintenanceValidation.php','classes/BackupValidation.php','js/rich-text.js','js/suneditor-3.3.3.min.js','css/suneditor-3.3.3.min.css','THIRD-PARTY-NOTICES.md','browser-dependencies.json']) and 'js/ckeditor.js' not in names,'Release includes replacement editor, license, integrity metadata, and new validators')
check(all(hashlib.sha256(archive.read(name)).hexdigest()==digest for name,digest in manifest.items()),'Release manifest verifies every packaged file')
binary=os.environ.get('HCR_DUMP_BINARY') or shutil.which('mariadb-dump')
if not binary:
candidate=Path('C:/Users/JamesRichie/AppData/Local/Temp/hcr-auth-mariadb/mariadb-11.4.5-winx64/bin/mariadb-dump.exe')
if candidate.is_file():binary=str(candidate)
if not binary:raise RuntimeError('Install mariadb-dump to verify backup tooling.')
env=app.env.copy();env['HCR_DUMP_BINARY']=binary
result=subprocess.run(PHP+['bin/backup-database.php',temporary],cwd=ROOT,env=env,capture_output=True,text=True)
if result.returncode:raise RuntimeError(result.stdout+result.stderr)
metadata=json.loads(result.stdout);backup=Path(metadata['backup'])
check(backup.is_file() and hashlib.sha256(backup.read_bytes()).hexdigest()==metadata['sha256'],'Backup scheduler entry point produces a complete checksummed SQL backup')
check(not list(Path(temporary).glob('hcr-connection-*')),'Backup credentials file is removed after completion')
result=subprocess.run(PHP+['bin/backup-database.php',str(ROOT/'.tools')],cwd=ROOT,env=env,capture_output=True)
check(result.returncode!=0,'Backup tool rejects storage within the application tree')
failed_directory=Path(temporary)/'failed-job';failed_directory.mkdir()
env['HCR_DUMP_BINARY']=PHP[0] # A real executable that rejects dump arguments.
result=subprocess.run(PHP+['bin/backup-database.php',str(failed_directory)],cwd=ROOT,env=env,capture_output=True)
check(result.returncode!=0,'Dump process failure produces a failing job exit status')
check(not list(failed_directory.iterdir()),'Failed backup leaves no credentials, partial dump, or success metadata')
for path in ['/.tools/backups/probe.sql','/migrations/003_production.sql','/bin/check-health.php','/xml/settings.xml','/release-manifest.json']:
check(app.request(path)[0] in [403,404],'Development router denies '+path)
finally:app.close()
print(str(len(checks))+' deployment tooling checks passed.')