"""일위대가 사전조합 나무 = 자유 폴더(PLAN 158-1 · `M02_Precombo_Folders`) — 임시 storage. 새 폴더(깊이 자유) · 문서 옮기기(파일은 제자리 · 자리만) · 폴더 이름 바꾸기 · 옮기기(아래 자리 따라감) · 지우기(빈 폴더만 · 문서 있으면 409 까닭) · 판 409 · 층(마스터 · 회사 · 개인)마다 따로 · 문서 수(양식 목록) 그대로 · 쓰기 권한 = 층 규칙(마스터 403). """ from pathlib import Path import pytest from fastapi import FastAPI from fastapi.testclient import TestClient from common_util.common_util_auth import verify_session from config import config_system from M02_MasterTemplete import M02_Labor_Router as labor_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_Precombo_Folders as pre from M02_MasterTemplete import M02_Template_Layers as layers from resources.tester.test_m02_structure import write_sample_table TREE = "/api/m02/layers/{}/precombo-tree" @pytest.fixture() def client(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> TestClient: system = tmp_path / "system" system.mkdir() write_sample_table(system) monkeypatch.setattr(store, "FOLDER", system) monkeypatch.setattr(layers, "SYSTEM_ROOT", system) (tmp_path / "storage").mkdir() monkeypatch.setattr(config_system, "STORAGE_BASE_DIR", str(tmp_path / "storage")) app = FastAPI() app.include_router(labor_router.router) app.include_router(layers_router.router) app.dependency_overrides[verify_session] = lambda: { "user_id": 42, "company_id": 7, "role": "USER", "is_master": False, } got = TestClient(app) for name in ("가", "나", "다"): # 개인 층 문서 셋 — SC-000001~3 body = {"문서": {"이름": name, "구분": "C 사면안정", "상세구분": "옹벽", "담은로직": []}} assert got.post("/api/m02/layers/personal/library/precombo", json=body).status_code == 200 return got class Tree: def __init__(self, client: TestClient, layer: str = "personal"): self.client, self.url = client, TREE.format(layer) self.now = client.get(self.url).json() def do(self, want: int = 200, **body) -> dict: res = self.client.post(self.url, json={"판": self.now["판"], **body}) assert res.status_code == want, res.text if want == 200: self.now = res.json() assert self.now == {**self.client.get(self.url).json(), "알림": self.now["알림"]} return res.json() def _docs(client: TestClient, layer: str = "personal") -> list[str]: rows = client.get(f"/api/m02/layers/{layer}/templates").json()["양식"] return [r["이름"] for r in rows if r["종류"] == "precombo"] def test_처음은_모두_뿌리_새_폴더_깊이_자유(client: TestClient): t = Tree(client) assert t.now == {"판": "", "폴더": [], "자리": {}} # 문서 셋 = 뿌리 t.do(op="add-folder", 경로=[], 이름="옹벽") t.do(op="add-folder", 경로=["옹벽"], 이름="찰쌓기") t.do(op="add-folder", 경로=["옹벽", "찰쌓기"], 이름="높이 3m") assert t.now["폴더"] == [["옹벽"], ["옹벽", "찰쌓기"], ["옹벽", "찰쌓기", "높이 3m"]] assert t.do(409, op="add-folder", 경로=[], 이름="옹벽")["detail"].startswith("이미 있는 폴더") assert t.do(404, op="add-folder", 경로=["없음"], 이름="x")["detail"].startswith("없는 폴더") assert t.do(422, op="add-folder", 경로=[], 이름=" ") # 빈 이름 def test_문서_옮기기는_자리만_파일과_문서_수_그대로(client: TestClient, tmp_path: Path): t = Tree(client) t.do(op="add-folder", 경로=[], 이름="옹벽") t.do(op="add-folder", 경로=["옹벽"], 이름="찰쌓기") t.do(op="move", 키="SC-000001", 새경로=["옹벽", "찰쌓기"]) t.do(op="move", 키="SC-000002", 새경로=["옹벽"]) assert t.now["자리"] == {"SC-000001": ["옹벽", "찰쌓기"], "SC-000002": ["옹벽"]} t.do(op="move", 키="SC-000002", 새경로=[]) # 뿌리로 = 자리 지움 assert t.now["자리"] == {"SC-000001": ["옹벽", "찰쌓기"]} assert t.do(404, op="move", 키="SC-000009", 새경로=[])["detail"].startswith("없는 사전조합") assert t.do(404, op="move", 키="SC-000001", 새경로=["없음"]) personal = layers.personal_dir(7, 42) assert sorted(p.name for p in (personal / "precombo").iterdir()) == [ "SC-000001.json", "SC-000002.json", "SC-000003.json", ] assert _docs(client) == ["SC-000001", "SC-000002", "SC-000003"] # 자리 파일은 목록에 안 섞임 assert (personal / pre.FILE).is_file() def test_폴더_이름_바꾸기_옮기기는_아래_자리가_따라감(client: TestClient): t = Tree(client) for parent, name in (([], "가"), (["가"], "나"), ([], "다")): t.do(op="add-folder", 경로=parent, 이름=name) t.do(op="move", 키="SC-000001", 새경로=["가", "나"]) t.do(op="rename", 경로=["가"], 이름="라") assert t.now["폴더"] == [["라"], ["라", "나"], ["다"]] assert t.now["자리"] == {"SC-000001": ["라", "나"]} assert t.do(409, op="rename", 경로=["라"], 이름="다") t.do(op="move", 경로=["라", "나"], 새경로=["다"]) assert t.now["자리"] == {"SC-000001": ["다", "나"]} and ["라", "나"] not in t.now["폴더"] assert t.do(422, op="move", 경로=["다"], 새경로=["다", "나"])["detail"].startswith("폴더를 제") t.do(op="add-folder", 경로=[], 이름="나") t.do(op="move", 경로=["다", "나"], 새경로=[]) # 같은 이름 = 「나(1)」 assert t.now["알림"]["바뀐이름"] == ["나(1)"] and t.now["자리"]["SC-000001"] == ["나(1)"] def test_지우기는_빈_폴더만_문서가_있으면_까닭(client: TestClient): t = Tree(client) t.do(op="add-folder", 경로=[], 이름="가") t.do(op="add-folder", 경로=["가"], 이름="나") t.do(op="add-folder", 경로=["가", "나"], 이름="다") t.do(op="move", 키="SC-000003", 새경로=["가", "나", "다"]) assert "사전조합 1개" in t.do(409, op="delete", 경로=["가"])["detail"] t.do(op="move", 키="SC-000003", 새경로=[]) t.do(op="delete", 경로=["가"]) # 빈 아래 폴더까지 assert t.now["폴더"] == [] and t.now["자리"] == {} assert _docs(client) == ["SC-000001", "SC-000002", "SC-000003"] def test_판이_다르면_409_고침_안_함(client: TestClient): t = Tree(client) t.do(op="add-folder", 경로=[], 이름="가") old = t.now["판"] t.do(op="add-folder", 경로=[], 이름="나") res = client.post(TREE.format("personal"), json={"판": old, "op": "delete", "경로": ["가"]}) assert res.status_code == 409 and ["가"] in client.get(TREE.format("personal")).json()["폴더"] def test_층마다_따로_마스터_쓰기는_층_규칙(client: TestClient): Tree(client).do(op="add-folder", 경로=[], 이름="내 폴더") company = Tree(client, "company") assert company.now["폴더"] == [] and Tree(client, "system").now["폴더"] == [] assert Tree(client, "system").do(403, op="add-folder", 경로=[], 이름="x") assert client.get(TREE.format("personal")).json()["폴더"] == [["내 폴더"]] def test_지운_문서_자리는_안_보임(): doc = {"폴더": [["가"]], "자리": {"SC-000001": ["가"], "SC-000002": ["가", "나"]}} got = pre.view_of(doc, "v", {"SC-000001"}) assert got == {"판": "v", "폴더": [["가"]], "자리": {"SC-000001": ["가"]}} out, _ = pre.apply(doc, {"op": "add-folder", "경로": [], "이름": "다"}, {"SC-000001"}) assert out["자리"] == {"SC-000001": ["가"]} # 고칠 때 걷힘