Files
Aislo/resources/tester/test_m02_cf_inputpanel_build.py
T

255 lines
10 KiB
Python

"""단가산출 다이어그램 입력 상자 판 — 고르는 규칙(`M02_CF_InputPanel_Build.ts`).
화면 TS 를 그 자리에서 트랜스파일해 node 로 돌림 — 순수 함수만 부름. 견본 줄 · 상자는 이 시험 안에 둠.
"""
import json
import shutil
import subprocess
from pathlib import Path
import pytest
ROOT = Path(__file__).resolve().parents[2]
SOURCE = ROOT / "M02_MasterTemplete" / "CostFlow" / "M02_CF_InputPanel_Build.ts"
RUNNER = r"""
const fs = require("fs");
const path = require("path");
const [root, file] = process.argv.slice(1);
const ts = require(path.join(root, "config", "node_modules", "typescript"));
const js = ts.transpileModule(fs.readFileSync(file, "utf8"), {
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 },
}).outputText;
const box = { exports: {} };
new Function("exports", "module", "require", js)(box.exports, box, () => ({}));
const m = box.exports;
const jobs = JSON.parse(fs.readFileSync(0, "utf8"));
const out = jobs.map((j) => {
if (j.op === "fn") return m[j.name](...j.args);
let lg = j.lg;
for (const [fn, ...args] of j.steps || []) lg = m[fn](lg, ...args);
return lg;
});
process.stdout.write(JSON.stringify(out));
"""
def run(jobs: list[dict]) -> list:
if shutil.which("node") is None:
pytest.skip("node 가 없음")
done = subprocess.run(
["node", "-e", RUNNER, str(ROOT), str(SOURCE)],
input=json.dumps(jobs, ensure_ascii=False),
capture_output=True,
text=True,
encoding="utf-8",
check=True,
)
return json.loads(done.stdout)
def logic() -> dict:
plain = {"color": "#fff", "unit": "-", "x": 0, "y": 0}
return {
"key": None,
"name": "견본",
"resultUnit": "원/㎡",
"userElements": [],
"boxes": [
{
"id": "b1",
"kind": "input",
"name": "입력",
**plain,
"input": {"mode": "variable", "elements": [], "variables": []},
},
{"id": "b2", "kind": "function", "name": "함수", **plain},
],
"links": [],
}
def call(name: str, *args):
return run([{"op": "fn", "name": name, "args": list(args)}])[0]
def edit(steps: list, lg: dict | None = None) -> dict:
return run([{"op": "lg", "lg": lg or logic(), "steps": steps}])[0]
LABOR = {"키": "LB000033", "이름": "석공", "규격": "", "단위": "원/일", "값": 268908}
LABOR2 = {"키": "LB000002", "이름": "보통인부", "규격": "", "단위": "원/일", "값": 172068}
MAT = {
"키": "MT1",
"이름": "각형흄관",
"규격": "D=300mm",
"단위": "M",
"물가자료": 30000,
"물가정보": 29600,
}
MACHINE = {
"키": "EQ000001",
"이름": "불도저",
"규격": "7ton",
"단위": {"가격": "천원", "손료계수": "10^-7/시간"},
"값": {"가격": 76547, "손료계수": 1811},
}
RATE_ONE = {"키": "RT1", "이름": "산재보험료", "값": {"rate_percent": 3.56}}
RATE_BRACKET = {"키": "RT2", "이름": "고용보험료", "값": {"brackets": []}}
def test_요소는_이름과_규격으로_알약_이름이_됨():
labor = call("elementOf", "인력", LABOR)
assert labor == {"ref": "LB000033", "name": "석공", "spec": "", "unit": "원/일"}
got = call("elementOf", "재료", MAT)
assert got["name"] == "각형흄관 D=300mm" and got["unit"] == "M"
def test_기계는_값_칸을_골라_요소가_됨():
assert call("machineFields", MACHINE) == ["가격", "손료계수"]
got = call("elementOf", "기계", MACHINE, "손료계수")
assert got["ref"] == "EQ000001#손료계수" and got["unit"] == "10^-7/시간"
assert got["name"].endswith("· 손료계수")
assert call("queryOf", "기계", "EQ000001#손료계수") == {
"group": "기계",
"ref": "EQ000001",
"field": "손료계수",
}
def test_값_읽기_재료는_낮은_값_요율은_비율():
assert call("valueOf", "재료", MAT) == 29600
assert call("valueOf", "요율", RATE_ONE) == 3.56
assert call("valueOf", "요율", RATE_BRACKET) is None
assert call("elementOf", "요율", RATE_ONE)["unit"] == "%"
assert call("elementOf", "요율", RATE_BRACKET)["unit"] == "-"
assert call("valueOf", "기계", MACHINE, "가격") == 76547
def test_사용자_요소는_그_그룹의_ref_로_물음():
assert call("queryOf", "인력", "UE-0001") == {"group": "인력", "ref": "UE-0001"}
assert call("queryOf", "재료", "MT1") == {"group": "재료", "ref": "MT1"}
row = {"키": "UE-0001", "이름": "특수공", "규격": "", "단위": "원/일", "값": 300000}
assert call("elementOf", "인력", row)["unit"] == "원/일"
assert call("elementOf", "요율", {**row, "단위": "%"})["unit"] == "%"
def test_상자_단위는_같으면_그_단위_여럿이면_하이픈():
one = edit([["setKind", "b1", "인력"], ["addElement", "b1", call("elementOf", "인력", LABOR)]])
assert one["boxes"][0]["unit"] == "원/일"
two = edit([["addElement", "b1", call("elementOf", "인력", LABOR2)]], one)
assert two["boxes"][0]["unit"] == "원/일" and len(two["boxes"][0]["input"]["elements"]) == 2
oil = {"ref": "X", "name": "경유", "spec": "", "unit": "원/L"}
assert edit([["addElement", "b1", oil]], two)["boxes"][0]["unit"] == "-"
def test_같은_요소_이름은_한_번만():
el = call("elementOf", "인력", LABOR)
lg = edit(
[
["setKind", "b1", "인력"],
["addElement", "b1", el],
["addElement", "b1", el],
["addElement", "b1", {**el, "ref": "다른"}],
]
)
assert len(lg["boxes"][0]["input"]["elements"]) == 1
def test_요소를_빼면_그_알약에서_나간_선도_지움():
el1, el2 = call("elementOf", "인력", LABOR), call("elementOf", "인력", LABOR2)
base = edit([["setKind", "b1", "인력"], ["addElement", "b1", el1], ["addElement", "b1", el2]])
base["links"] = [
{"id": "l1", "from": {"box": "b1", "name": "석공"}, "to": {"box": "b2", "name": "a"}},
{"id": "l2", "from": {"box": "b1", "name": "보통인부"}, "to": {"box": "b2", "name": "b"}},
]
out = edit([["removeElement", "b1", "LB000033"]], base)
assert [x["id"] for x in out["links"]] == ["l2"]
assert [e["name"] for e in out["boxes"][0]["input"]["elements"]] == ["보통인부"]
def test_갈래를_바꾸면_고른_것을_비움_표는_요소와_함께_못_둠():
el = call("elementOf", "인력", LABOR)
lg = edit([["setKind", "b1", "인력"], ["addElement", "b1", el], ["setKind", "b1", "재료"]])
assert lg["boxes"][0]["input"]["group"] == "재료"
assert lg["boxes"][0]["input"]["elements"] == []
t = edit([["setKind", "b1", "소요량"], ["setTable", "b1", {"key": "QF000421"}, "메쌓기(인력)"]])
assert t["boxes"][0]["input"]["table"] == {"key": "QF000421"}
assert t["boxes"][0]["name"] == "메쌓기(인력)" and t["boxes"][0]["unit"] == "-"
row = edit([["setTable", "b1", {"key": "QF000421", "row": 3}]], t)
assert row["boxes"][0]["input"]["table"] == {"key": "QF000421", "row": 3}
assert call("pillNames", row["boxes"][0]) == ["표"]
def test_변수_넣기_고치기_삭제_알약_이름():
v = {"name": "면적", "type": "number", "unit": "㎡", "example": 100, "range": [1, 1000]}
lg = edit([["setKind", "b1", "변수"], ["putVariable", "b1", v]])
box = lg["boxes"][0]
assert box["input"]["mode"] == "variable" and box["unit"] == "㎡"
assert call("pillNames", box) == ["면적"]
renamed = edit([["putVariable", "b1", {**v, "name": "연장", "unit": "m"}, "면적"]], lg)
assert call("pillNames", renamed["boxes"][0]) == ["연장"] and renamed["boxes"][0]["unit"] == "m"
gone = edit([["removeVariable", "b1", "연장"]], renamed)
assert gone["boxes"][0]["input"]["variables"] == [] and gone["boxes"][0]["unit"] == "-"
def test_변수_검사는_쉬운_말():
ok = {"name": "면적", "type": "number", "unit": "㎡", "example": 100, "range": [1, 1000]}
assert call("checkVariable", ok, []) is None
assert call("checkVariable", {**ok, "name": " "}, []) == "이름을 적어 주세요"
assert call("checkVariable", ok, [ok]) == "같은 이름의 변수가 이미 있음"
assert call("checkVariable", {**ok, "range": [9, 1]}, []) == "범위는 아래가 위보다 작아야 함"
assert call("checkVariable", {**ok, "example": 5000}, []) == "예시값이 범위 밖"
many = {**ok, "type": "choice", "choices": ["가"]}
assert call("checkVariable", many, []) == "선택 목록은 둘 이상"
bad = {**ok, "type": "choice", "choices": ["가", "나"], "example": "다"}
assert call("checkVariable", bad, []) == "예시값이 선택 목록에 없음"
def test_환율_유가전력_알약_이름에_날짜가_안_들어감():
fx = {"키": "FX1", "이름": "USD", "규격": "2026-08-14", "단위": "원/USD", "값": 1414.9}
assert call("elementOf", "환율", fx)["name"] == "USD"
oil = {
"키": "MO1",
"이름": "휘발유",
"규격": "2026-08-14",
"상세구분": "서울",
"단위": "원/L",
"값": 1900,
}
assert call("elementOf", "유가전력", oil)["name"] == "휘발유 서울"
def test_단위가_안_맞게_저장된_상자는_열_때_맞춤():
el1, el2 = call("elementOf", "인력", LABOR), call("elementOf", "인력", LABOR2)
lg = logic()
lg["boxes"][0]["input"] = {
"mode": "group",
"group": "인력",
"elements": [el1, el2],
"variables": [],
}
assert lg["boxes"][0]["unit"] == "-"
fixed = run([{"op": "fn", "name": "reconcileUnit", "args": [lg, "b1"]}])[0]
assert fixed["boxes"][0]["unit"] == "원/일"
again = run([{"op": "fn", "name": "reconcileUnit", "args": [fixed, "b1"]}])[0]
assert again == fixed
oil = {"ref": "X", "name": "경유", "spec": "", "unit": "원/L"}
lg["boxes"][0]["input"]["elements"].append(oil)
lg["boxes"][0]["unit"] = "원/일"
assert (
run([{"op": "fn", "name": "reconcileUnit", "args": [lg, "b1"]}])[0]["boxes"][0]["unit"]
== "-"
)
one = {
"mode": "variable",
"elements": [],
"variables": [{"name": "면적", "type": "number", "unit": "㎡", "example": 1}],
}
lg["boxes"][0]["input"] = one
assert (
run([{"op": "fn", "name": "reconcileUnit", "args": [lg, "b1"]}])[0]["boxes"][0]["unit"]
== "㎡"
)