feat(733-3): 서고 중복 찾기 · 합치기 서버 등록 · 지문 모듈 연결 · 시험

- M02_Library_Same_Router — 733-1a 지문(Same 한 벌 · above) 으로 묶음 · 합칠 때 지문 다시 셈(다르면 409)
- main.py 에 라우터 등록(로그인만 · 층 쓰기 권한은 길 안에서)
- 새 시험 test_733_3_library_same_merge(합치기 참조 옮김 · 금액 같음 · 되돌림 · 지문 · 판 409 · 권한 밖 남김 · 위 층 키 · 일반 사용자 회사 층 403 · SC 수량만 다름)
- 새 시험 test_733_3_library_same_screen(부품만 · 날 색 없음 · 나무 메뉴 · 서버 길 짝)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-02 13:44:00 +09:00
co-authored by Claude Opus 5.5
parent 16c7b2c782
commit b9e6f77dab
4 changed files with 235 additions and 19 deletions
+7 -19
View File
@@ -62,14 +62,6 @@ def _ref(at: refs.Here, key: str) -> refs.Ref:
return {"층": at[0], "주인": at[1], "키": key}
def _uppers(at: refs.Here) -> list[refs.Here]:
"""이 층이 읽는 위 층 — 개인 → 그 회사 · 마스터 / 회사 → 마스터 / 마스터 → 없음."""
layer, owner = at
if layer == "personal":
return [("company", str(owner).split("/")[0]), ("system", None)]
return [("system", None)] if layer == "company" else []
def _docs(at: refs.Here, kind: str) -> list[tuple[str, dict[str, Any]]]:
folder = refs.folder_of(*at) / kind
out = []
@@ -83,12 +75,6 @@ def _docs(at: refs.Here, kind: str) -> list[tuple[str, dict[str, Any]]]:
return out
def _finger(at: refs.Here, kind: str, doc: dict[str, Any]) -> str | None:
"""문서 내용 지문(733-1 · 빈 문서 None) — 참조는 가리킨 문서 지문(층 넘어 사본도 같음)."""
with store.at(refs.folder_of(*at)):
return same.finger(kind, doc)
# ── 쓰는 곳 ───────────────────────────────────────────────────────────
def _user_index() -> dict[str, list[tuple[Path, str, str]]]:
"""모든 층(프로젝트 뺌) 훑기 한 번 — {ref_id: [(파일, 종류, 「어디 이름」)]}."""
@@ -177,9 +163,10 @@ def find(folder: Path, kind: str) -> dict[str, Any]:
mine = refs.where(folder)
index = _user_index()
groups: dict[str, list[tuple[refs.Here, str, dict[str, Any]]]] = {}
for at in [mine, *_uppers(mine)]:
marks = same.Same() # 한 요청 안 참조 지문 메모
for at in [mine, *same.above(mine)]:
for key, doc in _docs(at, kind):
if (mark := _finger(at, kind, doc)) is not None:
if (mark := marks.finger(kind, doc)) is not None:
groups.setdefault(mark, []).append((at, key, doc))
out = []
for mark, docs in groups.items():
@@ -204,7 +191,7 @@ def merge(session: Session, folder: Path, kind: str, body: MergeBody) -> dict[st
raw = body.남김 or {}
keep_at: refs.Here = (str(raw.get("층") or ""), raw.get("주인") or None)
keep = _ref(keep_at, str(raw.get("키") or ""))
if keep_at not in [mine, *_uppers(mine)]:
if keep_at not in [mine, *same.above(mine)]:
raise _bad(400, "남김은 이 층 또는 위 층 문서")
if refs.kind_of(keep["키"]) != kind or (keep_doc := refs.read_ref(keep)) is None:
raise _bad(404, f"없는 {refs.TITLE[kind]} 「{keep['키']}」")
@@ -218,8 +205,9 @@ def merge(session: Session, folder: Path, kind: str, body: MergeBody) -> dict[st
tree_doc, 판 = pre_folders._read(kind)
if 판 != body.판:
raise _bad(409, {"stale": [pre_folders.file_of(kind)], "판": 판})
mark = _finger(keep_at, kind, keep_doc)
if mark is None or any(_finger(mine, kind, have[k]) != mark for k in gone):
marks = same.Same()
mark = marks.finger(kind, keep_doc)
if mark is None or any(marks.finger(kind, have[k]) != mark for k in gone):
raise _bad(409, "내용이 그새 달라짐 — [중복 찾기]를 다시 누름")
rids = {refs.ref_id(_ref(mine, k)): k for k in gone}