"""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' 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 # 5 themes, 5 distinct hero signatures (was 4 before trade-pro) 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