Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013rJwJMVL4EE31y77TB11AP
192 lines
9.6 KiB
Python
192 lines
9.6 KiB
Python
"""M02 층 권한 한 벌(PLAN 27-1c · `M02_Layer_Access`) — 나무 · 항목 · 양식 · 남의 층 목록 같은 규칙.
|
|
|
|
① `grant` 표 — 역할(일반 · 회사 관리자 · 시스템 관리자) × 층(마스터 · 자기 회사 · 남의 회사 ·
|
|
자기 개인 · 같은 회사 남 · 다른 회사 남) = 읽기 · 수정 · 막힘.
|
|
② 임시 storage — 남의 층은 자리가 있을 때만(없으면 404 · 폴더 안 만듦) · 나무 · 양식 길 같은 답 ·
|
|
남의 층 목록(회사 = 시스템 관리자 · 개인 = 회사 관리자 자기 회사 · 본인 뺌 · 자리 있는 것만).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
import pytest
|
|
from fastapi import HTTPException
|
|
|
|
from config import config_system
|
|
from M02_MasterTemplete import M02_Layer_Access as access
|
|
from M02_MasterTemplete import M02_MasterTemplete_Router_Items as items_router
|
|
from M02_MasterTemplete import M02_MasterTemplete_Router_Layers as layers_router
|
|
from M02_MasterTemplete import M02_MasterTemplete_Store as store
|
|
from M02_MasterTemplete import M02_Template_Layers as layers
|
|
from resources.tester.test_m02_structure import write_sample_table
|
|
from resources.tester.test_m02_tree import _app
|
|
|
|
USER = {"user_id": 42, "company_id": 7, "role": "USER", "is_master": False}
|
|
ADMIN = {**USER, "role": "ADMIN"}
|
|
MASTER = {**USER, "is_master": True}
|
|
SYSADMIN = {**USER, "role": "SYSTEM_ADMIN"}
|
|
|
|
|
|
def _answer(session: dict[str, Any], layer: str, owner: str | None) -> str:
|
|
try:
|
|
return "수정" if access.grant(session, layer, owner).write else "읽기"
|
|
except HTTPException as error:
|
|
return str(error.status_code)
|
|
|
|
|
|
def test_권한_표():
|
|
cases = [
|
|
("system", None),
|
|
("company", None),
|
|
("company", "9"),
|
|
("personal", None),
|
|
("personal", "7/43"),
|
|
("personal", "9/90"),
|
|
]
|
|
got = {
|
|
name: [_answer(s, *c) for c in cases]
|
|
for name, s in (
|
|
("일반", USER),
|
|
("회사관리", ADMIN),
|
|
("마스터", MASTER),
|
|
("시스템", SYSADMIN),
|
|
)
|
|
}
|
|
assert got == {
|
|
"일반": ["읽기", "읽기", "403", "수정", "403", "403"],
|
|
"회사관리": ["읽기", "수정", "403", "수정", "수정", "403"], # 맡은 회사(39-6)
|
|
"마스터": ["읽기", "수정", "403", "수정", "수정", "403"],
|
|
"시스템": ["수정", "수정", "수정", "수정", "수정", "수정"], # 어느 회사나 맡음
|
|
}
|
|
# 자기 층 owner 를 적어도 같음 · 모양 틀림 400 · 회사 없음 409
|
|
assert access.grant(USER, "personal", "7/42").write is True
|
|
assert _answer(SYSADMIN, "personal", "9") == "400"
|
|
assert _answer({**USER, "company_id": None}, "company", None) == "409"
|
|
with pytest.raises(HTTPException) as caught:
|
|
access.require(USER, "company", None, write=True)
|
|
assert caught.value.status_code == 403
|
|
|
|
|
|
@pytest.fixture()
|
|
def world(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> dict[str, Any]:
|
|
system = tmp_path / "system"
|
|
system.mkdir()
|
|
write_sample_table(system)
|
|
monkeypatch.setattr(store, "FOLDER", system)
|
|
monkeypatch.setattr(layers, "SYSTEM_ROOT", system)
|
|
store.renumber()
|
|
storage = tmp_path / "storage"
|
|
storage.mkdir()
|
|
monkeypatch.setattr(config_system, "STORAGE_BASE_DIR", str(storage))
|
|
users = {42: 7, 43: 7, 44: 7, 90: 9}
|
|
|
|
async def company_users(company_id: int) -> list[dict[str, Any]]:
|
|
return [{"user_id": u, "name": f"u{u}"} for u, c in users.items() if c == company_id]
|
|
|
|
async def companies() -> list[dict[str, Any]]:
|
|
return [{"company_id": c, "name": f"c{c}"} for c in (7, 8, 9)]
|
|
|
|
monkeypatch.setattr(layers_router, "_company_users", company_users)
|
|
monkeypatch.setattr(layers_router, "_companies", companies)
|
|
notes: list[tuple[int, str, str | None]] = []
|
|
|
|
async def member(owner: str) -> bool:
|
|
company, _, user = owner.partition("/")
|
|
return users.get(int(user)) == int(company) if user else int(company) in (7, 8, 9)
|
|
|
|
async def note(session: dict[str, Any], got: Any) -> None:
|
|
notes.append((session["user_id"], got.layer, got.owner))
|
|
|
|
monkeypatch.setattr(items_router, "_member", member)
|
|
monkeypatch.setattr(items_router, "_note", note)
|
|
session: dict[str, Any] = dict(USER)
|
|
return {"client": _app(session), "session": session, "storage": storage, "notes": notes}
|
|
|
|
|
|
def test_남의_층은_자리가_있을_때만_읽기(world: dict[str, Any]):
|
|
client, session, storage = world["client"], world["session"], world["storage"]
|
|
session.update(ADMIN)
|
|
tree = "/api/m02/layers/personal/tree?owner=7/43"
|
|
table = "/api/m02/layers/personal/templates/table/구조물집계표?owner=7/43"
|
|
assert client.get(tree).status_code == 404 # 43 은 아직 아무것도 없음
|
|
assert not (storage / "7/43").exists() # 읽기로 남의 폴더를 만들지 않음
|
|
(storage / "7/43/templates").mkdir(parents=True) # 폴더만 있고 집계표 없음
|
|
assert client.get(tree).status_code == 404
|
|
assert client.get(table).status_code == 404
|
|
assert client.get("/api/m02/items?layer=personal&owner=7/43").status_code == 404
|
|
assert not any((storage / "7/43/templates").iterdir()) # 읽기로 남의 층에 뼈대를 안 씀
|
|
session.update(user_id=43)
|
|
assert client.get("/api/m02/layers/personal/tree").status_code == 200 # 43 첫 진입 = 뼈대
|
|
session.update(user_id=42)
|
|
assert client.get(tree).status_code == 200
|
|
assert client.get(table).status_code == 200 # 양식 길도 같은 규칙
|
|
body = {"op": "add-folder", "경로": [], "이름": "새", "판": client.get(tree).json()["판"]}
|
|
assert client.post(tree, json=body).status_code == 200 # 맡은 회사 = 고침(39-6)
|
|
assert world["notes"] == [(42, "personal", "7/43")] # 남의 층 쓰기 = 시스템 로그 한 줄
|
|
assert client.get("/api/m02/items?layer=personal&owner=7/43").status_code == 200
|
|
session.update(USER)
|
|
assert client.get(tree).status_code == 403
|
|
assert client.get(table).status_code == 403
|
|
assert (
|
|
client.post(tree, json={**body, "이름": "몰래"}).status_code == 403
|
|
) # 일반 = 남 개인 막힘
|
|
session.update(SYSADMIN)
|
|
assert client.get(tree).status_code == 200
|
|
body = {"op": "add-folder", "경로": [], "이름": "AS", "판": client.get(tree).json()["판"]}
|
|
assert client.post(tree, json=body).status_code == 200 # 시스템 관리자 = 어느 회사나
|
|
|
|
|
|
def test_남의_층_첫_쓰기는_소속_확인_뒤_뼈대(world: dict[str, Any]):
|
|
"""39-6 — 폴더 없는 남의 층에 쓰면 주인이 있을 때만 뼈대 · 없는 사람 · 남의 회사 사람은 403 ·
|
|
읽기는 여전히 뼈대를 안 만듦 · 일위대가 키는 그 층 머리(UC · UP)."""
|
|
client, session, storage = world["client"], world["session"], world["storage"]
|
|
session.update(ADMIN)
|
|
add = {"op": "add-folder", "경로": [], "이름": "새", "판": ""}
|
|
assert client.get("/api/m02/layers/personal/tree?owner=7/44").status_code == 404
|
|
assert not (storage / "7/44").exists()
|
|
assert client.post("/api/m02/layers/personal/tree?owner=7/99", json=add).status_code == 403
|
|
assert not (storage / "7/99").exists() # 없는 사람 폴더를 안 만듦
|
|
assert client.post("/api/m02/layers/personal/tree?owner=9/90", json=add).status_code == 403
|
|
first = client.post("/api/m02/layers/personal/tree?owner=7/44", json=add)
|
|
assert first.status_code == 409 # 뼈대는 섰고 판이 달라 409 — 다시 읽고 고침
|
|
assert (storage / "7/44/templates/table/구조물집계표.json").is_file()
|
|
add["판"] = client.get("/api/m02/layers/personal/tree?owner=7/44").json()["판"]
|
|
assert client.post("/api/m02/layers/personal/tree?owner=7/44", json=add).status_code == 200
|
|
session.update(SYSADMIN)
|
|
assert client.post("/api/m02/layers/company/tree?owner=5", json=add).status_code == 403
|
|
assert not (storage / "5").exists() # 없는 회사
|
|
add["판"] = ""
|
|
assert client.post("/api/m02/layers/company/tree?owner=9", json=add).status_code == 409
|
|
assert (storage / "9/templates/table/구조물집계표.json").is_file()
|
|
assert [n[2] for n in world["notes"]] == ["7/44"] # 성공한 쓰기만(39-1d) · 409 · 403 은 안 남김
|
|
|
|
|
|
def test_남의_층_목록(world: dict[str, Any]):
|
|
client, session, storage = world["client"], world["session"], world["storage"]
|
|
for owner in ("7/43", "9/90"): # 층 폴더가 있는 사람만 목록에
|
|
(storage / owner / "templates").mkdir(parents=True)
|
|
(storage / "9/templates").mkdir()
|
|
people = "/api/m02/layers/personal/owners"
|
|
assert client.get(people).status_code == 403 # 일반 사용자
|
|
assert client.get("/api/m02/layers/company/owners").status_code == 403
|
|
(storage / "7/43/templates/table").mkdir(parents=True)
|
|
(storage / "7/43/templates/table/구조물집계표.json").write_text("{}", encoding="utf-8")
|
|
session.update(ADMIN)
|
|
assert client.get(people).json() == [ # 39-6 — 회사 사람 전부 · 나 맨 위 · 빈 사람 표시
|
|
{"owner": "7/42", "name": "u42", "비어있음": True},
|
|
{"owner": "7/43", "name": "u43", "비어있음": False},
|
|
{"owner": "7/44", "name": "u44", "비어있음": True},
|
|
]
|
|
assert not (storage / "7/44").exists() # 목록이 폴더를 안 만듦
|
|
assert client.get(f"{people}?company=9").status_code == 403 # 남의 회사 사람
|
|
session.update(SYSADMIN)
|
|
assert client.get("/api/m02/layers/company/owners").json() == [
|
|
{"owner": "7", "name": "c7"}, # 자기 회사는 자리 없어도
|
|
{"owner": "9", "name": "c9"},
|
|
]
|
|
assert client.get(f"{people}?company=9").json() == [
|
|
{"owner": "9/90", "name": "u90", "비어있음": True}
|
|
]
|