"""End-to-end HTTP + Worker smoke test on an isolated disposable SQLite database. Launches an actual uvicorn process, calls authenticated HTTP APIs, runs a real Worker claim + isolated subprocess build, then downloads and checks the archive. Never points at production data or remote deployment services. """ from __future__ import annotations import io import json import os import socket import subprocess import sys import tempfile import time import zipfile from pathlib import Path from urllib.error import URLError from urllib.request import urlopen import httpx ROOT=Path(__file__).resolve().parent.parent def run(): with tempfile.TemporaryDirectory(prefix='siteforge-e2e-') as folder: with socket.socket() as sock: sock.bind(('127.0.0.1',0)); port=sock.getsockname()[1] base=f'http://127.0.0.1:{port}' env=os.environ.copy() env.update(SITEFORGE_ADMIN_PASSWORD='e2e-only-dummy-password-123456', SITEFORGE_SESSION_SECRET='e2e-only-session-secret-long-value-123456', SITEFORGE_DATA_DIR=folder,SITEFORGE_DB_FILE=str(Path(folder)/'siteforge.sqlite3'), SITEFORGE_DATABASE_URL='',SITEFORGE_REDIS_URL='',SITEFORGE_COOKIE_SECURE='false') proc=subprocess.Popen([sys.executable,'-m','uvicorn','app.main:app','--host','127.0.0.1','--port',str(port)], cwd=ROOT,env=env,stdout=subprocess.DEVNULL,stderr=subprocess.PIPE) try: for n in range(120): if proc.poll() is not None:raise RuntimeError('Web startup failed: '+proc.stderr.read().decode(errors='replace')[-3000:]) try: with urlopen(base+'/api/ready',timeout=0.5) as result: if result.status==200:break except (OSError,URLError):time.sleep(0.1) else:raise RuntimeError('Web readiness timeout') with httpx.Client(base_url=base,timeout=20,follow_redirects=False) as client: auth=client.post('/api/auth/login',json={'username':'admin','password':env['SITEFORGE_ADMIN_PASSWORD']}) assert auth.status_code==200,auth.text me=client.get('/api/auth/me') assert me.status_code==200,me.text client.headers['X-SiteForge-CSRF']=me.json()['csrf'] site=client.post('/api/sites',json={'name':'E2E Production Smoke','theme':'corporate'}) assert site.status_code==201,site.text sid=site.json()['id'] page=client.post(f'/api/sites/{sid}/entries',json={'type':'page','title':'Test about','route':'/about/','body':'E2E smoke content'}) assert page.status_code==201,page.text job=client.post(f'/api/sites/{sid}/builds',json={'mirror_images':False},headers={'Idempotency-Key':'e2e-build-000001'}) assert job.status_code==202,job.text completed=subprocess.run([sys.executable,'-c','from app import db,enterprise,worker; db.init(); enterprise.setup(); assert worker.run_once()'],cwd=ROOT,env=env,capture_output=True,text=True,timeout=70) assert completed.returncode==0,completed.stderr[-2500:] builds=client.get(f'/api/sites/{sid}/builds') assert builds.status_code==200,builds.text done=[x for x in builds.json() if x['status']=='complete'] if not done: raise AssertionError('No completed static build: '+str(builds.json())[:1200]) archive=client.get(f"/api/sites/{sid}/builds/{done[0]['id']}/download") assert archive.status_code==200,archive.text[:300] with zipfile.ZipFile(io.BytesIO(archive.content)) as z: assert 'index.html' in z.namelist() assert 'about/index.html' in z.namelist() preview=client.get(f"/preview/{sid}/{done[0]['id']}/about/") assert preview.status_code==200 and 'E2E smoke content' in preview.text print('PASS: ready -> login/CSRF -> create site/page -> enqueue -> Worker build -> ZIP -> preview') finally: proc.terminate() try:proc.communicate(timeout=5) except subprocess.TimeoutExpired:proc.kill();proc.communicate() if __name__=='__main__':run()