172 lines
8.7 KiB
Python
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
|