Files
cms-SiteForge/tests/test_site_design_v87.py
T
浅念 8cea7dee20 chore: baseline V8.10.0-dev.1 with v8_workflows type-mapping fix and trade-pro spec
- SiteForge Enterprise V8.10.0-dev.1 upstream source (210 tests)
- Fix: v8_workflows._mapped_input now reads both 类型/分类 columns so WXR
  imports keep post/product types instead of collapsing to page
- Docs: trade-pro theme port spec (R3) and implementation plan

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-22 16:49:25 +08:00

172 lines
8.7 KiB
Python

"""P4 whole-site chrome is editable, safe, versioned, and build-isolated."""
import json
import pytest
from fastapi import HTTPException
from tests.test_factory import client,create
from app import db,builder,site_design_v87
def _payload(**header):
base=json.loads(json.dumps(site_design_v87.DEFAULTS))
base['header'].update({'show_announcement':True,'announcement':'V87_GLOBAL_DRAFT_MARKER',
'cta_text':'Get Quote V87','cta_href':'/contact/'})
base['header'].update(header)
base['footer'].update({'title':'V87_ENTERPRISE_FOOTER','description':'International operations',
'email':'team@example.test','show_nav':True})
base['nav']=[{'title':'首页','route':'/'},{'title':'Solutions','route':'/services/',
'children':[{'title':'Consulting','route':'/about/'}]}]
base['responsive'].update({'tablet_gap':72,'mobile_gap':33,
'mobile_container_padding':16,'mobile_hide_announcement':False})
return base
def _get_page(sid,bid):
return (db.DATA/'builds'/sid/bid/'dist'/'index.html').read_text('utf-8')
def test_site_chrome_draft_isolation_publish_and_build(client):
sid=create(client)
before=builder.build(sid,False)
marker='V87_GLOBAL_DRAFT_MARKER'
assert marker not in _get_page(sid,before['version'])
url=f'/api/sites/{sid}/site-design'
payload=_payload()
saved=client.put(url,json={'config':payload,'expected_revision':0})
assert saved.status_code==200,saved.text
assert saved.json()['build_unchanged'] is True
assert client.get(url).json()['state']=='draft'
waiting=builder.build(sid,False)
assert marker not in _get_page(sid,waiting['version'])
published=client.post(url+'/publish',json={'expected_revision':1})
assert published.status_code==200,published.text
after=builder.build(sid,False)
html=_get_page(sid,after['version'])
assert marker in html and 'V87_ENTERPRISE_FOOTER' in html
assert 'Get Quote V87' in html and 'Consulting' in html
css=(db.DATA/'builds'/sid/after['version']/'dist/assets/custom.css').read_text('utf-8')
assert 'padding-block:33px' in css and 'padding-left:16px' in css
assert len(client.get(url+'/publications').json())==1
assert client.get(url).json()['state']=='published'
assert marker not in _get_page(sid,before['version'])
@pytest.mark.parametrize('theme',['automotive','industrial','corporate','minimal'])
def test_whole_site_theme_render_uses_published_design(client,theme):
sid=create(client,theme)
url=f'/api/sites/{sid}/site-design'
payload=_payload()
assert client.put(url,json={'config':payload,'expected_revision':0}).status_code==200
assert client.post(url+'/publish',json={'expected_revision':1}).status_code==200
result=builder.build(sid,False)
html=_get_page(sid,result['version'])
assert 'V87_GLOBAL_DRAFT_MARKER' in html
assert 'Get Quote V87' in html
assert 'team@example.test' in html
assert 'Consulting' in html
def test_site_design_preview_is_not_committed(client):
sid=create(client);url=f'/api/sites/{sid}/site-design'
assert client.put(url,json={'config':_payload(),'expected_revision':0}).status_code==200
eid=next(x['id'] for x in client.get(f'/api/sites/{sid}/entries').json() if x['route']=='/')
resp=client.post(f'/api/sites/{sid}/pages/{eid}/editor-preview',json={
'blocks':[],'page_template':'home','layout_mode':'append','site_design':_payload()})
assert resp.status_code==200,resp.text[:600]
assert 'V87_GLOBAL_DRAFT_MARKER' in resp.text
assert 'V87_GLOBAL_DRAFT_MARKER' not in _get_page(sid,builder.build(sid,False)['version'])
assert '全站页眉与页脚' in client.get(f'/editor/{sid}/{eid}').text
assert 'showSiteDesign' in client.get('/ui/editor.js').text
def test_conflicting_versions_and_website_updates(client):
sid=create(client);url=f'/api/sites/{sid}/site-design'
assert client.put(url,json={'config':_payload(),'expected_revision':0}).status_code==200
assert client.put(url,json={'config':_payload(),'expected_revision':0}).status_code==409
assert client.post(url+'/publish',json={'expected_revision':0}).status_code==409
assert client.put(f'/api/enterprise/sites/{sid}/design-tokens',json={'heading_scale':1.3}).status_code==200
assert client.get(url).json()['conflict'] is True
assert client.post(url+'/publish',json={'expected_revision':1}).status_code==409
assert client.get(url+'/publications').json()==[]
def test_invalid_navigation_and_restricted_responsive_values(client):
sid=create(client);url=f'/api/sites/{sid}/site-design'
for bad in [
{'nav':[{'title':'Bad','route':'javascript:alert(1)'}]},
{'nav':[{'title':'Bad','route':'//169.254.169.254/'}]},
{'nav':[{'title':'Bad','route':'/','children':[{'title':'A','route':'/','children':[{'title':'B','route':'/', 'children':[{'title':'C','route':'/'}]}]}]}]},
{'responsive':{'mobile_gap':-1}},
{'responsive':{'mobile_gap':'calc(1px * 3)'}},
{'header':{'announcement':'x'*141}},
{'footer':{'title':'<script>x</script>','unknown':'a'}},
]:
payload=_payload()
for k,v in bad.items():
if isinstance(v,dict):payload[k].update(v)
else:payload[k]=v
response=client.put(url,json={'config':payload,'expected_revision':0})
assert response.status_code==400,(bad,response.text)
assert client.get(url).json()['revision']==0
def test_cancel_draft_and_multi_tenant_isolation(client):
sid=create(client);other=create(client)
url=f'/api/sites/{sid}/site-design'
assert client.put(url,json={'config':_payload(),'expected_revision':0}).status_code==200
assert client.get(f'/api/sites/{other}/site-design').json()['state']=='published'
assert client.delete(url).status_code==200
assert client.get(url).json()['revision']==0
assert client.post(url+'/publish',json={'expected_revision':0}).status_code==409
assert 'V87_GLOBAL_DRAFT_MARKER' not in _get_page(other,builder.build(other,False)['version'])
def test_empty_nav_preserves_legacy_navigation_and_default_theme(client):
sid=create(client)
r=client.get(f'/api/sites/{sid}/site-design').json()
assert r['state']=='published' and r['config']['nav']==[]
html=_get_page(sid,builder.build(sid,False)['version'])
assert '首页' in html and '产品' in html
def test_legacy_direct_design_writes_blocked_by_default(client,monkeypatch):
sid=create(client)
eid=next(x['id'] for x in client.get(f'/api/sites/{sid}/entries').json() if x['route']=='/')
monkeypatch.delenv('SITEFORGE_ALLOW_LEGACY_DIRECT_DESIGN_WRITE',raising=False)
assert client.put(f'/api/sites/{sid}/pages/{eid}/blueprint',json={
'blocks':[],'page_template':'home'}).status_code==409
assert client.post(f'/api/sites/{sid}/pages/{eid}/blueprint/preset',json={
'page_template':'home'}).status_code==409
assert client.put(f'/api/sites/{sid}/navigation',json=[{'title':'Bad','route':'/'}]).status_code==409
assert client.put(f'/api/enterprise/sites/{sid}/design-tokens',json={
'font':'serif'}).status_code==409
assert client.patch(f'/api/sites/{sid}/entries/{eid}',json={
'meta':{'blocks':[]}}).status_code==409
# New draft APIs work while legacy direct-write interfaces are disabled.
assert client.put(f'/api/sites/{sid}/site-design',json={
'config':_payload(),'expected_revision':0}).status_code==200
assert client.get(f'/api/sites/{sid}/site-design').json()['state']=='draft'
def test_site_design_tokens_follow_draft_publish_boundary(client):
sid=create(client)
original=builder.build(sid,False)
path=f'/api/sites/{sid}/site-design'
config=_payload();config['tokens'].update({'font':'serif','container_width':1380,'heading_scale':1.2})
assert client.put(path,json={'config':config,'expected_revision':0}).status_code==200
waiting=builder.build(sid,False)
css=lambda bid:(db.DATA/'builds'/sid/bid/'dist/assets/custom.css').read_text('utf-8')
assert '--sf-width:1380px' not in css(original['version'])
assert '--sf-width:1380px' not in css(waiting['version'])
assert client.post(path+'/publish',json={'expected_revision':1}).status_code==200
done=builder.build(sid,False)
assert '--sf-width:1380px' in css(done['version']) and 'Georgia' in css(done['version'])
assert client.get(f'/api/enterprise/sites/{sid}/design-tokens').json()['container_width']==1380
def test_legacy_frontends_use_drafts(client):
script=client.get('/ui/admin.js').text
enterprise_script=client.get('/ui/enterprise-ui.js').text
assert "'/site-design'" in script or "'/site-design'" in script.replace('"',"'")
assert '/site-design' in enterprise_script
assert 'design-tokens`,' not in enterprise_script