Files
Aislo/resources/tester/test_session_keys_registered.py
T
eomsangdonandClaude Opus 5.5 1c090c0f5e feat(횡단초안): 344-3 측점 열쇠 — 세션 횡단 초안을 측점 열쇠로 · 옛 초안 옮김 · 노선 바뀜 넘김 · 노선 밖 고침 알림
- 열쇠 규칙 한 벌 = common_util_station_key(ts · py 짝 · 거울 시험) — 일반 grid:k · 관 pipe:<id> · 구조물 structure:<id> · 시종점 end:BP/EP · id 모름 at:<0.01 m>
- 세션 칸 다섯(카드 선택 · 노견 확장 · 소단 깊이 · 암 경계 · 암 경사) = 측점 열쇠로 담음 · 부르는 쪽은 그대로 누가거리로 읽고 씀 · 칸마다 쓴 때 자리를 함께 담아 구조물 id 를 아직 모를 때도 그 측점 고침을 읽음
- 옛 초안(누가거리 열쇠)은 읽을 때 측점 열쇠로 옮김 · 지금 측점에 없는 고침은 지우지 않고 남김
- 측점 목록 = 상세가 초안 측점을 얹을 때 · 다시 셈 때 세움(관 id = 관 장부 · 구조물 id = 측점 계획 · 구조물 목록)
- 노선 바뀜 넘김 carryStationDrafts — 카드 선택 넷(지반 유형 · 측구 종류 · 측구 선택 · 2단 경사) · 암 경계 · 암 경사만 · 단면 유형 · 측구 방향은 새 지형이 정함 · 사라진 측점 = 「노선 밖 고침 N 건」 한 줄(부르는 자리 = 3파)
- 옛 시험 셋 새 창구로 맞춤

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QfHKQs7J3hQELKMWQMUJEy
2026-10-16 11:45:31 +09:00

115 lines
5.6 KiB
Python

