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>
This commit is contained in:
浅念
2026-10-22 16:49:25 +08:00
commit 8cea7dee20
354 changed files with 20736 additions and 0 deletions
+109
View File
@@ -0,0 +1,109 @@
"""Regression tests for release safety and CMS review persistence."""
import json
from fastapi.testclient import TestClient
from fastapi import HTTPException
import pytest
from app import db, cms_pipeline, release_api
from app.main import app
def test_pending_and_failed_build_cannot_be_deployed(monkeypatch,tmp_path):
monkeypatch.setattr(db,'DATA',tmp_path)
monkeypatch.setattr(db,'DB_FILE',tmp_path/'pending.sqlite3')
db.init()
with db.connect() as c:
c.execute('INSERT INTO sites(id,name,created_at,updated_at) VALUES (?,?,?,?)',('sitepending','Sample',db.now(),db.now()))
for state in ['queued','running','failed','unknown']:
c.execute('INSERT INTO builds(id,site_id,created_at,status,zip_path) VALUES (?,?,?,?,?)',
(state,'sitepending',db.now(),state,str(tmp_path/'missing.zip')))
for state in ['queued','running','failed','unknown']:
with pytest.raises(HTTPException) as exc:
release_api.approved('sitepending',state)
assert exc.value.status_code==409
def test_cms_warnings_saved_and_reviewed(monkeypatch,tmp_path):
monkeypatch.setattr(db,'DATA',tmp_path)
monkeypatch.setattr(db,'DB_FILE',tmp_path/'review.sqlite3')
monkeypatch.setenv('SITEFORGE_ADMIN_PASSWORD','test-admin-password-for-cms-reviews')
monkeypatch.setenv('SITEFORGE_SESSION_SECRET','test-session-secret-for-cms-reviews-long')
with TestClient(app) as c:
assert c.post('/api/auth/login',json={'password':'test-admin-password-for-cms-reviews'}).status_code==200
c.headers['X-SiteForge-CSRF']=c.get('/api/auth/me').json()['csrf']
res=c.post('/api/sites',json={'name':'CMS Review Test','mode':'migration'})
assert res.status_code==201,res.text
sid=res.json()['id']
blob={'items':[{'id':3,'type':'page','title':{'rendered':'About'},'link':'https://old.example/about/',
'content':{'rendered':'<p>old</p>'},'acf':{'related':[1,2]}}]}
res=c.post(f'/api/migrations/sites/{sid}/import',data={'platform':'wordpress-rest','conflict':'skip'},
files={'file':('export.json',json.dumps(blob).encode(),'application/json')})
assert res.status_code==200,res.text
assert res.json()['pending_review_count']==1
get=c.get(f'/api/migrations/sites/{sid}/reviews')
assert get.status_code==200 and get.json()['pending']==1
issue=get.json()['items'][0]['id']
reject=c.post(f'/api/migrations/sites/{sid}/reviews/{issue}/resolve',json={'resolution':'ok'})
assert reject.status_code==400
ok=c.post(f'/api/migrations/sites/{sid}/reviews/{issue}/resolve',json={'resolution':'已逐项检查关联产品及图片,确认无需迁移旧关联。'})
assert ok.status_code==200,ok.text
assert ok.json()['pending']==0
assert c.get(f'/api/migrations/sites/{sid}/reviews').json()['items']==[]
assert c.get(f'/api/migrations/sites/{sid}/reviews?state=resolved').json()['items'][0]['resolution']
def test_signatures_are_bound_to_http_path(monkeypatch,tmp_path):
from app import deploy_agent
import zipfile,io,time
from fastapi.testclient import TestClient
monkeypatch.setattr(deploy_agent,'ROOT',tmp_path)
secret='release-secret-very-long-at-least-thirtytwo-bytes'
monkeypatch.setenv('SITEFORGE_DEPLOY_SECRET',secret)
buf=io.BytesIO()
with zipfile.ZipFile(buf,'w') as z:z.writestr('index.html','<title>OK</title>')
client=TestClient(deploy_agent.app)
stamp=str(int(time.time()))
headers={'X-SiteForge-Timestamp':stamp,'X-SiteForge-Signature':deploy_agent.signature(secret,'company',stamp,buf.getvalue(),'POST','/deploy/company')}
created=client.post('/deploy/company',headers=headers,content=buf.getvalue())
assert created.status_code==200
version=created.json()['release']
# A read-only inventory signature must not authorize state-changing rollback.
inventory_headers={'X-SiteForge-Timestamp':stamp,'X-SiteForge-Signature':deploy_agent.signature(secret,'company',stamp,b'','GET','/sites/company/releases')}
assert client.get('/sites/company/releases',headers=inventory_headers).status_code==200
assert client.post(f'/sites/company/rollback/{version}',headers=inventory_headers).status_code==401
rollback_headers={'X-SiteForge-Timestamp':stamp,'X-SiteForge-Signature':deploy_agent.signature(secret,'company',stamp,b'','POST',f'/sites/company/rollback/{version}')}
assert client.post(f'/sites/company/rollback/{version}',headers=rollback_headers).status_code==200
def test_art_installer_idempotent_mock_checkout(tmp_path):
from scripts.install_into_art_design_pro import install
base=tmp_path/'official'/'src'/'router'/'routes'
base.mkdir(parents=True)
(base/'asyncRoutes.ts').write_text('export const asyncRoutes: MenuListType[] = [];',encoding='utf-8')
(tmp_path/'official'/'package.json').write_text('{"name":"art-design-pro"}',encoding='utf-8')
install(tmp_path/'official',dry_run=True)
assert 'SITEFORGE_FACTORY' not in (base/'asyncRoutes.ts').read_text()
install(tmp_path/'official',dry_run=False)
before=(base/'asyncRoutes.ts').read_text()
assert before.count('// SITEFORGE_FACTORY_V7_REGISTERED')==1
assert 'SiteForgeMigration' in before
install(tmp_path/'official',dry_run=False)
assert (base/'asyncRoutes.ts').read_text()==before
def test_new_site_missing_content_gets_noindex_scaffold(monkeypatch,tmp_path):
from app import builder,quality
from fastapi.testclient import TestClient
monkeypatch.setattr(db,'DATA',tmp_path)
monkeypatch.setattr(db,'DB_FILE',tmp_path/'scaffold.sqlite3')
monkeypatch.setenv('SITEFORGE_ADMIN_PASSWORD','test-admin-password-for-scaffold')
monkeypatch.setenv('SITEFORGE_SESSION_SECRET','test-session-secret-for-scaffold-extended')
with TestClient(app) as c:
assert c.post('/api/auth/login',json={'password':'test-admin-password-for-scaffold'}).status_code==200
c.headers['X-SiteForge-CSRF']=c.get('/api/auth/me').json()['csrf']
sid=c.post('/api/sites',json={'name':'New Company','theme':'automotive'}).json()['id']
version=builder.build(sid)['version']
root=tmp_path/'builds'/sid/version/'dist'
assert (root/'products/index.html').is_file()
assert (root/'brands/index.html').is_file()
assert 'noindex' in (root/'brands/index.html').read_text(encoding='utf-8')
assert quality.report(sid,version)['errors']==0