601451f22b
Overlay ~150 upstream items onto our V8.12 base, preserving trade-pro
theme, registry tests, and design docs. Re-applied our patch set
(discovery/env_for/blocks.css/type-mapping/theme_color/frontend entries,
test_theme_pro_v3 mods) — all anchors survived upstream evolution.
New platform enhancement (fixes upgrade-introduced regression):
multilingual.site_locale now honors settings_json.locale, plus
PUT /api/i18n/sites/{id}/locale — standalone English sites get
<html lang='en'> under the V8.15 locale-rewrite pipeline.
compose: adopt V8.21.3 (image 8.21.3-dev.1, /api/ready probes, MySQL/S3
env) with project name pinned to siteforge-v8-10 (data volumes).
Tests: 356 passed / 11 failed — all 11 confirmed pre-existing in
pristine V8.21.3 on this host (10 upstream Windows env + our build test
now green after locale fix). Patch registry updated in plan doc.
Co-Authored-By: Claude Code <noreply@anthropic.com>
127 lines
7.1 KiB
Python
127 lines
7.1 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')))
|
|
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 '<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
|