79 lines
4.4 KiB
Python
79 lines
4.4 KiB
Python
|
|
"""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':'V8.33 Trade Pro Worker','theme':'trade-pro'})
|
||
|
|
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()
|
||
|
|
assert '<html lang="en"' in z.read('index.html').decode('utf-8')
|
||
|
|
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()
|