Files
cms-SiteForge/tests/test_enterprise.py
T
浅念 8cea7dee20 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>
2026-10-22 16:49:25 +08:00

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