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>
This commit is contained in:
@@ -0,0 +1,171 @@
|
||||
"""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
|
||||
Reference in New Issue
Block a user