Files
cms-SiteForge/scripts/http_end_to_end.py
T
浅念 601451f22b 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>
2026-10-23 12:31:03 +08:00

78 lines
4.3 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':'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()