위임 관리자(부관리자)가 권한·역할·표현식·비밀 콘텐츠 경계를 우회하던 결함군을 계층 대칭성 원칙으로 전건 차단한다. 약한 경로가 정상 응답을 내보내는 것이 유일한 증상이라, 게이트를 생산 지점 한 곳(SSoT)에 두고 같은 데이터를 내보내는 소비 경로 전부가 그 게이트를 경유하도록 맞췄다. - 등급 상한(rank ceiling): 슈퍼관리자 보호·역할/사용자 역할 배정(추가·제거 대칭)·일괄 상태변경·순서변경을 상세 경로와 동일 강도로 재적용. 가드는 DB 쓰기에 선행하여 거부 시 상태 불변. - 레이아웃 표현식: new Function/with 실행을 AST 화이트리스트 평가기로 교체. 비-문자열 computed 키 정규화(normalizeKey)·Object facade(리플렉션 static 제거)·legacy 접근자 차단. 저장측 검증·정적 검사와 3계층 동형. - secret 게이트: 비밀글의 댓글·첨부·문의 독립 경로 재적용, hash 파일서빙 소유권·비밀·발행 상태 검사 통일. - 신뢰 스크립트 호스트: 확장 선언 기반 + same-origin 브라우저 정규화를 런타임·저장측·정적검사 3층 동형화. - 회귀 감지: 단위·Feature·E2E·시나리오 매니페스트 전축 + audit 룰 4종 신설.
67 lines
3.9 KiB
TypeScript
67 lines
3.9 KiB
TypeScript
/**
|
|
* Layout Editor — 상점 화면의 상태 미리보기 전환이 상점 주소 설정과 무관하게 표시된다.
|
|
*
|
|
* 상점 주소는 운영자 설정이다. 편집기는 라우트 path 의 `{{...}}` 표현식을 실제 값으로
|
|
* 평가하므로(useEditorRoutes::resolveEditorRouteExpressions) `selectedRoute.path` 의 첫
|
|
* 세그먼트가 사이트마다 다르다 — 기본 `/shop/products`, `route_path` 를 바꾸면
|
|
* `/store/products`, `no_route` 를 켜면 `/products`.
|
|
*
|
|
* editor-spec 의 `states.groups[].scope.match` 가 `/shop/...` 리터럴이던 시절에는 기본값이
|
|
* 아닌 상점에서 상태 그룹이 매칭되지 않아 캔버스 상태 토글(PageStateSwitcher)이 조용히
|
|
* 사라졌다. 예외도 경고도 남지 않아 "이 화면은 원래 상태 전환이 없다" 로 오인된다.
|
|
* 선택 세그먼트 토큰 `/*?`(engine-v1.58.0)로 세 경우를 모두 매칭한다.
|
|
*
|
|
* 단위 테스트(matchStateScope.test.ts)는 매칭 함수만 본다. 이 spec 은 편집기를 실제로
|
|
* 열어 상태 토글이 화면에 나타나는지를 확인한다 — 스펙과 라우트 평가가 실제로 만나는
|
|
* 지점은 브라우저에만 있다.
|
|
*
|
|
* 축 요약(마커 아님 — 평문): route_setting, surface. 요약을 시나리오 축 마커로 적으면 파서가
|
|
* `=` 없는 토큰을 버려 빈 조합 `{}` 이 된다.
|
|
*
|
|
* 효과 요약(마커 아님 — 평문): editor_state_switcher_visible_on_default_shop_route, editor_state_switcher_visible_after_route_path_change.
|
|
*
|
|
* 활성화 절차: PLAYWRIGHT_BASE_URL 과 PlaywrightIssueToken 발급이 가능한 환경에서
|
|
* `test.describe.skip` → `test.describe` 로 바꾼다. 상점 주소를 바꾸는 축은 이커머스
|
|
* 모듈 설정(basic_info.route_path)을 변경해야 하므로, 설정 변경 API 가 열려 있는
|
|
* 검수 환경에서만 두 번째 케이스가 의미를 갖는다.
|
|
*/
|
|
import { test, expect, issueToken, authenticatePage } from '../../fixtures/auth';
|
|
|
|
const PRODUCTS_ROUTE = '/shop/products';
|
|
|
|
test.describe.skip('레이아웃 편집기 — 상점 화면 상태 전환 노출', () => {
|
|
/** @effects editor_state_switcher_visible_on_default_shop_route */
|
|
test('기본 상점 주소에서 상태 전환 토글이 보인다', async ({ page }) => {
|
|
const token = issueToken('core.templates.layouts.edit');
|
|
await authenticatePage(page, token);
|
|
|
|
await page.goto(
|
|
`/admin/layout-editor/sirsoft-basic?route=${encodeURIComponent(PRODUCTS_ROUTE)}`,
|
|
);
|
|
await page.waitForLoadState('domcontentloaded', { timeout: 30_000 });
|
|
await page.waitForSelector('[data-testid="g7le-preview-frame"]', { timeout: 30_000 });
|
|
|
|
// 상태 그룹이 매칭되어야 토글이 마운트된다. 미매칭이면 이 요소 자체가 없다.
|
|
const switcher = page.locator('[data-testid="g7le-state-switcher"]');
|
|
await expect(switcher).toBeVisible({ timeout: 20_000 });
|
|
});
|
|
|
|
/** @effects editor_state_switcher_visible_after_route_path_change */
|
|
test('상점 주소를 바꾼 사이트에서도 상태 전환 토글이 보인다', async ({ page }) => {
|
|
// 이 케이스는 basic_info.route_path 가 기본값이 아닌 환경에서만 의미가 있다.
|
|
// 검수 환경의 실제 설정값을 읽어 라우트를 만든다 — 기본값이면 위 케이스와 같아진다.
|
|
const token = issueToken('core.templates.layouts.edit');
|
|
await authenticatePage(page, token);
|
|
|
|
const routePath = process.env.G7_E2E_SHOP_ROUTE_PATH ?? 'store';
|
|
await page.goto(
|
|
`/admin/layout-editor/sirsoft-basic?route=${encodeURIComponent(`/${routePath}/products`)}`,
|
|
);
|
|
await page.waitForLoadState('domcontentloaded', { timeout: 30_000 });
|
|
await page.waitForSelector('[data-testid="g7le-preview-frame"]', { timeout: 30_000 });
|
|
|
|
const switcher = page.locator('[data-testid="g7le-state-switcher"]');
|
|
await expect(switcher).toBeVisible({ timeout: 20_000 });
|
|
});
|
|
});
|