"""V8.19 unified console contract; server permissions remain authoritative.""" from app.version import VERSION import json import pytest from tests.test_enterprise import root,site from scripts.install_into_art_design_pro import install, transform_routes def test_bootstrap_admin_uses_one_navigation_and_no_secrets(root): one=site(root,'HESU A');two=site(root,'HESU B') r=root.get('/api/console/bootstrap') assert r.status_code==200,r.text info=r.json() assert info['version']==VERSION assert {x['id'] for x in info['sites']}=={one,two} assert info['selected_site_id'] in {one,two} assert info['capabilities']['create_site'] and info['capabilities']['deploy'] assert [i['key'] for i in info['navigation']]==['dashboard','sites','content','design','migration','release','system'] assert 'csrf' not in json.dumps(info).lower() assert root.get('/api/console/bootstrap?site_id=not-a-site').status_code==403 def test_scope_does_not_disclose_foreign_site_and_rejects_unassigned(root): one=site(root,'Assigned');two=site(root,'Not assigned') uid=root.post('/api/enterprise/team/users',json={ 'username':'v819view','display_name':'Viewer','password':'viewer-strong-password-2026'}).json()['id'] assert root.put(f'/api/enterprise/sites/{one}/members',json={'user_id':uid,'role':'viewer'}).status_code==200 root.post('/api/auth/logout') assert root.post('/api/auth/login',json={'username':'v819view','password':'viewer-strong-password-2026'}).status_code==200 root.headers['X-SiteForge-CSRF']=root.get('/api/auth/me').json()['csrf'] r=root.get('/api/console/bootstrap') assert r.status_code==200,r.text b=r.json() assert [s['id'] for s in b['sites']]==[one] assert b['capabilities']['role']=='viewer' assert not b['capabilities']['edit'] and not b['capabilities']['deploy'] assert not b['capabilities']['manage_users'] assert all(item['key']!='system' for item in b['navigation']) assert root.get(f'/api/console/bootstrap?site_id={two}').status_code==403 assert root.get(f'/api/sites/{two}/entries').status_code==403 assert root.get(f'/api/console/bootstrap?site_id={one}').status_code==200 def test_empty_site_roles_still_have_dashboard(root): resp=root.get('/api/console/bootstrap') assert resp.status_code==200 nav=resp.json()['navigation'] assert nav[0]['key']=='dashboard' assert all(n['key'] not in {'content','design','migration','release'} for n in nav) def fixture(tmp_path): repo=tmp_path/'art';route=repo/'src/router/routes/asyncRoutes.ts' route.parent.mkdir(parents=True,exist_ok=True) route.write_text('export const asyncRoutes: MenuListType[] = [\n{path:"/unrelated",name:"Safe"},\n];\n') (repo/'package.json').write_text('{"name":"art-design-pro"}') return repo,route def test_art_installer_single_menu_and_safe_backup(tmp_path): repo,route=fixture(tmp_path) original=route.read_text() result=install(repo,dry_run=True) assert result['dry_run'] and not result['conflicting_views'] assert route.read_text()==original install(repo,dry_run=False) updated=route.read_text() assert updated.count("path: '/siteforge'")==1 assert "path: 'production'" in updated assert updated.count('// SITEFORGE_FACTORY_V8_REGISTERED')==1 assert 'Safe' in updated assert install(repo,verify=True)['installed'] assert route.with_name('asyncRoutes.ts.siteforge-backup').read_text()==original install(repo,dry_run=False) assert route.read_text()==updated def test_installer_fails_closed_on_conflicting_views(tmp_path): repo,route=fixture(tmp_path) target=repo/'src/views/siteforge/ConsoleHome.vue' target.parent.mkdir(parents=True) target.write_text('someone else code') with pytest.raises(ValueError,match='拒绝覆盖'): install(repo,dry_run=False) assert 'SITEFORGE' not in route.read_text() install(repo,dry_run=False,overwrite_views=True) assert target.with_name('ConsoleHome.vue.siteforge-backup').read_text()=='someone else code' target.write_text('broken') with pytest.raises(ValueError,match='不匹配'): install(repo,verify=True) def test_installer_rejects_partial_routes(tmp_path): repo,route=fixture(tmp_path) route.write_text('export const asyncRoutes = [ /* SITEFORGE_V819_ROUTES_BEGIN */ ]') with pytest.raises(ValueError,match='标记不完整'): install(repo,dry_run=False) def test_legacy_upgrade_requires_explicit_confirmation(tmp_path): repo,route=fixture(tmp_path) route.write_text('export const asyncRoutes: any[] = [\n // SITEFORGE_FACTORY_V8_REGISTERED\n {path:"/siteforge/production",meta:{a:1}},\n // SITEFORGE_FACTORY_V7_REGISTERED\n {path:"/siteforge",children:[{path:"factory",name:"A"}]},\n {path:"/unrelated"},\n]') with pytest.raises(ValueError,match='--migrate-legacy'): install(repo,dry_run=False) install(repo,dry_run=False,migrate_legacy=True) routes=route.read_text() assert routes.count("path: '/siteforge'")==1 assert 'unrelated' in routes assert routes.count('// SITEFORGE_FACTORY_V8_REGISTERED')==1 def test_shared_sdk_and_vue_pages_exist(): from pathlib import Path base=Path(__file__).resolve().parents[1]/'integrations/art-design-pro/src/views/siteforge' assert (base/'_shared.ts').is_file() assert 'X-SiteForge-CSRF' in (base/'_shared.ts').read_text() for name in ('ConsoleHome.vue','ContentCenter.vue'): text=(base/name).read_text() assert '