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>
125 lines
7.1 KiB
Python
125 lines
7.1 KiB
Python
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
|