Merge remote-tracking branch 'origin/sub_laptop_6' into main_laptop_1
This commit is contained in:
@@ -78,7 +78,7 @@ def test_화면_층_단추_최상위_폴더_이름_바꾸기():
|
||||
|
||||
def test_구조물_조합_층_제자리_바꿈():
|
||||
"""720-4 — 해시 이동 없음 · replaceState · 나무 · 층 줄 · 띠를 감싼 칸에 갈아 끼움 · 권한 = 함수 ·
|
||||
도면 양식 = 누구나(단추는 관리자) · 실무문서 = 시스템 관리자 · 불러오기 장부 = 열 때 층 · 권한."""
|
||||
도면 양식 = 누구나(단추는 관리자) · 불러오기 장부 = 열 때 층 · 권한."""
|
||||
page = _text("M02_MasterTemplete_UI_Page.ts")
|
||||
assert "navigateTo" not in page
|
||||
switch = page.split("const switchStructure = async")[1].split("const go =")[0]
|
||||
@@ -91,10 +91,10 @@ def test_구조물_조합_층_제자리_바꿈():
|
||||
assert "() => isAdmin," in page and 'isSystem: user?.role === "SYSTEM_ADMIN",' in page
|
||||
assert "main.root.isConnected ? void afterItem()" in page # 나무를 갈아도 듣기 그대로
|
||||
side = _text("M02_MasterTemplete_UI_Side.ts")
|
||||
assert "const drawing = section(" in side and "const practice = opt.isSystem" in side
|
||||
assert "const drawing = section(" in side and "practice" not in side # 732-1
|
||||
# 도면 양식 칸 = 누구나 · 단추만 관리자(720-4 덧)
|
||||
assert "const drawing = opt.isSystem" not in side
|
||||
assert 'if (!opt.isSystem) return [];' in side
|
||||
assert "if (!opt.isSystem) return [];" in side
|
||||
assert "items = await listTemplates(layer, null);" in side
|
||||
hopyo = _text("M02_MasterTemplete_Hopyo_Side.ts")
|
||||
assert "buildHopyoLedger({ scope: pageScope(), canWrite: opt.canWrite() })" in hopyo
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""일위대가 조합(PLAN 4-2 · 27-2a) — 담은 단가산출 로직 목록만 두고 미리 보기로 비목별 합계.
|
||||
|
||||
길 = M02 층 길 마스터(`/api/m02/layers/system/combos` · 옛 `/api/m01/combo*` 를 옮김).
|
||||
읽기 = M02 조합 저장소(`combos.one` · `combos.listing` · 서버 길은 없앰 732-1).
|
||||
|
||||
계약 `resources/master_data/_화면_계약.md` 9장 · 틀 `_틀.md` 10장 · 엔진 `scripts/master_combo.py`.
|
||||
못박는 것
|
||||
@@ -39,7 +39,6 @@ STACK = "GF000219" # 13-4-1 돌쌓기 메쌓기(인력)
|
||||
LAYER = "GF000220" # 13-4-2 돌쌓기 메쌓기(장비)
|
||||
GATHER = "GF000211" # 13-2-1 모래·자갈·약돌 채집(인력)
|
||||
SAMPLE = (STACK, LAYER, GATHER) # 견본 조합 — 로직 셋
|
||||
SYS = "/api/m02/layers/system/combos" # 마스터 층 조합 길
|
||||
ADMIN = {"user_id": 1, "company_id": 1, "role": "SYSTEM_ADMIN", "is_master": False}
|
||||
|
||||
|
||||
@@ -87,12 +86,12 @@ def _new(client: TestClient, body: dict, want: int = 200) -> dict:
|
||||
|
||||
|
||||
def _one(client: TestClient, key: str) -> dict:
|
||||
got = _get(client, f"{SYS}/{key}")
|
||||
got = combos.one(key)
|
||||
return {"version": got["판"], **got}
|
||||
|
||||
|
||||
def _list(client: TestClient) -> list[dict]:
|
||||
return _get(client, SYS)["줄"]
|
||||
return combos.listing()["줄"]
|
||||
|
||||
|
||||
def _edit(client: TestClient, key: str, version: str, row: dict, want: int = 200) -> dict:
|
||||
@@ -177,7 +176,7 @@ def test_목록은_소유_담은_수_폴더를_준다(client: TestClient) -> Non
|
||||
"""거르기(구분 · 소유 · 찾기)는 화면 몫 — 층 목록은 통째 · 폴더 = [구분, 상세구분]."""
|
||||
_make(client, 이름="돌쌓기 한 벌")
|
||||
_make(client, keys=(GATHER,), 이름="채집만", owner="공용")
|
||||
got = _get(client, SYS)
|
||||
got = combos.listing()
|
||||
assert len(got["줄"]) == 2 and got["판"]
|
||||
only = [c for c in got["줄"] if c["소유"] == "공용"]
|
||||
assert [c["이름"] for c in only] == ["채집만"] and only[0]["count"] == 1
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""조합 알약(`GET /api/m02/layers/system/combos/{키}/inputs`) — 결과단위 분모가 물량 하나.
|
||||
"""조합 알약(저장소 `combo_inputs`) — 결과단위 분모가 물량 하나.
|
||||
|
||||
못박는 것
|
||||
- 물량 = 로직 결과단위 분모(원/㎥ → ㎥) · 설계 조건(고르기 · 범위)은 안 넣음(`조건` 은 빈 자리).
|
||||
@@ -26,7 +26,6 @@ from M02_MasterTemplete import M02_MasterTemplete_Combo_Store as combos
|
||||
from M02_MasterTemplete import M02_MasterTemplete_Store as m02
|
||||
from M02_MasterTemplete.M02_MasterTemplete_Router_Combos import router as combos_router
|
||||
|
||||
SYS = "/api/m02/layers/system/combos"
|
||||
ADMIN = {"user_id": 1, "company_id": 1, "role": "SYSTEM_ADMIN", "is_master": False}
|
||||
|
||||
|
||||
@@ -118,22 +117,24 @@ def _use(sheet: Path, key: str) -> None:
|
||||
def test_쓰는_열이_있으면_조합을_못_지우고_구조물_이름을_알린다(own) -> None:
|
||||
key = _new(own, "쓰는 조합", ["GF000222"])
|
||||
_use(combo_store.USERS[0], key)
|
||||
version = own.get(f"{SYS}/{key}").json()["판"]
|
||||
version = combos.one(key)["판"]
|
||||
with pytest.raises(m02.StoreError) as caught: # 지우기 길은 은퇴(39-1d) — 저장소 함수로
|
||||
combos.delete(key, version)
|
||||
assert caught.value.status == 409
|
||||
detail = caught.value.detail
|
||||
assert detail["users"] == ["돌기슭막이 찰쌓기"]
|
||||
assert "돌기슭막이 찰쌓기" in detail["errors"][0]
|
||||
assert own.get(f"{SYS}/{key}").status_code == 200 # 그대로 남음
|
||||
assert combos.one(key)["combo"]["키"] == key # 그대로 남음
|
||||
|
||||
|
||||
def test_안_쓰는_조합은_지워진다(own) -> None:
|
||||
key = _new(own, "안 쓰는 조합", ["GF000222"])
|
||||
_use(combo_store.USERS[0], "UA000009") # 다른 조합을 씀
|
||||
version = own.get(f"{SYS}/{key}").json()["판"]
|
||||
version = combos.one(key)["판"]
|
||||
combos.delete(key, version)
|
||||
assert own.get(f"{SYS}/{key}").status_code == 404
|
||||
with pytest.raises(m02.StoreError) as gone:
|
||||
combos.one(key)
|
||||
assert gone.value.status == 404
|
||||
|
||||
|
||||
def test_본뜬키는_목록에_나오고_없는_키_자기_키는_400(own) -> None:
|
||||
@@ -141,13 +142,13 @@ def test_본뜬키는_목록에_나오고_없는_키_자기_키는_400(own) -> N
|
||||
combo = {"이름": "본뜬 것", "본뜬키": base, "담은로직": [{"로직": "GF000222", "메모": None}]}
|
||||
got = combos.create(combo)
|
||||
assert got["combo"]["본뜬키"] == base
|
||||
rows = {r["키"]: r for r in own.get(SYS).json()["줄"]}
|
||||
rows = {r["키"]: r for r in combos.listing()["줄"]}
|
||||
assert rows[got["키"]]["본뜬키"] == base and rows[base]["본뜬키"] is None
|
||||
bad = {**combo, "이름": "없는 원본", "본뜬키": "UA999999"}
|
||||
with pytest.raises(m02.StoreError) as caught:
|
||||
combos.create(bad)
|
||||
assert caught.value.status == 400
|
||||
version = own.get(f"{SYS}/{base}").json()["판"]
|
||||
version = combos.one(base)["판"]
|
||||
me = {"이름": "원본", "본뜬키": base, "담은로직": combo["담은로직"]}
|
||||
with pytest.raises(store.StoreError) as caught: # 조합 고치기 길(PUT) 은퇴 — 저장소 함수로
|
||||
combo_store.combo_edit(base, version, me)
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
- 만들기 · 고치기 · 지우기 · 알약 · 고르기(use-combo) · 갈라 내기 길은 은퇴(39-1d) — 만들기 ·
|
||||
지우기는 층 문(`_root`) + 저장소 함수로 봄.
|
||||
- 세 층 목록 · 키 하나 읽기 · 조합 이름 · 알약(저장소) · 스냅샷 원본이 회사 · 개인 조합도 읽음.
|
||||
- 조합 목록 · 키 하나 · 통째 지우기 서버 길은 없앰(732-1) — 읽기는 저장소 함수로 봄.
|
||||
정본(마스터 · 마스터 템플릿)은 읽기만 · 쓰기는 임시 storage 와 마스터 사본.
|
||||
"""
|
||||
|
||||
@@ -52,27 +53,9 @@ def world(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> dict[str, Any]:
|
||||
app.include_router(items_router.router)
|
||||
app.include_router(combos_router.router)
|
||||
app.dependency_overrides[verify_session] = lambda: session
|
||||
# 문(39-6 · 시스템 관리자만)은 `test_조합_길은_시스템_관리자만` 이 봄 — 여기는 그 안의 층 규칙
|
||||
app.dependency_overrides[combos_router._door] = lambda: None
|
||||
return {"client": TestClient(app), "session": session, "storage": storage}
|
||||
|
||||
|
||||
def test_조합_길은_시스템_관리자만(world):
|
||||
"""39-6 — 일위대가 조합 = 실무문서 · 조합 길 전부 시스템 관리자만 · 담을 로직은 로그인만."""
|
||||
world["client"].app.dependency_overrides.pop(combos_router._door)
|
||||
for role in ("USER", "ADMIN"):
|
||||
client = _as(world, role)
|
||||
assert client.get(URL.format("system")).status_code == 403
|
||||
assert client.get("/api/m02/combos/key/UA000001").status_code == 403
|
||||
assert client.get("/api/m02/logic-lib/logics").status_code == 200
|
||||
world["session"]["is_master"] = True
|
||||
assert _as(world, "USER").get(URL.format("system")).status_code == 403 # 회사 마스터도
|
||||
world["session"]["is_master"] = False
|
||||
admin = _as(world, "SYSTEM_ADMIN")
|
||||
assert admin.get(URL.format("system")).status_code == 200
|
||||
assert admin.get(URL.format("company")).status_code == 200
|
||||
|
||||
|
||||
def _as(world: dict, role: str = "USER", user: int = 42) -> TestClient:
|
||||
world["session"].update(role=role, user_id=user)
|
||||
return world["client"]
|
||||
@@ -99,12 +82,6 @@ def _new(client: TestClient, layer: str, combo: dict, want: int = 200, **params)
|
||||
return got if code == 200 else {"detail": detail}
|
||||
|
||||
|
||||
def _list(client: TestClient, layer: str, **params) -> dict:
|
||||
res = client.get(URL.format(layer), params=params)
|
||||
assert res.status_code == 200, res.text
|
||||
return res.json()
|
||||
|
||||
|
||||
# ── 키 · 자리 ─────────────────────────────────────────────────────────
|
||||
def test_회사_개인_키_장부_지운_번호는_다시_안_줌(world):
|
||||
boss = _as(world, "ADMIN")
|
||||
@@ -120,7 +97,8 @@ def test_회사_개인_키_장부_지운_번호는_다시_안_줌(world):
|
||||
with store.at(layers.personal_dir(7, 42)): # 지우기 길은 은퇴(39-1d) — 저장소 함수로
|
||||
combos.delete(mine["키"], mine["판"])
|
||||
assert _new(me, "personal", _combo("다음 것"))["키"] == "UP42-000002"
|
||||
got = me.get(f"{URL.format('personal')}/UP42-000002").json()
|
||||
with store.at(layers.personal_dir(7, 42)):
|
||||
got = combos.one("UP42-000002")
|
||||
assert [x["로직"] for x in got["줄"]] == [A] and got["blocked"] is False
|
||||
|
||||
|
||||
@@ -129,9 +107,6 @@ def test_권한_층_규칙(world):
|
||||
_new(user, "system", _combo("마스터"), 403)
|
||||
_new(user, "company", _combo("회사"), 403)
|
||||
_new(user, "personal", _combo("남의 것"), 403, owner="7/43")
|
||||
assert user.get(URL.format("system")).status_code == 200 # 마스터 읽기 = 누구나
|
||||
assert user.get(URL.format("company")).status_code == 200 # 같은 회사 읽기
|
||||
assert user.get(URL.format("personal"), params={"owner": "8/9"}).status_code == 403
|
||||
_as(world, "ADMIN")
|
||||
_new(user, "company", _combo("회사"))
|
||||
world["session"]["company_id"] = None
|
||||
@@ -151,16 +126,15 @@ def test_검사_빈_조합_이름_겹침_본뜬키_판(world):
|
||||
|
||||
|
||||
def test_조합_쓰기_알약_길은_은퇴(world):
|
||||
"""39-1d — 만들기 · 고치기 · 지우기 · 알약 길 없음(읽기 · 실무문서 통째 지우기만 남음)."""
|
||||
"""39-1d — 만들기 · 고치기 · 지우기 · 알약 길 없음(조합 서버 길 자체가 없음 · 732-1)."""
|
||||
me = _as(world, "SYSTEM_ADMIN")
|
||||
key = _new(me, "personal", _combo("원본"))["키"]
|
||||
url = f"{URL.format('personal')}/{key}"
|
||||
assert me.post(URL.format("personal"), json={"combo": _combo("새")}).status_code == 405
|
||||
assert me.put(url, json={"combo": _combo("고침")}).status_code == 405
|
||||
assert me.delete(url, params={"판": ""}).status_code == 405
|
||||
assert me.get(f"{url}/inputs").status_code == 404
|
||||
assert me.post(URL.format("personal"), json={"combo": _combo("새")}).status_code in (404, 405)
|
||||
assert me.put(url, json={"combo": _combo("고침")}).status_code in (404, 405)
|
||||
assert me.delete(url, params={"판": ""}).status_code in (404, 405)
|
||||
assert me.get(url).status_code in (404, 405)
|
||||
assert me.get(f"/api/m02/combos/key/{key}/inputs").status_code == 404
|
||||
assert me.get(url).status_code == 200 # 읽기는 남음
|
||||
|
||||
|
||||
# ── 마스터 사본 ───────────────────────────────────────────────────────
|
||||
@@ -184,7 +158,7 @@ def master_copy(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path:
|
||||
|
||||
|
||||
# ── 세 층 고르기 · 갈라 내기 · 읽는 곳 ─────────────────────────────────
|
||||
def test_세_층_목록_고르기_남의_층_403_갈라_낸_것은_그_층에(world):
|
||||
def test_세_층_조합_찾기_갈라_낸_것은_그_층에(world):
|
||||
boss = _as(world, "ADMIN")
|
||||
uc = _new(boss, "company", _combo("회사 조합", (A, B)))["키"]
|
||||
me = _as(world)
|
||||
@@ -192,13 +166,8 @@ def test_세_층_목록_고르기_남의_층_403_갈라_낸_것은_그_층에(wo
|
||||
assert me.get("/api/m02/combos/all").status_code == 404 # 세 층 고르기 길 은퇴(39-1d)
|
||||
assert combos.find(uc)["이름"] == "회사 조합" and combos.find(up)["이름"] == "내 조합"
|
||||
assert combos.find("UP42-999999") is None and combos.find("엉터리") is None
|
||||
by_key = me.get(f"/api/m02/combos/key/{up}").json()
|
||||
assert (by_key["layer"], by_key["owner"], by_key["combo"]["키"]) == ("personal", "7/42", up)
|
||||
assert me.get(f"/api/m02/combos/key/{uc}").json()["owner"] == "7"
|
||||
assert me.get("/api/m02/combos/key/UP42-999999").status_code == 404
|
||||
world["session"]["company_id"] = 8 # 다른 회사 사람 — 남의 회사 조합은 못 봄
|
||||
assert me.get(f"/api/m02/combos/key/{uc}").status_code == 403
|
||||
world["session"]["company_id"] = 7
|
||||
assert combos.where(up) == ("personal", "7/42") and combos.where(uc) == ("company", "7")
|
||||
assert me.get(f"/api/m02/combos/key/{up}").status_code == 404 # 키 하나 읽기 길 없앰(732-1)
|
||||
# 고르기(use-combo) · 갈라 내기 길은 은퇴(39-1d) — 열마다 labor 문서
|
||||
base = "/api/m02/layers/personal/structures/c1"
|
||||
assert me.post(f"{base}/use-combo", json={"종류": "일위대가", "키": up}).status_code == 404
|
||||
|
||||
@@ -96,20 +96,6 @@ def test_권한_없는_원본_층_403_없는_원본_404(world):
|
||||
assert client.post(URL.format("system", "labor", "c1") + "?from=rv").status_code == 403
|
||||
|
||||
|
||||
def test_실무문서_조합에서_일위대가로_시스템_관리자만(world):
|
||||
url = URL.format("system", "labor", "c1") + "?from=UA000008&from_layer=practice"
|
||||
assert _as(world).post(url).status_code == 403
|
||||
admin = _as(world, "SYSTEM_ADMIN")
|
||||
assert admin.post(url).status_code == 200
|
||||
c1 = _labor(world["system"], "c1")
|
||||
assert c1["본뜬"] == {"층": "system", "주인": None, "키": "UA000008"}
|
||||
assert len(c1["담은로직"]) == 14 and all(x["입력값"] == {} for x in c1["담은로직"])
|
||||
area = URL.format("system", "area", "c1") + "?from=UA000008&from_layer=practice"
|
||||
assert admin.post(area).status_code == 403 # 실무문서는 일위대가로만
|
||||
bad = URL.format("system", "labor", "c1") + "?from=UP1-000001&from_layer=practice"
|
||||
assert admin.post(bad).status_code == 400
|
||||
|
||||
|
||||
def test_구조물_조합_복사는_새_일위대가번_조합_키는_안_받음(world):
|
||||
system, personal = world["system"], world["personal"]
|
||||
with store.at(system):
|
||||
|
||||
@@ -97,8 +97,8 @@ def test_주소_인자를_라우트에서_뗌():
|
||||
|
||||
@pytest.mark.skipif(shutil.which("node") is None, reason="node 가 없음")
|
||||
def test_가져올_곳_탭():
|
||||
"""39-6 · 답 2 — 탭 = 마스터 · 볼 회사 · 내 개인 · 다른 사람(맡은 회사) · 실무문서(시스템 관리자 ·
|
||||
일위대가) · 기본 = 시스템 관리자 마스터 · 그 밖 내 개인(회사 없으면 마스터)."""
|
||||
"""39-6 · 답 2 — 탭 = 마스터 · 볼 회사 · 내 개인 · 다른 사람(맡은 회사) ·
|
||||
기본 = 시스템 관리자 마스터 · 그 밖 내 개인(회사 없으면 마스터)."""
|
||||
text = _text(LAYER)
|
||||
parts = [
|
||||
re.search(r"^export const ownOwner[\s\S]*?;\n", text, re.M),
|
||||
@@ -114,8 +114,8 @@ def test_가져올_곳_탭():
|
||||
"const L = (key: string): string => key;\n"
|
||||
+ "\n".join(p.group(0).replace("export ", "") for p in parts if p)
|
||||
+ "\nconst cases = JSON.parse(process.argv[1]);"
|
||||
+ "\nconsole.log(JSON.stringify(cases.map(([u, c, practice]) => {"
|
||||
+ "\n const got = readableSources(u, c, { practice });"
|
||||
+ "\nconsole.log(JSON.stringify(cases.map(([u, c]) => {"
|
||||
+ "\n const got = readableSources(u, c);"
|
||||
+ "\n return [got.first, got.tabs.map((t) => t.id + (t.owner ? ':' + t.owner : ''))];"
|
||||
+ "\n})));"
|
||||
)
|
||||
@@ -125,8 +125,8 @@ def test_가져올_곳_탭():
|
||||
user = {"id": "7", "role": "USER", "company_id": 3}
|
||||
orphan = {"id": "8", "role": "USER", "company_id": None}
|
||||
cases = [
|
||||
[sys1, "3", True], [sys1, "1", False], [admin, "3", True], [master, "3", False],
|
||||
[user, "3", True], [orphan, None, False],
|
||||
[sys1, "3"], [sys1, "1"], [admin, "3"], [master, "3"],
|
||||
[user, "3"], [orphan, None],
|
||||
] # fmt: skip
|
||||
result = subprocess.run(
|
||||
["node", "--experimental-strip-types", "--no-warnings",
|
||||
@@ -135,9 +135,9 @@ def test_가져올_곳_탭():
|
||||
) # fmt: skip
|
||||
assert result.returncode == 0, result.stderr
|
||||
assert json.loads(result.stdout) == [
|
||||
["master", ["master", "company:3", "personal", "people", "practice"]], # 고객 회사 봄
|
||||
["master", ["master", "company:3", "personal", "people"]], # 고객 회사 봄
|
||||
["master", ["master", "company", "personal", "people"]], # 자기 회사 · 일위대가 아님
|
||||
["personal", ["master", "company", "personal", "people"]], # 회사 관리자 · 실무문서 없음
|
||||
["personal", ["master", "company", "personal", "people"]], # 회사 관리자
|
||||
["personal", ["master", "company", "personal", "people"]], # is_master = 회사 관리자
|
||||
["personal", ["master", "company", "personal"]], # 일반 = 다른 사람 없음
|
||||
["master", ["master"]], # 회사 없음
|
||||
|
||||
@@ -68,19 +68,6 @@ def test_pair_없으면_예전처럼_한_칸만(world): # noqa: F811
|
||||
assert "연결" not in _doc(personal, "area", "a")["시트"][0]
|
||||
|
||||
|
||||
def test_실무문서는_일위대가만_면적_그대로(world): # noqa: F811
|
||||
system = world["system"]
|
||||
_link_rv(system)
|
||||
area = _doc(system, "area", "rv")
|
||||
for kind in ("labor", "area"):
|
||||
url = URL.format("system", kind, "rv") + "?from=UA000008&from_layer=practice&pair=true"
|
||||
assert _as(world).post(url).status_code == 403 # 시스템 관리자만
|
||||
got = _as(world, "SYSTEM_ADMIN").post(url)
|
||||
assert got.status_code == 200 and set(got.json()) == {"labor"}
|
||||
assert len(_doc(system, "labor", "rv")["담은로직"]) == 14
|
||||
assert _doc(system, "area", "rv") == area
|
||||
|
||||
|
||||
def test_원본에_면적산출_없으면_일위대가만(world): # noqa: F811
|
||||
system, personal = world["system"], world["personal"]
|
||||
(system / "area" / "rv.json").unlink()
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
"""실무문서(PLAN 39-2) — 옛 마스터 일위대가 조합 표 통째 · 시스템 관리자만 통째 지우기.
|
||||
|
||||
못박는 것
|
||||
- `DELETE /api/m02/practice?판=` = 시스템 관리자만(그 밖 403) · 판 다르면 409 ·
|
||||
구조물이 쓰는 조합이 있으면 409(아무것도 안 지움) · 지우면 줄 0 · 마스터 틀 검사 그대로.
|
||||
- 화면(`Practice.ts`) = 마스터 층 · 시스템 관리자만 · 물음 두 번 · 상세 읽기만.
|
||||
정본(마스터)은 읽기만 · 쓰기는 임시 사본.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from M01_MasterData import M01_MasterData_Store as m01
|
||||
from M01_MasterData import M01_MasterData_Store_Combo as combo_store
|
||||
from M02_MasterTemplete import M02_MasterTemplete_Combo_Store as combos
|
||||
from resources.tester.test_m02_combo_layers import _as, _list, master_copy, world # noqa: F401
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
M02 = ROOT / "M02_MasterTemplete"
|
||||
|
||||
|
||||
def _drop(client, 판: str, want: int) -> dict:
|
||||
res = client.delete("/api/m02/practice", params={"판": 판})
|
||||
assert res.status_code == want, res.text
|
||||
return res.json()
|
||||
|
||||
|
||||
def test_통째_지우기_시스템_관리자만_판_쓰는_곳(world, master_copy, monkeypatch): # noqa: F811
|
||||
admin = _as(world, "SYSTEM_ADMIN")
|
||||
before = _list(admin, "system")
|
||||
assert before["줄"] # 사본에 옛 조합이 있음
|
||||
for role in ("USER", "ADMIN"):
|
||||
_drop(_as(world, role), before["판"], 403)
|
||||
admin = _as(world, "SYSTEM_ADMIN")
|
||||
_drop(admin, "옛판", 409)
|
||||
used = before["줄"][0]["키"]
|
||||
monkeypatch.setattr(combo_store, "combo_users", lambda k: ["항목 하나"] if k == used else [])
|
||||
got = _drop(admin, before["판"], 409)
|
||||
assert "항목 하나" in json.dumps(got, ensure_ascii=False)
|
||||
assert len(_list(admin, "system")["줄"]) == len(before["줄"]) # 아무것도 안 지움
|
||||
monkeypatch.setattr(combo_store, "combo_users", lambda k: [])
|
||||
got = _drop(admin, before["판"], 200)
|
||||
assert got["줄"] == [] and len(got["지운조합"]) == len(before["줄"])
|
||||
doc = json.loads((master_copy / combos.FILE).read_text(encoding="utf-8"))
|
||||
assert doc["줄"] == [] and m01.cm.check_form(combos.FILE, doc) == []
|
||||
again = _list(admin, "system")
|
||||
assert _drop(admin, again["판"], 200)["지운조합"] == [] # 빈 표 = 그대로
|
||||
|
||||
|
||||
def test_화면_시스템_관리자_마스터만_물음_두_번_읽기만():
|
||||
side = (M02 / "M02_MasterTemplete_UI_Side.ts").read_text("utf-8")
|
||||
assert "const practice = opt.isSystem" in side # 720-4 — 시스템 관리자면 늘
|
||||
order = side.split('className: "m02-side__lists ui-sidebar-panel",')[1].split("});")[0]
|
||||
assert order.index("drawing,") < order.index("practice ?") # 도면 양식 아래
|
||||
view = (M02 / "M02_MasterTemplete_Practice.ts").read_text("utf-8")
|
||||
assert view.count("showConfirmDialog(") == 2
|
||||
assert "dropPractice(" in view and "createInputField" not in view # 고칠 칸 없음
|
||||
@@ -155,7 +155,6 @@ def test_왼쪽_칸_차례_일위대가_구조물조합_구분선_불러오기_7
|
||||
"createDivider()",
|
||||
"hopyo.title",
|
||||
"drawing,",
|
||||
"practice ?",
|
||||
]
|
||||
at = [order.index(m) for m in marks]
|
||||
assert at == sorted(at)
|
||||
|
||||
@@ -109,12 +109,12 @@ def test_없는_종류_키_참거짓_아님은_422_낡은_판은_409(client) ->
|
||||
|
||||
def test_좌측_줄은_구조물_집계_양식_아래_한_줄_같은_한_곳에서_지움() -> None:
|
||||
side = (ROOT / "M02_MasterTemplete/M02_MasterTemplete_UI_Side.ts").read_text("utf-8")
|
||||
# 716-3 — 옵션 줄은 M01 로 옮겨 좌측 칸에서 뺌(차례 = 일위대가 → 구조물 조합 → 불러오기 → 도면 양식 → 실무문서)
|
||||
# 716-3 — 옵션 줄은 M01 로 옮겨 좌측 칸에서 뺌(차례 = 일위대가 → 구조물 조합 → 도면 양식)
|
||||
assert "options.title" not in side and "options.clear" not in side
|
||||
order = side.split('className: "m02-side__lists ui-sidebar-panel",')[1].split("});")[0]
|
||||
assert order.index("summary") < order.index("drawing,") < order.index("practice")
|
||||
# 실무문서를 열면 이 줄 고름 지움(자재 조합 · 「전체 항목」 없앰)
|
||||
assert "onOpen: () => (practice?.clear(), paint())" in side
|
||||
assert order.index("summary") < order.index("drawing,")
|
||||
# 실무문서 칸은 걷음(732-1)
|
||||
assert "practice" not in side
|
||||
assert "matset" not in side
|
||||
# 화면은 정의 한 벌로 읽고 씀(PLAN 48-2) · 보이기 체크는 정의 화면 옵션 줄
|
||||
api = (ROOT / "M02_MasterTemplete/StructOptions/M02_SO_Api.ts").read_text("utf-8")
|
||||
|
||||
@@ -194,7 +194,7 @@ def test_좌측_고름_표시는_열린_것_한_줄뿐():
|
||||
# 다른 판도 같은 한 곳 — 열면 양식 · 열 줄 고름 지움 · 양식을 열면 실무자료 판 고름 지움
|
||||
# (716-3 옵션 줄 뺌 — 좌측 판 = 실무자료)
|
||||
assert re.search(r"sel = null;\s*onOpened\?\.\(\);", _text(MAIN))
|
||||
assert "if (opt.getOpen()) practice?.clear();" in side.split("function paint(): void {")[1]
|
||||
assert "practice" not in side # 실무문서 칸은 걷음(732-1)
|
||||
# 편집 칸이 열 때마다 좌측 `paint`(일위대가 화면 해시 맞춤은 39-2 에서 지움)
|
||||
assert "() => side.paint()," in _text(M02 / "M02_MasterTemplete_UI_Page.ts")
|
||||
|
||||
|
||||
@@ -77,11 +77,6 @@ def test_실패_글과_다시_불러오기_M02_자리():
|
||||
main = _text(M02 / "M02_MasterTemplete_UI_Main.ts")
|
||||
assert "const stop = showLoading(host);" in main
|
||||
assert "loadingError(why, () => void open(next))" in main
|
||||
# 옛 조합 나무 · 상세(Labor.ts · Labor_Edit.ts)는 39 에 지움 — 그 자리는 실무문서(같은 꼴)
|
||||
practice = _text(M02 / "M02_MasterTemplete_Practice.ts")
|
||||
assert "const stop = showLoading(view);" in practice
|
||||
assert "loadingError(why(error), () => void drawList())" in practice
|
||||
assert "loadingError(why(error), () => void drawOne(key))" in practice
|
||||
cards = _text(M02 / "M02_MasterTemplete_ItemCards.ts")
|
||||
# 일위대가 카드는 뺌 — 몸(Labor_View)을 덮었다 지우면 빈 카드(40 D1)
|
||||
assert "cards.filter((c) => c !== laborCard).map((c) => c.shell.loading())" in cards
|
||||
|
||||
Reference in New Issue
Block a user