Files
cms-SiteForge/tests/test_release_reviews_v7.py
T

110 lines
6.4 KiB
Python
Raw Normal View History

"""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