feat(가이드): 538-2 [?] = 넓은 모달 한 가이드 · 차례 묶음 → 페이지 → 기능 · 그림 · 주의/참고 · B01 [?]
- [?] = openModal wide · 왼쪽 차례(임도 설계 B → 안정성평가 E → 예정노선 D · 지금 페이지 펼침) ·
오른쪽 = 모든 페이지 이어 그림 · 처음 = 보이는 data-help-topic 절 → 지금 페이지 절 → 맨 앞 · B05↔B06 짝 걷음
- 그리기: 덩어리마다 원문 줄(data-src) · `` → figure(loading=lazy · ?v=판) ·
없는 그림 = 관리자 「그림 없음 — 더블클릭해 지정」 / 일반 「그림 준비 중」 · `> 주의:` · `> 참고:` 상자 ·
`[글](B06/00_설명서)` → 모달 안 이동 · 페이지를 안 넘기면(B01 지식 채팅) 옛 출력 그대로
- GET /api/help 에 페이지마다 그림 판 · GET /api/help/{page}/img/{name}(이름 규칙 · no-cache) ·
옛 GET /api/help/{page} 걷음(모달이 한 가이드로 바뀜)
- B01 대시보드 머리 역할 배지 왼쪽에 [?]
- 시험 test_538_help_guide.py(변환 글 · 그림 404 · admin 칸) + helper_538_help_render.cjs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
/* PLAN 538 단위검증 헬퍼 — 진짜 `ui_template_help.ts` 의 `renderHelpMarkdown` 을 Node 로 돌려 변환 글을 JSON 으로 냄.
|
||||
* 화면 모듈(단추 · 모달 · 고침)은 빈 가짜로 끼움.
|
||||
*/
|
||||
const path = require("path");
|
||||
const { makeLoader } = require("./helper_ts_stub_loader.cjs");
|
||||
|
||||
const ROOT = path.join(__dirname, "..", "..");
|
||||
const STUBS = {
|
||||
"@config/config_frontend": {
|
||||
API_BASE_URL: "/api",
|
||||
ROUTES: {},
|
||||
PLAN_STEP_ROUTES: [],
|
||||
STAB_STEP_ROUTES: [],
|
||||
},
|
||||
"./ui_template_elements": {},
|
||||
"./ui_template_modal": {},
|
||||
"./ui_template_help_edit": {},
|
||||
};
|
||||
const { load } = makeLoader({ root: ROOT, stubs: STUBS });
|
||||
const { renderHelpMarkdown } = load("ui_template", "ui_template_help.ts");
|
||||
|
||||
const ctx = (admin) => ({ page: "B05", images: { "b05_full.webp": "abc" }, admin });
|
||||
const DOC = [
|
||||
"# 종단 설계", // 0
|
||||
"", // 1
|
||||
"첫 문단 [표준 횡단](B06/00_설명서) · [돌쌓기](B05/구조물_돌쌓기(메))", // 2
|
||||
"둘째 줄", // 3
|
||||
"", // 4
|
||||
"- 하나", // 5
|
||||
"- 둘", // 6
|
||||
"", // 7
|
||||
"| 칸 | 뜻 |", // 8
|
||||
"| --- | --- |", // 9
|
||||
"| a | b |", // 10
|
||||
"", // 11
|
||||
"> 주의: 저장 전 확인", // 12
|
||||
"> 둘째 줄", // 13
|
||||
"", // 14
|
||||
"> 참고: 알아 둘 것", // 15
|
||||
"", // 16
|
||||
"", // 17
|
||||
"", // 18
|
||||
"", // 19
|
||||
'<script>alert("x")</script>', // 20
|
||||
].join("\n");
|
||||
|
||||
process.stdout.write(
|
||||
JSON.stringify({
|
||||
plain: renderHelpMarkdown(DOC),
|
||||
admin: renderHelpMarkdown(DOC, ctx(true)),
|
||||
user: renderHelpMarkdown(DOC, ctx(false)),
|
||||
crlf: renderHelpMarkdown("# 가\r\n\r\n- 나\r\n- 다\r\n", ctx(false)),
|
||||
}),
|
||||
);
|
||||
@@ -0,0 +1,132 @@
|
||||
"""PLAN 538 [?] 사용자 가이드 — 변환 글 · 그림 내려주기 · 바로 고침.
|
||||
|
||||
짝: `ui_template/ui_template_help.ts`(`renderHelpMarkdown`)
|
||||
— `helper_538_help_render.cjs` 가 Node 로 돌림 ·
|
||||
`common_util/common_util_help_router.py`(GET /api/help · 그림 · PUT · POST).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
from fastapi import FastAPI
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from common_util import common_util_help_router as help_router
|
||||
from common_util.common_util_auth import verify_session
|
||||
|
||||
HERE = os.path.dirname(__file__)
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def html() -> dict:
|
||||
proc = subprocess.run( # noqa: S603 — 고정 실행 파일
|
||||
["node", os.path.join(HERE, "helper_538_help_render.cjs")],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
timeout=60,
|
||||
)
|
||||
assert proc.returncode == 0, proc.stderr
|
||||
return json.loads(proc.stdout)
|
||||
|
||||
|
||||
def test_덩어리마다_원문_줄(html) -> None:
|
||||
admin = html["admin"]
|
||||
for tag in [
|
||||
'<h2 data-src="0-0">',
|
||||
'<p data-src="2-3">',
|
||||
'<ul data-src="5-6">',
|
||||
'class="ui-markdown-table" data-src="8-10"',
|
||||
'data-src="12-13"',
|
||||
'<figure data-src="17-17"',
|
||||
]:
|
||||
assert tag in admin, tag
|
||||
assert '<ul data-src="2-3">' in html["crlf"]
|
||||
|
||||
|
||||
def test_페이지_안_넘기면_옛_출력(html) -> None:
|
||||
"""B01 지식 채팅 — 원문 줄 · 그림 · 상자 · 링크 없음."""
|
||||
plain = html["plain"]
|
||||
assert "data-src" not in plain and "<figure" not in plain and "<aside" not in plain
|
||||
assert plain.startswith("<h2>종단 설계</h2><p>첫 문단 [표준 횡단](B06/00_설명서)")
|
||||
|
||||
|
||||
def test_그림_상자_링크(html) -> None:
|
||||
admin, user = html["admin"], html["user"]
|
||||
assert (
|
||||
'<img src="/api/help/B05/img/b05_full.webp?v=abc" alt="화면 통째" loading="lazy">' in admin
|
||||
)
|
||||
assert "<figcaption>화면 통째</figcaption>" in admin
|
||||
assert "그림 없음 — 더블클릭해 지정" in admin and "그림 없음" not in user
|
||||
assert "그림 준비 중" in user
|
||||
assert (
|
||||
'<aside class="ui-help-note ui-help-note--warn" data-src="12-13"><strong>주의</strong>'
|
||||
in admin
|
||||
)
|
||||
assert '<aside class="ui-help-note ui-help-note--info"' in admin
|
||||
assert 'data-help-goto="B06/00_설명서">표준 횡단</a>' in admin
|
||||
assert 'data-help-goto="B05/구조물_돌쌓기(메)">돌쌓기</a>' in admin
|
||||
|
||||
|
||||
def test_html_은_먼저_막음(html) -> None:
|
||||
for out in html.values():
|
||||
assert "<script>" not in out
|
||||
assert "../x.webp" in html["admin"] and 'data-img="../x.webp"' not in html["admin"]
|
||||
|
||||
|
||||
# ── 서버 ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def root(monkeypatch):
|
||||
path = Path(tempfile.mkdtemp())
|
||||
(path / "B05").mkdir()
|
||||
(path / "B05" / "00_설명서.md").write_text("# 종단\n\n첫 문단\n", encoding="utf-8")
|
||||
monkeypatch.setattr(help_router, "HELP_ROOT", path)
|
||||
yield path
|
||||
shutil.rmtree(path, ignore_errors=True)
|
||||
|
||||
|
||||
def client(role: str = "SYSTEM_ADMIN") -> TestClient:
|
||||
app = FastAPI()
|
||||
app.include_router(help_router.router)
|
||||
app.dependency_overrides[verify_session] = lambda: {"role": role, "user_id": 1}
|
||||
return TestClient(app)
|
||||
|
||||
|
||||
def test_guide_admin_칸(root) -> None:
|
||||
assert client().get("/api/help").json()["admin"] is True
|
||||
body = client("USER").get("/api/help").json()
|
||||
assert body["admin"] is False and body["pages"][0]["page"] == "B05"
|
||||
|
||||
|
||||
def test_그림_내려주기_no_cache(root) -> None:
|
||||
(root / "B05" / "full.webp").write_bytes(b"RIFF0000WEBP")
|
||||
res = client("USER").get("/api/help/B05/img/full.webp")
|
||||
assert res.status_code == 200 and res.content == b"RIFF0000WEBP"
|
||||
assert res.headers["cache-control"] == "no-cache"
|
||||
assert res.headers["content-type"] == "image/webp"
|
||||
assert list(client().get("/api/help").json()["pages"][0]["images"]) == ["full.webp"]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"url",
|
||||
[
|
||||
"/api/help/B05/img/none.webp",
|
||||
"/api/help/B05/img/00_설명서.md",
|
||||
"/api/help/B05/img/..%2F..%2Fmain.py",
|
||||
"/api/help/B05/img/a.gif",
|
||||
"/api/help/..%2FB05/img/full.webp",
|
||||
"/api/help/b05/img/full.webp",
|
||||
],
|
||||
)
|
||||
def test_그림_404(root, url) -> None:
|
||||
(root / "B05" / "full.webp").write_bytes(b"RIFF0000WEBP")
|
||||
assert client().get(url).status_code == 404
|
||||
Reference in New Issue
Block a user