"""등록표가 정한 값을 **제 맘대로 저장하지 않는지** — 키도, 저장소도.
왜 (2026-09-07 실사고) — 암 경계선 오프셋은 쓰는 쪽이 `stateKey("rockb", …)` 로
`aislo:draft:rockb:{p}:{r}` 에 쓰는데, 읽는 쪽이 옛 키 `b06:rockb:{p}:{r}` 를 날문자열로
읽고 있었다. 옛 키는 아무도 안 쓰므로 **읽는 쪽이 늘 빈 값**을 받았고, 계획선을 고쳐
횡단을 다시 계산할 때 사용자가 옮긴 암 경계선이 안 실려 나갔다(토사/암 나눔 = 수량).
키 이름을 정하는 자리는 `A00_Common/b_page_state.ts` 의 등록표 하나뿐이다. 세션을 직접
만지는 모듈이 있어도 **키는 `stateKey` 로 받아야** 한다. 이 시험은 날문자열 키를 막는다.
"""
import os
import re
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
#: 등록표 자신 — 여기서만 키 문자열을 만든다.
_REGISTRY = ROOT / "A00_Common" / "b_page_state.ts"
#: 세션을 직접 만지는 호출. 인자가 따옴표·백틱으로 시작하면 **날문자열 키**다.
_RAW_CALL = re.compile(r"sessionStorage\.(?:get|set|remove)Item\(\s*[\"'`]")
#: 훑을 화면 폴더 — 저장소 전체를 rglob 하면 `node_modules` 정션까지 타서 느리다.
_ROOTS = (
"A00_Common",
"B05_Profile",
"B06_Section",
"B07_DesignDetail",
"common_util",
"ui_template",
)
def _sources() -> list[Path]:
"""대상 폴더를 훑되 `node_modules` 는 **들어가기 전에** 가지친다.
(걸러내기만 하면 B07 CAD 앱의 의존성 트리를 통째로 걸어 몇 분씩 걸린다.)"""
files: list[Path] = []
for name in _ROOTS:
folder = ROOT / name
if not folder.is_dir():
continue
for current, dirs, names in os.walk(folder):
dirs[:] = [d for d in dirs if d not in ("node_modules", "dist", ".git")]
for filename in names:
if not filename.endswith(".ts"):
continue
path = Path(current) / filename
if path != _REGISTRY:
files.append(path)
return files
def test_등록표_밖에서_날문자열_세션_키를_안_쓴다():
offenders = []
for path in _sources():
text = path.read_text(encoding="utf-8")
for number, line in enumerate(text.splitlines(), start=1):
if _RAW_CALL.search(line):
offenders.append(f"{path.relative_to(ROOT)}:{number}: {line.strip()[:80]}")
assert not offenders, "세션 키를 등록표 없이 만든 자리:\n" + "\n".join(offenders)
def test_옛_암경계_키가_코드에_안_남아_있다():
"""실사고 자리 못박기 — 읽는 쪽이 등록표를 거치는지."""
persist = (ROOT / "B06_Section" / "B06_Section_UI_Page_Persist.ts").read_text(encoding="utf-8")
# 344-3 — 측점 열쇠 창구(readKeyed → 등록표 readState)로 읽음
assert 'readKeyed<number>("rockb", projectId, routeId)' in persist, "등록표를 안 거침"
# 옛 키가 **코드로** 되살아나는 것만 막는다 — 주석에 사연으로 적힌 것은 남겨 둔다.
code = "\n".join(
line for line in persist.splitlines() if not line.lstrip().startswith(("*", "//", "/*"))
)
assert "b06:rockb:" not in code, "옛 키가 코드에 남아 있음"
def test_시험이_빈_목록으로_통과하지_않는다():
"""훑는 대상이 실제로 있는지 — 경로가 틀리면 조용히 통과한다."""
files = _sources()
assert len(files) > 100, f"TS 파일을 {len(files)}개밖에 못 찾음 — 경로 규칙 확인"
assert any("B06_Section" in str(path) for path in files)
#: 저장소를 직접 이름으로 고르는 것이 **정당한** 자리.
#: `b_page_state` 는 고르는 곳 자신이고, 나머지는 등록표 밖의 제 값을 쓰는 자리다.
_STORAGE_PICKERS_OK = {
"b_page_state.ts", # 여기가 유일하게 고르는 곳(`storageFor`/`storageOf`)
}
#: 등록표가 관리하지 않는 앱 전역 값 — 이 키를 쓰는 줄은 이 시험의 대상이 아니다.
#: `frd_current_project_id`(`config_frontend.ts:29`)는 「지금 보고 있는 프로젝트」 가리개라
#: 화면 상태가 아니고, 워크플로우 전체가 그 한 값을 본다.
_NON_REGISTRY_KEYS = ("CURRENT_PROJECT_ID_KEY",)
def test_등록표_키를_쓰면서_저장소를_직접_고르지_않는다():
"""오늘 두 번째로 같은 계통이 났다 — 키는 등록표에서 받는데 **저장소는 제 맘대로** 고르는 자리.
`rockb` 는 키를 날문자열로 만들어 샜고(위 시험이 막음), 화면 취향은 키는 맞았는데
저장소를 직접 골라 **절반만 옮겨졌다**(접힘 4개는 옮겨지고 높이 3개는 세션에 남음).
저장소 선택도 올려보내기도 `readByKey`/`writeByKey` 한 창구만 하도록 못박는다.
"""
offenders = []
for path in _sources():
if path.name in _STORAGE_PICKERS_OK:
continue
text = path.read_text(encoding="utf-8")
if "stateKey(" not in text and "ByKey(" not in text:
continue # 등록표와 무관한 제 값을 쓰는 자리 — 이 시험의 대상이 아니다.
for number, line in enumerate(text.splitlines(), start=1):
if any(name in line for name in _NON_REGISTRY_KEYS):
continue
if re.search(r"(?<![.\w])(window\.)?(session|local)Storage\.", line):
offenders.append(f"{path.relative_to(ROOT)}:{number}: {line.strip()[:80]}")
assert not offenders, "저장소를 직접 고른 자리(`storageOf` 를 쓸 것):\n" + "\n".join(offenders)