- 등록부 home 줄 = mountHome · 썸네일 선 · 단계 점 · 「다음: 지금 단계」 · [이어서 하기] = 그 단계 화면 · [자세히] = 상세 - 4~8 = 팀원별 · 단계 분포 · 14 일 넘게 그대로 표시 · 9 넘음 = 요약 숫자 · 회사별 막대 · 종류 원(인라인 SVG) - 오른쪽 = 처리할 일 · 임시 보관함 · 회사(회사 관리자) · 예정노선(산림청) · 처음 = 세 걸음 · 꼴 고정(자동 · 작게 · 크게) 기억 - 홈 역할 차례 = 셸 roleOf 와 같게(회사 관리자가 산림청 앞) - test_546 등록부 시험 — import 한 mount 이름을 빈 함수로 저절로(페이지 줄 갈아 끼워도 그대로) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
153 lines
5.7 KiB
Python
153 lines
5.7 KiB
Python
"""547-2 대시보드 홈 — 개수별 꼴 고르기(TS logic) · 역할 가르기 · 역할별 홈 API 칸."""
|
|
|
|
import asyncio
|
|
import json
|
|
import re
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
import B01_Dashboard.B01_Dashboard_Router_Home as home
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
TSC = ROOT / "config" / "node_modules" / "typescript" / "bin" / "tsc"
|
|
LOGIC = ROOT / "B01_Dashboard" / "logic" / "B01_Dashboard_Home_Layout.ts"
|
|
|
|
_RUNNER = """
|
|
import { writeFileSync } from "node:fs";
|
|
import * as m from "./B01_Dashboard_Home_Layout.js";
|
|
const counts = [0, 1, 2, 3, 4, 8, 9, 40];
|
|
const out = {
|
|
auto: counts.map((n) => m.homeLayout(n)),
|
|
small: counts.map((n) => m.homeLayout(n, "small")),
|
|
large: counts.map((n) => m.homeLayout(n, "large")),
|
|
buckets: m.stageBuckets([
|
|
{ stage_index: 0, stage_total: 7 }, { stage_index: 3, stage_total: 7 },
|
|
{ stage_index: 6, stage_total: 7 }, { stage_index: 2, stage_total: 3 },
|
|
]),
|
|
stale: [m.isStale({ stage_index: 1, stage_total: 7, stale_days: 13 }),
|
|
m.isStale({ stage_index: 1, stage_total: 7, stale_days: 14 })],
|
|
count: m.countBy(["나", "가", "나"], (x) => x),
|
|
rest: m.topWithRest([5, 4, 3, 2, 1, 1].map((c, i) => ({ label: "c" + i, count: c })), 5),
|
|
donut: m.donutArcs([1, 1, 2]),
|
|
};
|
|
writeFileSync(process.argv[2], JSON.stringify(out));
|
|
"""
|
|
|
|
|
|
@pytest.fixture(scope="module")
|
|
def ts(tmp_path_factory):
|
|
if not TSC.is_file():
|
|
pytest.skip("프론트엔드 의존성 미설치")
|
|
tmp = tmp_path_factory.mktemp("home")
|
|
out = tmp / "js"
|
|
args = ["--outDir", str(out), "--module", "esnext", "--target", "es2022", "--ignoreConfig"]
|
|
subprocess.run( # noqa: S603 — 고정 실행 파일
|
|
["node", str(TSC), str(LOGIC), *args], cwd=str(ROOT), check=True, capture_output=True
|
|
)
|
|
for emitted in out.glob("*.js"):
|
|
text = emitted.read_text(encoding="utf-8")
|
|
emitted.write_text(re.sub(r'(from\s+"\./[^"]+)"', r'\1.js"', text), encoding="utf-8")
|
|
(out / "runner.mjs").write_text(_RUNNER, encoding="utf-8")
|
|
dst = tmp / "out.json"
|
|
subprocess.run(["node", str(out / "runner.mjs"), str(dst)], check=True, capture_output=True) # noqa: S603, S607
|
|
return json.loads(dst.read_text(encoding="utf-8"))
|
|
|
|
|
|
def test_layout_by_count(ts):
|
|
# 개수 0 1 2 3 4 8 9 40
|
|
assert ts["auto"] == ["empty", "one", "few", "few", "list", "list", "many", "many"]
|
|
assert ts["small"] == ["empty", "list", "list", "list", "list", "list", "many", "many"]
|
|
assert ts["large"] == ["empty", "one", "few", "few", "few", "few", "few", "few"]
|
|
|
|
|
|
def test_counts_and_charts(ts):
|
|
assert ts["buckets"] == {"start": 1, "working": 1, "last": 2}
|
|
assert ts["stale"] == [False, True]
|
|
assert ts["count"] == [{"label": "나", "count": 2}, {"label": "가", "count": 1}]
|
|
assert ts["rest"][-1] == {"label": "", "count": 2, "rest": 2} and len(ts["rest"]) == 5
|
|
assert [a["dash"] for a in ts["donut"]] == ["25 75", "25 75", "50 50"]
|
|
assert [a["offset"] for a in ts["donut"]] == [25, 0, -25]
|
|
|
|
|
|
def test_home_role_order_matches_shell():
|
|
s = {"email": "a@forest.go.kr"}
|
|
assert home.home_role({**s, "role": "SYSTEM_ADMIN"}) == "SYSTEM_ADMIN"
|
|
assert home.home_role({**s, "role": "ADMIN"}) == "ADMIN"
|
|
assert home.home_role({**s, "role": "USER"}) == "FOREST"
|
|
assert home.home_role({"email": "a@b.com", "role": "USER"}) == "USER"
|
|
|
|
|
|
class _Cursor:
|
|
async def __aenter__(self):
|
|
return self
|
|
|
|
async def __aexit__(self, *_):
|
|
return False
|
|
|
|
async def execute(self, sql, args=()):
|
|
self.sql = sql
|
|
|
|
async def fetchone(self):
|
|
return {"n": 2, "name": "회사", "member_count": 3, "companies": 4, "users": 9}
|
|
|
|
async def fetchall(self):
|
|
if "temp_upload_batches" in self.sql or "plan_route_years" in self.sql:
|
|
return []
|
|
return [{"id": 1, "name": "회사"}]
|
|
|
|
|
|
class _Pool:
|
|
def acquire(self):
|
|
class _Conn:
|
|
async def __aenter__(self):
|
|
return self
|
|
|
|
async def __aexit__(self, *_):
|
|
return False
|
|
|
|
def cursor(self, *_):
|
|
return _Cursor()
|
|
|
|
return _Conn()
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("session", "keys", "join"),
|
|
[
|
|
({"role": "SYSTEM_ADMIN", "user_id": 1, "company_id": 1}, {"system"}, True),
|
|
({"role": "ADMIN", "user_id": 1, "company_id": 1}, {"company"}, True),
|
|
({"role": "USER", "user_id": 1, "company_id": 4, "email": "x@a.go.kr"}, {"forest"}, False),
|
|
({"role": "USER", "user_id": 1, "company_id": 1, "email": "x@a.com"}, set(), False),
|
|
],
|
|
)
|
|
def test_home_blocks_by_role(monkeypatch, session, keys, join):
|
|
async def fake_ids(_session, _role):
|
|
return ["a", "b"]
|
|
|
|
async def fake_summaries(ids):
|
|
return [
|
|
{"id": i, "done": i == "b", "thumb": [[0, 0], [1, 1]]} for i in ids
|
|
] # b = 완료 → 빠짐
|
|
|
|
async def fake_plans(_session):
|
|
return {"routes": []}
|
|
|
|
async def fake_resources(days):
|
|
return {"current": {"cpu_usage_percent": 1, "memory_usage_percent": 2}}
|
|
|
|
import D01_PlanRegister.D01_PlanRegister_Router as d01
|
|
|
|
monkeypatch.setattr(home, "home_project_ids", fake_ids)
|
|
monkeypatch.setattr(home, "project_summaries", fake_summaries)
|
|
monkeypatch.setattr(home, "get_db_pool", lambda: _Pool())
|
|
monkeypatch.setattr(home, "get_system_resources", fake_resources)
|
|
monkeypatch.setattr(d01, "assigned_plan_routes", fake_plans)
|
|
body = asyncio.run(home.dashboard_home(session=session))
|
|
assert {"system", "company", "forest"} & body.keys() == keys
|
|
assert [p["id"] for p in body["projects"]] == ["a"] and body["project_total"] == 2
|
|
assert ("join_requests" in body["todos"]) is join
|
|
if "system" in body:
|
|
assert body["system"]["server"] == {"cpu": 1, "memory": 2, "disk": None}
|