upgrade: adopt V8.21.3 (V8.13→8.21.3: P5 SFTP/S3, P6 multilingual, editor overhaul, enterprise hardening)
Overlay ~150 upstream items onto our V8.12 base, preserving trade-pro
theme, registry tests, and design docs. Re-applied our patch set
(discovery/env_for/blocks.css/type-mapping/theme_color/frontend entries,
test_theme_pro_v3 mods) — all anchors survived upstream evolution.
New platform enhancement (fixes upgrade-introduced regression):
multilingual.site_locale now honors settings_json.locale, plus
PUT /api/i18n/sites/{id}/locale — standalone English sites get
<html lang='en'> under the V8.15 locale-rewrite pipeline.
compose: adopt V8.21.3 (image 8.21.3-dev.1, /api/ready probes, MySQL/S3
env) with project name pinned to siteforge-v8-10 (data volumes).
Tests: 356 passed / 11 failed — all 11 confirmed pre-existing in
pristine V8.21.3 on this host (10 upstream Windows env + our build test
now green after locale fix). Patch registry updated in plan doc.
Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
"""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()
|
||||
Reference in New Issue
Block a user