Files
cms-SiteForge/tests/test_theme_pro_v3.py
T
浅念 95982fad23 upgrade: adopt V8.32.0-dev.1 (theme preview iframe fix, editor simple mode)
Overlay 46 upstream items; full patch set re-applied (all patched files
reset by overlay regardless of upstream change status — lesson recorded).
Verified: theme suites 35/35; dual-baseline 418 passed vs pristine
389+29 ours, FAILED-set diff EMPTY (same 10 Windows env fails).
Live: container 8.32.0-dev.1 healthy, hesupipe 192 entries intact,
trade-pro build 203 pages 0 errors.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-26 11:18:45 +08:00

131 lines
7.3 KiB
Python

"""Professional theme system integration tests use the real FastAPI and static builder."""
import io
import json
import re
import zipfile
from pathlib import Path
from bs4 import BeautifulSoup
from test_factory_v2 import factory, site, zip_theme
from app.core import THEMES
def test_four_complete_theme_families_have_distinct_markup(factory):
main=[]
for theme in THEMES:
for page_type in ('home','about','contact','listing','product','article','brand','standard','notfound'):
r=factory.get(f'/api/factory/themes/{theme}/preview',params={'page_type':page_type})
assert r.status_code==200,(theme,page_type,r.text[:400])
soup=BeautifulSoup(r.text,'html.parser')
assert soup.find('header',class_='pro-header')
assert soup.find('footer',class_='pro-footer')
assert len(soup.find_all('h1'))==1,(theme,page_type,len(soup.find_all('h1')))
if theme == 'automotive':
assert 'preview.bundle.css' in r.text and 'preview.bundle.js' in r.text
else:
assert 'pro-base.css' in r.text and 'pro.js' in r.text
if page_type=='product' and theme!='trade-pro':assert soup.select_one('.pro-product-layout .pro-specs') # trade-pro covered by test_theme_registry
if page_type=='listing':assert soup.select_one('[data-pro-filter-search]')
if page_type=='article':assert soup.select_one('.pro-article-hero')
main.append(factory.get(f'/api/factory/themes/{theme}/preview?page_type=home').text)
def _hero_sig(html):
import re as _re
m=_re.search(r'class="((?:pro-home-hero|sf-tp-hero)[^"]*)"',html)
return m.group(1) if m else html[:40]
assert len(set(_hero_sig(s) for s in main))==5
def test_theme_build_derived_category_brand_search_and_seo(factory):
sid=site(factory)
assert factory.post(f'/api/factory/sites/{sid}/bootstrap').status_code==200
r=factory.post(f'/api/sites/{sid}/entries',json={
'type':'product','title':'Electric A','route':'/old/product-a.html',
'summary':'Long-range sample','seo_title':'Keep Me SEO','seo_description':'Original description',
'meta':{'fields':{'brand':'Quanta Motors','category':'SUV','range':'500 km'}}})
assert r.status_code==201,r.text
for theme in THEMES:
selected=factory.put(f'/api/factory/sites/{sid}/theme',json={'theme':theme,'version':'1.0.0'})
assert selected.status_code==200,selected.text
result=factory.post(f'/api/sites/{sid}/builds',json={})
assert result.status_code==200,result.text
bid=result.json()['version']
for url in ['/','/about/','/contact/','/products/','/brands/','/news/','/cases/','/categories/suv/','/brands/quanta-motors/','/search/','/old/product-a.html']:
page=factory.get(f'/preview/{sid}/{bid}{url}')
assert page.status_code==200,(theme,url,page.text[:200])
soup=BeautifulSoup(page.text,'html.parser')
assert len(soup.find_all('h1'))==1,(theme,url)
assert soup.select_one('.pro-header') and soup.select_one('.pro-footer')
product=factory.get(f'/preview/{sid}/{bid}/old/product-a.html').text
assert '<title>Keep Me SEO</title>' in product
assert 'range' in product.lower() and '500 km' in product
assert 'https://test.example/old/product-a.html' in product
listing=factory.get(f'/preview/{sid}/{bid}/categories/suv/').text
assert 'Electric A' in listing
theme_css=factory.get(f'/preview/{sid}/{bid}/assets/pro.css')
assert theme_css.status_code==200 and len(theme_css.content)>2000
report=factory.get(f'/api/sites/{sid}/builds/{bid}/report').json()
assert not report['errors'] and not report['broken_links'] and not report['missing_local_images']
def test_builtin_version_pin_is_real(factory):
sid=site(factory)
assert factory.post(f'/api/factory/sites/{sid}/bootstrap').status_code==200
versions=factory.get('/api/factory/themes/automotive/versions').json()
assert {v['version'] for v in versions}=={'1.4.1','1.4.0','1.3.0','1.2.0','1.1.0','1.0.0','0.3.0'}
assert factory.put(f'/api/factory/sites/{sid}/theme',json={'theme':'automotive','version':'0.3.0'}).status_code==200
b=factory.post(f'/api/sites/{sid}/builds',json={}).json()
old=factory.get(f'/preview/{sid}/{b["version"]}/').text
assert 'pro-auto-hero' not in old
assert factory.put(f'/api/factory/sites/{sid}/theme',json={'theme':'automotive','version':'1.0.0'}).status_code==200
b=factory.post(f'/api/sites/{sid}/builds',json={}).json()
new=factory.get(f'/preview/{sid}/{b["version"]}/').text
assert 'pro-auto-hero' in new and 'auto31-home' not in new
def test_custom_theme_package_uses_trusted_layout_and_css(factory):
sid=site(factory)
raw=io.BytesIO(zip_theme('custom-v3','2.0.0'))
data=io.BytesIO()
with zipfile.ZipFile(raw) as old,zipfile.ZipFile(data,'w') as new:
for fn in old.namelist():
content=old.read(fn)
if fn=='manifest.json':
obj=json.loads(content);obj['layout_family']='industrial'
content=json.dumps(obj).encode()
if fn=='theme.css':content=b':root{--pro-accent:#dd5500} .pro-card{border-width:2px}'
new.writestr(fn,content)
install=factory.post('/api/factory/themes/install',files={'file':('theme.zip',data.getvalue(),'application/zip')})
assert install.status_code==200,install.text
assert factory.put(f'/api/factory/sites/{sid}/theme',json={'theme':'custom-v3','version':'2.0.0'}).status_code==200
b=factory.post(f'/api/sites/{sid}/builds',json={}).json()
assert 'pro-industrial-hero' in factory.get(f'/preview/{sid}/{b["version"]}/').text
css=factory.get(f'/preview/{sid}/{b["version"]}/assets/theme-overrides.css').text
assert '--pro-accent:#dd5500' in css
pkg=factory.get('/api/factory/themes/custom-v3/package?version=2.0.0')
assert pkg.status_code==200
with zipfile.ZipFile(io.BytesIO(pkg.content)) as z:
assert {'manifest.json','theme.css','page-presets.json'}<=set(z.namelist())
def test_static_responsive_and_search_facilities(factory):
sid=site(factory)
b=factory.post(f'/api/sites/{sid}/builds',json={}).json()
bid=b['version']
css=factory.get(f'/preview/{sid}/{bid}/assets/pro-base.css').text
js=factory.get(f'/preview/{sid}/{bid}/assets/pro.js').text
assert '@media(max-width:560px)' in css
assert 'aria-expanded' in js and 'data-pro-filter-brand' in js
assert 'search-index.json' in factory.get(f'/preview/{sid}/{bid}/assets/site-runtime.js').text
assert 'noindex' in factory.get(f'/preview/{sid}/{bid}/search/').text
def test_all_fifty_six_page_templates_and_live_gallery(factory):
base=Path(__file__).resolve().parent.parent/'themes'
expected={'home','about','contact','listing','category','product','brand','article','case','service','faq','standard','search','notfound'}
for theme in THEMES:
assert {p.stem for p in (base/theme/'pages').glob('*.html')}==expected
for page_type in ('case','service','faq','category'):
r=factory.get(f'/api/factory/themes/{theme}/preview?page_type={page_type}')
assert r.status_code==200,(theme,page_type,r.text[:300])
page=factory.get('/theme-gallery')
assert page.status_code==200 and 'iframe' in page.text and 'page_type' in page.text