Files
cms-SiteForge/tests/test_theme_pro_v3.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

123 lines
6.8 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
THEMES=('automotive','industrial','corporate','minimal')
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')))
assert 'pro-base.css' in r.text and 'pro.js' in r.text
if page_type=='product':assert soup.select_one('.pro-product-layout .pro-specs')
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)
assert len(set(re.findall(r'class="pro-home-hero [^"]+"',s)[0] for s in main))==4
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)
versions=factory.get('/api/factory/themes/automotive/versions').json()
assert {v['version'] for v in versions}=={'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
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