위임 관리자(부관리자)가 권한·역할·표현식·비밀 콘텐츠 경계를 우회하던 결함군을 계층 대칭성 원칙으로 전건 차단한다. 약한 경로가 정상 응답을 내보내는 것이 유일한 증상이라, 게이트를 생산 지점 한 곳(SSoT)에 두고 같은 데이터를 내보내는 소비 경로 전부가 그 게이트를 경유하도록 맞췄다. - 등급 상한(rank ceiling): 슈퍼관리자 보호·역할/사용자 역할 배정(추가·제거 대칭)·일괄 상태변경·순서변경을 상세 경로와 동일 강도로 재적용. 가드는 DB 쓰기에 선행하여 거부 시 상태 불변. - 레이아웃 표현식: new Function/with 실행을 AST 화이트리스트 평가기로 교체. 비-문자열 computed 키 정규화(normalizeKey)·Object facade(리플렉션 static 제거)·legacy 접근자 차단. 저장측 검증·정적 검사와 3계층 동형. - secret 게이트: 비밀글의 댓글·첨부·문의 독립 경로 재적용, hash 파일서빙 소유권·비밀·발행 상태 검사 통일. - 신뢰 스크립트 호스트: 확장 선언 기반 + same-origin 브라우저 정규화를 런타임·저장측·정적검사 3층 동형화. - 회귀 감지: 단위·Feature·E2E·시나리오 매니페스트 전축 + audit 룰 4종 신설.
73 lines
3.4 KiB
TypeScript
73 lines
3.4 KiB
TypeScript
/**
|
|
* Layout Editor — 번들 분리 (lazy 로드) 검증. (engine-v1.51.0)
|
|
*
|
|
* 코어 번들(template-engine.min.js)에서 편집기 코드를 분리해 별도 layout-editor.min.js 로
|
|
* 빌드하고, `/admin/layout-editor/*` 진입 시에만 런타임 <script> 주입으로 로드한다.
|
|
*
|
|
* 검증:
|
|
* - 일반 admin 페이지(대시보드) 접속 → layout-editor.min.js 요청이 발생하지 않는다
|
|
* (초기 접속 payload 축소의 핵심 — 회귀 시 편집기 코드가 다시 모든 페이지에 실린다)
|
|
* - 편집기 URL 진입 → layout-editor.min.js 요청 발생 + 편집기 마운트(g7le-toolbar +
|
|
* g7le-preview-frame). React 사본이 둘이면 "Invalid hook call" 로 마운트 자체가 크래시하므로,
|
|
* 마운트 성공이 곧 단일 React 인스턴스 + 컨텍스트/싱글톤 동일성(shim __runtime 공유)을 증명한다.
|
|
*
|
|
* @scenario bundle_split_lazy_editor
|
|
* 효과 요약(마커 아님 — 평문): editor_bundle_absent_on_normal_page, editor_bundle_loaded_and_mounted_on_editor_route.
|
|
*/
|
|
import { test, expect, issueToken, authenticatePage } from '../../fixtures/auth';
|
|
|
|
type PwPage = import('@playwright/test').Page;
|
|
|
|
const EDITOR_BUNDLE_RE = /layout-editor\.min\.js/;
|
|
|
|
/**
|
|
* page 의 네트워크 요청 중 편집기 번들 요청 URL 을 수집하는 리스너를 설치한다.
|
|
*
|
|
* @param page Playwright page
|
|
* @returns 수집된 편집기 번들 요청 URL 배열(참조 — 이후 계속 채워짐)
|
|
*/
|
|
function collectEditorBundleRequests(page: PwPage): string[] {
|
|
const hits: string[] = [];
|
|
page.on('request', (req) => {
|
|
if (EDITOR_BUNDLE_RE.test(req.url())) {
|
|
hits.push(req.url());
|
|
}
|
|
});
|
|
return hits;
|
|
}
|
|
|
|
test.describe('Layout Editor — 번들 분리 (lazy 로드)', () => {
|
|
/** @effects editor_bundle_absent_on_normal_page */
|
|
test('일반 admin 페이지는 layout-editor.min.js 를 요청하지 않는다', async ({ page }) => {
|
|
const token = issueToken('core.templates.layouts.edit');
|
|
await authenticatePage(page, token);
|
|
|
|
const editorRequests = collectEditorBundleRequests(page);
|
|
|
|
await page.goto('/admin/dashboard');
|
|
await page.waitForLoadState('networkidle', { timeout: 30_000 });
|
|
|
|
// 초기 접속 payload 에 편집기 번들 미포함 — 회귀 방지 핵심
|
|
expect(editorRequests, '일반 페이지에서 편집기 번들이 로드됨(회귀)').toEqual([]);
|
|
});
|
|
|
|
/** @effects editor_bundle_loaded_and_mounted_on_editor_route */
|
|
test('편집기 URL 진입 시 layout-editor.min.js 를 로드하고 마운트한다', async ({ page }) => {
|
|
const token = issueToken('core.templates.layouts.edit');
|
|
await authenticatePage(page, token);
|
|
|
|
const editorRequests = collectEditorBundleRequests(page);
|
|
|
|
await page.goto('/admin/layout-editor/sirsoft-basic?route=%2F');
|
|
await page.waitForLoadState('domcontentloaded', { timeout: 30_000 });
|
|
|
|
// 편집기 셸 마운트 대기 — 마운트 성공 = 단일 React 인스턴스 + 컨텍스트 동일성 증명
|
|
await page.waitForSelector('[data-testid="g7le-toolbar"]', { timeout: 30_000 });
|
|
await page.waitForSelector('[data-testid="g7le-preview-frame"]', { timeout: 30_000 });
|
|
|
|
// 편집기 진입 시에는 lazy 번들이 실제로 로드됐어야 한다
|
|
expect(editorRequests.length, '편집기 번들 요청 미발생').toBeGreaterThan(0);
|
|
expect(editorRequests[0]).toMatch(EDITOR_BUNDLE_RE);
|
|
});
|
|
});
|