Merge remote-tracking branch 'origin/sub_laptop_2' into sub_laptop_1
This commit is contained in:
@@ -787,3 +787,16 @@
|
||||
outline: 2px solid var(--color-royal-amethyst, #6843a7);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
/* 계산 자리 창(504-2) — 묶음 · 까닭 글은 줄바꿈 · 체크 칸은 가운데 */
|
||||
.b01-dashboard__calc-sites .b01-dashboard__table td {
|
||||
white-space: normal;
|
||||
}
|
||||
.b01-dashboard__calc-sites .b01-dashboard__table td:nth-child(2),
|
||||
.b01-dashboard__calc-sites .b01-dashboard__table td:nth-child(3) {
|
||||
text-align: center;
|
||||
}
|
||||
.b01-dashboard__calc-count,
|
||||
.b01-dashboard__calc-note {
|
||||
margin: 0 0 0.5rem;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
"""PLAN 504-2 화면 — 자기 포트(5177) Playwright 머리 없는 브라우저 · 서버가 떠 있을 때만.
|
||||
|
||||
- 시스템 관리자 = 「계산 자리」 카드 · 창(밝음 · 어두움 한 장씩 · tmp/shots_504_2)
|
||||
- 회사 관리자 · 일반 = 카드 없음 · PUT 403
|
||||
- G1 체크 끔 → [저장] → B05 열기(검증_sub2) → 지반 표본이 서버 길(`/route/elevations`) · 판 기본값으로 되돌림
|
||||
|
||||
./venv/Scripts/python.exe -m pytest resources/tester/test_309_2_calc_sites_browser.py -q
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import subprocess
|
||||
import urllib.request
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
BASE = "http://localhost:5177"
|
||||
BOARD = ROOT / "resources" / "master_template" / "calc_sites.json"
|
||||
PROJECT = "cb15892a-5788-4a26-9316-20a15421f68b" # 검증_sub2(OWNERS.md)
|
||||
SHOTS = ROOT / "tmp" / "shots_504_2"
|
||||
CARD = "계산 자리"
|
||||
|
||||
|
||||
def _up() -> bool:
|
||||
try:
|
||||
urllib.request.urlopen(BASE, timeout=3)
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
pytestmark = pytest.mark.skipif(not _up(), reason="5177 서버 없음")
|
||||
|
||||
|
||||
def _cred(who: str) -> dict:
|
||||
if who == "시스템 관리자":
|
||||
return json.loads((ROOT / "tmp" / "aislo_cred.json").read_text(encoding="utf-8"))
|
||||
accounts = json.loads((ROOT / "tmp" / "verify_accounts.json").read_text(encoding="utf-8"))
|
||||
return {k: accounts[who][k] for k in ("email", "password")}
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def browser():
|
||||
sync = pytest.importorskip("playwright.sync_api")
|
||||
committed = subprocess.run(
|
||||
["git", "show", "HEAD:resources/master_template/calc_sites.json"],
|
||||
cwd=ROOT, capture_output=True, check=True,
|
||||
).stdout # fmt: skip
|
||||
with sync.sync_playwright() as play:
|
||||
chrome = play.chromium.launch(channel="msedge", headless=True)
|
||||
yield chrome
|
||||
chrome.close()
|
||||
BOARD.write_bytes(committed) # 판 = 커밋한 기본값 그대로
|
||||
|
||||
|
||||
def _context(browser, who: str):
|
||||
ctx = browser.new_context(viewport={"width": 1400, "height": 900})
|
||||
r = ctx.request.post(f"{BASE}/api/auth/login/request", data=_cred(who), timeout=60000)
|
||||
assert r.ok, f"로그인 실패 {r.status}" # 비밀번호는 안 찍음
|
||||
return ctx
|
||||
|
||||
|
||||
def _dashboard(ctx):
|
||||
page = ctx.new_page()
|
||||
page.goto(f"{BASE}/#/b01-account", wait_until="domcontentloaded")
|
||||
page.wait_for_selector(".b01-dashboard__setting-card", timeout=60000)
|
||||
return page
|
||||
|
||||
|
||||
def _cards(page) -> list[str]:
|
||||
return page.eval_on_selector_all(
|
||||
".b01-dashboard__setting-title", "(els) => els.map((e) => e.textContent.trim())"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("who", ["회사관리자", "일반"])
|
||||
def test_다른_역할_카드_없음_403(browser, who):
|
||||
ctx = _context(browser, who)
|
||||
try:
|
||||
assert CARD not in _cards(_dashboard(ctx))
|
||||
put = ctx.request.put(
|
||||
f"{BASE}/api/dashboard/admin/calc-sites",
|
||||
data={"sites": {"ground_sample": "server"}},
|
||||
)
|
||||
assert put.status == 403
|
||||
assert ctx.request.get(f"{BASE}/api/dashboard/calc-sites").ok # 읽기 = 로그인
|
||||
finally:
|
||||
ctx.close()
|
||||
|
||||
|
||||
def _elevation_calls(ctx) -> int:
|
||||
page = ctx.new_page()
|
||||
# 대시보드가 지금 프로젝트 칸을 비움 — 페이지 스크립트보다 먼저 넣음
|
||||
page.add_init_script(f"localStorage.setItem('frd_current_project_id', '{PROJECT}')")
|
||||
calls: list[str] = []
|
||||
page.on("request", lambda r: "/route/elevations" in r.url and calls.append(r.url))
|
||||
page.goto(f"{BASE}/#/b05-profile", wait_until="domcontentloaded")
|
||||
page.wait_for_function("() => !!window.__aisloPipeline", timeout=90000)
|
||||
page.wait_for_timeout(8000)
|
||||
page.close()
|
||||
return len(calls)
|
||||
|
||||
|
||||
def test_시스템_관리자_카드_창_G1_서버_길(browser):
|
||||
ctx = _context(browser, "시스템 관리자")
|
||||
try:
|
||||
before = _elevation_calls(ctx) # 기본값 = 격자(브라우저)
|
||||
page = _dashboard(ctx)
|
||||
assert CARD in _cards(page)
|
||||
page.get_by_text(CARD, exact=True).click()
|
||||
dialog = page.wait_for_selector(".b01-dashboard__calc-sites", timeout=30000)
|
||||
rows = page.eval_on_selector_all(
|
||||
".b01-dashboard__calc-sites tbody tr",
|
||||
"(trs) => trs.map((tr) => [...tr.querySelectorAll('input')]"
|
||||
".map((i) => [i.checked, i.disabled]))",
|
||||
)
|
||||
assert len(rows) == 8
|
||||
assert rows[0] == [[True, False], [True, True]] # G1 바꿀 수 있음 · 서버 정본 잠금
|
||||
assert all(row[0][1] and row[1][1] for row in rows[2:]) # 나머지 잠금
|
||||
assert "지금 서버 0 줄" in dialog.inner_text()
|
||||
SHOTS.mkdir(parents=True, exist_ok=True)
|
||||
dialog.screenshot(path=str(SHOTS / "light.png"))
|
||||
page.evaluate("document.documentElement.setAttribute('data-theme', 'dark')")
|
||||
dialog.screenshot(path=str(SHOTS / "dark.png"))
|
||||
page.evaluate("document.documentElement.setAttribute('data-theme', 'light')")
|
||||
|
||||
page.click("input[data-calc-site='ground_sample']")
|
||||
assert "지금 서버 1 줄" in dialog.inner_text()
|
||||
page.locator(".b01-dashboard__calc-sites .ui-modal__actions button").click()
|
||||
page.wait_for_selector(".b01-dashboard__calc-sites", state="detached", timeout=30000)
|
||||
assert json.loads(BOARD.read_text("utf-8"))["sites"]["ground_sample"] == "server"
|
||||
logs = ctx.request.get(f"{BASE}/api/dashboard/admin/audit-logs").json()["items"]
|
||||
assert any(
|
||||
log.get("action") == "CALC_SITE_CHANGE"
|
||||
and log.get("resource_ref") == "ground_sample=server"
|
||||
for log in logs
|
||||
)
|
||||
|
||||
after = _elevation_calls(ctx)
|
||||
print(f"elevations 요청: 기본값 {before} · 서버 {after}")
|
||||
assert after > before
|
||||
|
||||
back = ctx.request.put(
|
||||
f"{BASE}/api/dashboard/admin/calc-sites", data={"sites": {"ground_sample": "both"}}
|
||||
)
|
||||
assert back.ok and back.json()["sites"]["ground_sample"] == "both"
|
||||
finally:
|
||||
ctx.close()
|
||||
Reference in New Issue
Block a user