8cea7dee20
- 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>
110 lines
6.4 KiB
Python
110 lines
6.4 KiB
Python
"""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
|