"""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')))
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 '
Keep Me SEO' 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