#!/usr/bin/env python3 """REAL PostgreSQL data migration CI smoke with a disposable isolated target. This script deliberately never runs on ambiguous production-looking DB names. Set SITEFORGE_ALLOW_PG_DATA_SMOKE=1 and database suffix _ci/_test. """ # Allow direct `python scripts/name.py` from a fresh source checkout. import sys as _sys from pathlib import Path as _Path _sys.path.insert(0, str(_Path(__file__).resolve().parents[1])) import os import subprocess import sys import tempfile import uuid from pathlib import Path from urllib.parse import unquote,urlsplit from app import db from app.sqlite_pg_transfer import migrate from app.pg_disaster_recovery import RecoveryError url = os.environ.get('SITEFORGE_DATABASE_URL','') name = unquote(urlsplit(url).path.lstrip('/')) if not db.POSTGRES or os.environ.get('SITEFORGE_ALLOW_PG_DATA_SMOKE')!='1' or not name.endswith(('_ci','_test')): raise SystemExit('Requires explicit CI-only PG smoke flag and _ci/_test database') with tempfile.TemporaryDirectory(prefix='sf-v818-ci-') as temp: source=Path(temp)/'siteforge.sqlite3' env=os.environ.copy(); env.pop('SITEFORGE_DATABASE_URL',None) env['SITEFORGE_DATA_DIR']=temp env['SITEFORGE_DB_FILE']=str(source) marker='migration-smoke-'+uuid.uuid4().hex[:10] code=r'''from app import (db,enterprise,factory_api,migration_audit,reusables,release_api, cms_pipeline,v8_workflows,v8_templates,wp_migration_v81,wp_migration_v82, wp_migration_v83,connectors_v84,connectors_v85,studio_v86,site_design_v87,multilingual) import os assert not db.POSTGRES assert not os.path.exists(db.DB_FILE) db.init() for x in (enterprise,factory_api,migration_audit,reusables,release_api, cms_pipeline,v8_workflows,v8_templates,wp_migration_v81,wp_migration_v82, wp_migration_v83,connectors_v84,connectors_v85,studio_v86,site_design_v87,multilingual):x.setup() s=os.environ['V818_MARKER'] with db.connect() as c: c.execute('INSERT INTO sites(id,name,created_at,updated_at) VALUES (?,?,?,?)',(s,'Migration Sample',db.now(),db.now())) for i in range(3): c.execute('INSERT INTO entries(id,site_id,type,title,route,slug,created_at,updated_at) VALUES (?,?,?,?,?,?,?,?)', (s+'e'+str(i),s,'product','Migrated Car '+str(i),'/products/'+str(i)+'/','p'+str(i),db.now(),db.now())) c.execute('INSERT INTO redirects(id,site_id,old_path,new_path) VALUES (?,?,?,?)',(s+'r',s,'/old/','/products/0/')) ''' env['V818_MARKER']=marker subprocess.run([sys.executable,'-c',code],check=True,env=env) try: result=migrate(source,confirmed_offline=True) assert result['verified'] and result['details']['entries']['rows']==3 assert result['details']['redirects']['rows']==1 assert db.one('SELECT COUNT(*) AS n FROM entries WHERE site_id=?',(marker,))['n']==3 assert db.one('SELECT COUNT(*) AS n FROM redirects WHERE site_id=?',(marker,))['n']==1 # Re-running must fail: no duplicate rows, no silent upsert. try:migrate(source,confirmed_offline=True) except RecoveryError as exc: assert 'not empty' in str(exc) else:raise AssertionError('Migration unexpectedly allowed nonempty target') print(f'PASS: actual PostgreSQL transfer {result["tables"]} tables, {result["records"]} records; rerun blocked') finally: with db.connect() as c: c.execute('DELETE FROM sites WHERE id=?',(marker,))