"""826-1 층 목록 요약 색인 — 답 = 다시 셈한 것 · 자국 같으면 항목 파일 안 엶 · 고침 · 지움 · 더함 · 참조 고침 · M01 값 바뀜 · 색인 깨짐 뒤에도 맞음 · 판 충돌(409) 그대로.""" from __future__ import annotations import json import shutil from pathlib import Path import pytest from M01_MasterData import M01_MasterData_Store as m01 from M02_MasterTemplete import M02_Labor_Ref as refs from M02_MasterTemplete import M02_Layer_Index as index from M02_MasterTemplete import M02_MasterTemplete_Store as store from M02_MasterTemplete import M02_Template_Layers as layers from resources.tester.test_m02_labor_ref import COMPANY, world # noqa: F401 — 같은 임시 층 PCT = "GC001109" # 원/㎡ · 인력 둘 + 덧줄 % WAGE = "LB000001" LIB = ("sangeun", "precombo", "labor") @pytest.fixture() def fresh(world, monkeypatch): # noqa: F811 """색인 기억 비움 — 시험끼리 안 섞임.""" monkeypatch.setattr(index, "_MEM", {}) monkeypatch.setattr(index, "_IDS_LOADED", []) monkeypatch.setattr(refs, "_IDS", {}) return world def _forget(): index._MEM.clear() index._IDS_LOADED.clear() refs._IDS.clear() def _sk(folder, name, extra=None): doc = {"이름": name, "단위": "㎡", "바탕": {"로직": PCT}, "더한줄": extra or []} with store.at(folder): return refs.create("sangeun", doc)["이름"] def _wage_line(qty=1): return {"종류": "인력", "요소": WAGE, "이름": "보통인부", "단위": "인", "수량": qty} def _old(folder): rows = layers.list_templates(folder) refs.summaries(folder, rows) return rows def _new(folder): rows = index.list_rows(folder) index.summaries(folder, rows) return rows def _same(folder): got = _new(folder) assert json.dumps(got, ensure_ascii=False) == json.dumps(_old(folder), ensure_ascii=False) return got @pytest.fixture() def reads(monkeypatch): """항목 파일(서고 · 일위대가) 읽기 수.""" seen: list[str] = [] real = Path.read_bytes def counting(self): if self.parent.name in LIB: seen.append(f"{self.parent.name}/{self.stem}") return real(self) monkeypatch.setattr(Path, "read_bytes", counting) return seen def _shelf(w): up = _sk(w["company"], "회사 단가산출") mine = w["personal"] a = _sk(mine, "가", [_wage_line()]) b = _sk(mine, "나", [{"참조": {**COMPANY, "키": up}, "수량": 2}]) with store.at(mine): sc = refs.create( "precombo", { "이름": "일위대가", "단위": "㎡", "담은로직": [{"참조": {**COMPANY, "키": up}, "수량": {"손": 3}}], }, )["이름"] return up, a, b, sc def test_same_and_no_reads_when_stamps_same(fresh, reads): up, a, b, sc = _shelf(fresh) mine = fresh["personal"] first = _same(mine) assert {r["이름"] for r in first} >= {a, b, sc} and first[0]["기준금액"] is not None reads.clear() assert _new(mine) == first and reads == [] # 메모리 기억 _forget() reads.clear() assert _new(mine) == first and reads == [] # 디스크 파생 저장본(서버 다시 뜸) # 쓰는곳수 — 회사 SK 를 개인 SK 「나」 가 씀(사전조합은 사본으로 펼쳐 담음 · 740-1) company = _same(fresh["company"]) assert company[0]["쓰는곳수"] == 1 def _unfreeze(folder, key): """옛 꼴(사본 뗌) — M01 을 지금 값으로 셈(사본 있으면 만든 때 값 그대로).""" doc = json.loads(layers.template_path(folder, "sangeun", key).read_bytes()) old = {k: v for k, v in doc.items() if k not in ("사본", "수식", "M01판", "바탕판")} layers.write_template(folder, "sangeun", key, old) def test_edit_delete_add_and_dep_change(fresh, reads): up, a, b, sc = _shelf(fresh) mine = fresh["personal"] gone = _sk(mine, "지울것") _same(mine) with store.at(mine): # 고침 · 지움 got = store.read("sangeun", a) store.write("sangeun", a, got["판"], {**got["문서"], "더한줄": [_wage_line(3)]}) store.delete("sangeun", gone) c = _sk(mine, "다") # 더함 reads.clear() rows = _new(mine) assert set(reads) == {f"sangeun/{a}", f"sangeun/{c}"} # 바뀐 항목만 다시 읽음 assert rows == _old(mine) titles = {r["이름"]: r.get("제목") for r in rows if r["종류"] in index.layers.LIBRARY_KINDS} assert gone == c and titles == { a: "가", b: "나", c: "다", sc: "일위대가", } # 지운 키를 새 것이 받음 reads.clear() was = {r["이름"]: r.get("기준금액") for r in _new(mine)} assert reads == [] with store.at(fresh["company"]): # 사전조합이 담은 위 층 SK 고침 → 그 사전조합만 다시 got = store.read("sangeun", up) store.write("sangeun", up, got["판"], {**got["문서"], "더한줄": [_wage_line(5)]}) reads.clear() now = {r["이름"]: r.get("기준금액") for r in _new(mine)} assert set(reads) == {f"precombo/{sc}", f"sangeun/{up}"} assert now[sc] != was[sc] and {k: v for k, v in now.items() if k != sc} == { k: v for k, v in was.items() if k != sc } assert _new(mine) == _old(mine) def test_m01_change_recounts_amount(fresh, tmp_path, monkeypatch): copy = tmp_path / "m01" copy.mkdir() for path in Path(m01.FOLDER).glob("*.json"): shutil.copyfile(path, copy / path.name) shutil.copytree(Path(m01.FOLDER) / "기계", copy / "기계") # 기계 = 폴더(826-2) monkeypatch.setattr(m01, "FOLDER", copy) mine = fresh["personal"] a = _sk(mine, "가", [_wage_line()]) _unfreeze(mine, a) one = next(r for r in _same(mine) if r["이름"] == a)["기준금액"] assert one is not None wages = json.loads((copy / "인력.json").read_text(encoding="utf-8")) for row in wages["줄"]: if row.get("키") == WAGE: row["값"] = float(row["값"]) * 2 (copy / "인력.json").write_text(json.dumps(wages, ensure_ascii=False), encoding="utf-8") two = next(r for r in _same(mine) if r["이름"] == a)["기준금액"] assert two != one def test_broken_index_rebuilds(fresh): _up, _a, _b, _sc = _shelf(fresh) mine = fresh["personal"] first = _same(mine) folder = index.derived_dir(layers.storage_root(), index.KIND) for path in folder.glob("*.json"): path.write_text("{깨짐", encoding="utf-8") _forget() assert _same(mine) == first for path in folder.glob("*.json"): path.write_text(json.dumps({"key": path.stem, "value": {"sangeun/x": 1}}), encoding="utf-8") _forget() assert _same(mine) == first shutil.rmtree(folder) _forget() assert _same(mine) == first def test_version_conflict_still_409(fresh): mine = fresh["personal"] a = _sk(mine, "가") listed = next(r for r in index.list_rows(mine) if r["이름"] == a)["판"] path = layers.template_path(mine, "sangeun", a) assert listed == layers.version_of(path) doc = json.loads(path.read_bytes()) layers.write_template( mine, "sangeun", a, {**doc, "단위": "m"}, version=listed, check_version=True ) with pytest.raises(layers.StaleTemplate): # 옛 판으로 또 저장 layers.write_template(mine, "sangeun", a, doc, version=listed, check_version=True) fresh_ver = next(r for r in index.list_rows(mine) if r["이름"] == a)["판"] assert fresh_ver == layers.version_of(path) != listed