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:
@@ -0,0 +1,124 @@
|
||||
import json
|
||||
import os
|
||||
from tests.legacy import LegacyTestClient as TestClient
|
||||
import pytest
|
||||
from app import db, enterprise, builder
|
||||
from app.main import app
|
||||
from app.worker import run_once
|
||||
|
||||
@pytest.fixture
|
||||
def root(tmp_path,monkeypatch):
|
||||
monkeypatch.setattr(db,'DATA',tmp_path)
|
||||
monkeypatch.setattr(db,'DB_FILE',tmp_path/'app.sqlite3')
|
||||
monkeypatch.setenv('SITEFORGE_ALLOW_LEGACY_DIRECT_DESIGN_WRITE','1')
|
||||
monkeypatch.setenv('SITEFORGE_ADMIN_PASSWORD','administrator-strong-password-2026')
|
||||
monkeypatch.setenv('SITEFORGE_SESSION_SECRET','secure-random-test-key-with-long-length-1234')
|
||||
monkeypatch.setenv('SITEFORGE_PUBLISH_ROOT',str(tmp_path/'published'))
|
||||
with TestClient(app) as c:
|
||||
assert c.post('/api/auth/login',json={'username':'admin','password':'administrator-strong-password-2026'}).status_code==200
|
||||
csrf=c.get('/api/auth/me').json()['csrf']
|
||||
c.headers.update({'X-SiteForge-CSRF':csrf})
|
||||
yield c
|
||||
|
||||
def site(c,name):
|
||||
r=c.post('/api/sites',json={'name':name,'theme':'corporate','mode':'migration','domain':'https://example.org'})
|
||||
assert r.status_code==201,r.text
|
||||
return r.json()['id']
|
||||
|
||||
def test_team_rbac_isolation(root):
|
||||
one=site(root,'Company A')
|
||||
two=site(root,'Company B')
|
||||
response=root.post('/api/enterprise/team/users',json={'username':'editor1','password':'long-editor-password-2026','display_name':'Editor'})
|
||||
assert response.status_code==201,response.text
|
||||
uid=response.json()['id']
|
||||
r=root.put(f'/api/enterprise/sites/{one}/members',json={'user_id':uid,'role':'editor'})
|
||||
assert r.status_code==200,r.text
|
||||
root.post('/api/auth/logout')
|
||||
assert root.post('/api/auth/login',json={'username':'editor1','password':'long-editor-password-2026'}).status_code==200
|
||||
root.headers.update({'X-SiteForge-CSRF':root.get('/api/auth/me').json()['csrf']})
|
||||
visible=root.get('/api/sites').json()
|
||||
assert [x['id'] for x in visible]==[one]
|
||||
assert root.get(f'/api/sites/{two}').status_code==403
|
||||
assert root.post(f'/api/sites/{two}/entries',json={'title':'Blocked'}).status_code==403
|
||||
assert root.post('/api/sites',json={'name':'Blocked'}).status_code==403
|
||||
assert root.post(f'/api/sites/{one}/entries',json={'title':'Allowed','type':'page','route':'/allowed/'}).status_code==201
|
||||
assert root.post(f'/api/sites/{one}/builds/invalid/publish-local').status_code==403
|
||||
assert root.get('/api/enterprise/team/users').status_code==403
|
||||
assert root.get(f'/api/enterprise/sites/{one}/baseline/report').status_code==200
|
||||
|
||||
|
||||
def test_baseline_and_release_gate(root):
|
||||
sid=site(root,'Legacy')
|
||||
x=root.post(f'/api/sites/{sid}/entries',json={'type':'page','title':'Original','route':'/legacy/a.html','seo_title':'Old Title'})
|
||||
assert x.status_code==201
|
||||
assert root.post(f'/api/enterprise/sites/{sid}/baseline/snapshot').json()['captured']==2
|
||||
e=x.json()['id']
|
||||
assert root.delete(f'/api/sites/{sid}/entries/{e}').status_code==200
|
||||
report=root.get(f'/api/enterprise/sites/{sid}/baseline/report').json()
|
||||
assert report['errors']==1
|
||||
build=root.post(f'/api/sites/{sid}/builds',json={})
|
||||
assert build.status_code==200,build.text
|
||||
bid=build.json()['version']
|
||||
publish=root.post(f'/api/sites/{sid}/builds/{bid}/publish-local')
|
||||
assert publish.status_code==409
|
||||
assert root.post(f'/api/sites/{sid}/entries',json={'type':'page','title':'Replaced','route':'/legacy/a.html','seo_title':'Old Title'}).status_code==201
|
||||
assert root.get(f'/api/enterprise/sites/{sid}/baseline/report').json()['errors']==0
|
||||
rebuilt=root.post(f'/api/sites/{sid}/builds',json={}).json()['version']
|
||||
publish=root.post(f'/api/sites/{sid}/builds/{rebuilt}/publish-local')
|
||||
assert publish.status_code==200,publish.text
|
||||
qa=root.get(f'/api/enterprise/sites/{sid}/builds/{rebuilt}/quality')
|
||||
assert qa.status_code==200,qa.text
|
||||
assert qa.json()['files']>=2
|
||||
history=root.get(f'/api/enterprise/sites/{sid}/releases').json()
|
||||
assert history and history[0]['build_id']==rebuilt
|
||||
rollback=root.post(f'/api/enterprise/sites/{sid}/releases/{rebuilt}/rollback')
|
||||
assert rollback.status_code==200,rollback.text
|
||||
|
||||
|
||||
def test_revision_restore_and_design_tokens(root):
|
||||
sid=site(root,'Design')
|
||||
x=root.post(f'/api/sites/{sid}/entries',json={'type':'page','title':'Draft v1','route':'/old-page/'})
|
||||
entry=x.json()['id']
|
||||
assert root.patch(f'/api/sites/{sid}/entries/{entry}',json={'title':'Draft v2'}).status_code==200
|
||||
revs=root.get(f'/api/enterprise/sites/{sid}/entries/{entry}/revisions').json()
|
||||
assert len(revs)>=1
|
||||
old=root.get(f'/api/enterprise/sites/{sid}/revisions/{revs[0]["id"]}').json()
|
||||
assert old['entry']['title']=='Draft v1'
|
||||
assert root.post(f'/api/enterprise/sites/{sid}/revisions/{revs[0]["id"]}/restore').status_code==200
|
||||
refreshed=root.get(f'/api/sites/{sid}/entries').json()
|
||||
assert any(i['title']=='Draft v1' for i in refreshed)
|
||||
invalid=root.put(f'/api/enterprise/sites/{sid}/design-tokens',json={'font':'system','radius':999})
|
||||
assert invalid.status_code==400
|
||||
payload={'font':'serif','heading_scale':1.2,'radius':18,'section_spacing':80,'container_width':1360,'button_style':'pill','header_style':'compact'}
|
||||
assert root.put(f'/api/enterprise/sites/{sid}/design-tokens',json=payload).status_code==200
|
||||
assert root.get(f'/api/enterprise/sites/{sid}/design-tokens').json()==payload
|
||||
build=root.post(f'/api/sites/{sid}/builds',json={}).json()['version']
|
||||
import pathlib
|
||||
css=(db.DATA/'builds'/sid/build/'dist'/'assets'/'custom.css').read_text()
|
||||
assert 'Georgia' in css and '--sf-width:1360px' in css
|
||||
|
||||
|
||||
def test_background_worker_and_audit(root):
|
||||
sid=site(root,'Jobs')
|
||||
res=root.post(f'/api/enterprise/sites/{sid}/jobs',json={'kind':'build'})
|
||||
assert res.status_code==202,res.text
|
||||
assert run_once(executor=lambda sid,opts:builder.build(sid,opts.get('mirror_images',False)))==res.json()['id']
|
||||
jobs=root.get(f'/api/enterprise/sites/{sid}/jobs').json()
|
||||
assert jobs[0]['state']=='done'
|
||||
audits=root.get('/api/enterprise/audit').json()
|
||||
assert any(x['operation']=='POST' for x in audits)
|
||||
assert root.get('/ui/enterprise-ui.js').status_code==200
|
||||
|
||||
def test_viewer_cannot_modify_or_see_other_site(root):
|
||||
own=site(root,'Site One'); foreign=site(root,'Site Two')
|
||||
user=root.post('/api/enterprise/team/users',json={'username':'viewer22','display_name':'Viewer','password':'long-viewer-password-2026'}).json()['id']
|
||||
assert root.put(f'/api/enterprise/sites/{own}/members',json={'user_id':user,'role':'viewer'}).status_code==200
|
||||
root.post('/api/auth/logout')
|
||||
root.post('/api/auth/login',json={'username':'viewer22','password':'long-viewer-password-2026'})
|
||||
root.headers['X-SiteForge-CSRF']=root.get('/api/auth/me').json()['csrf']
|
||||
assert root.get(f'/api/sites/{own}/entries').status_code==200
|
||||
assert root.get(f'/preview/{foreign}/any/').status_code==403
|
||||
assert root.post(f'/api/sites/{own}/builds',json={}).status_code==403
|
||||
assert root.put(f'/api/enterprise/sites/{own}/design-tokens',json={}).status_code==403
|
||||
assert root.get(f'/api/enterprise/sites/{own}/jobs').status_code==200
|
||||
assert root.get(f'/api/enterprise/sites/{foreign}/jobs').status_code==403
|
||||
Reference in New Issue
Block a user