Files
Aislo/resources/tester/test_m02_df_input_logic.py
T

231 lines
7.9 KiB
Python

"""단가산출 흐름도 입력 판 — 다른 로직 입력 = 마스터 로직 고르기(순수 부분 · `M02_DF_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" / "DataFlow" / "M02_DF_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;
(async () => {
const jobs = JSON.parse(fs.readFileSync(0, "utf8"));
const out = [];
for (const j of jobs) {
if (j.name === "wouldCycle") {
const graph = j.args[2];
out.push(await m.wouldCycle(j.args[0], j.args[1], async (k) => graph[k] ?? null));
} else out.push(await m[j.name](...j.args));
}
process.stdout.write(JSON.stringify(out));
})();
"""
def call(name: str, *args):
if shutil.which("node") is None:
pytest.skip("node 가 없음")
done = subprocess.run(
["node", "-e", RUNNER, str(ROOT), str(SOURCE)],
input=json.dumps([{"name": name, "args": list(args)}], ensure_ascii=False),
capture_output=True,
text=True,
encoding="utf-8",
check=True,
)
return json.loads(done.stdout)[0]
def row(key, name, book, chapter, no="", unit="원/㎡", **more):
return {
"file": f"로직_{book}.json",
"구분": book,
"상세구분": chapter,
"키": key,
"원문번호": no,
"이름": name,
"결과단위": unit,
**more,
}
ITEMS = [
row("GF000219", "돌쌓기 메쌓기(인력)", "산림품셈", "13장 구조물", "13-4-1"),
row("GF000220", "돌쌓기 찰쌓기(인력)", "산림품셈", "13장 구조물", "13-4-2"),
row("GF000025", "떼붙임(재배잔디)", "산림품셈", "05장 녹화", "5-12"),
row("GC000051", "보행자 안전통로", "건설품셈", "01장 공통", "1-3-9", "원/m"),
row("DF-0001", "내 흄관 로직", "흐름도", "내가 만든 것", "", "원/m"),
]
def keys(items):
return [i["키"] for i in items]
def test_찾기는_띄어쓰기로_나눈_말을_모두_포함():
got = call("filterLogics", ITEMS, {"text": "돌쌓기 메쌓기"})
assert keys(got) == ["GF000219"]
assert keys(call("filterLogics", ITEMS, {"text": "돌쌓기"})) == ["GF000219", "GF000220"]
assert keys(call("filterLogics", ITEMS, {"text": "돌 쌓기메쌓기"})) == [
"GF000219"
] # 띄어쓰기는 가리지 않음
assert keys(call("filterLogics", ITEMS, {"text": "13-4"})) == [
"GF000219",
"GF000220",
] # 원문번호
assert keys(call("filterLogics", ITEMS, {"text": "gc000051"})) == [
"GC000051"
] # 번호 · 대소문자
assert call("filterLogics", ITEMS, {"text": ""}) == ITEMS
def test_원문_장으로_거름():
assert keys(call("filterLogics", ITEMS, {"text": "", "book": "건설품셈"})) == ["GC000051"]
got = call("filterLogics", ITEMS, {"text": "", "book": "산림품셈", "chapter": "05장 녹화"})
assert keys(got) == ["GF000025"]
assert call("filterLogics", ITEMS, {"text": "메쌓기", "book": "건설품셈"}) == []
def test_원문_장_목록은_나온_차례():
assert call("logicBooks", ITEMS) == ["산림품셈", "건설품셈", "흐름도"]
assert call("logicChapters", ITEMS, "산림품셈") == ["13장 구조물", "05장 녹화"]
assert call("logicChapters", ITEMS, "") == [
"13장 구조물",
"05장 녹화",
"01장 공통",
"내가 만든 것",
]
def box(id_, kind, name, **more):
return {
"id": id_,
"kind": kind,
"name": name,
"color": "#fff",
"unit": "-",
"x": 0,
"y": 0,
**more,
}
FLOW = {
"key": "GF000219",
"name": "돌쌓기 메쌓기(인력)",
"resultUnit": "원/㎡",
"userElements": [],
"links": [],
"boxes": [
box(
"b1",
"input",
"입력",
input={
"mode": "variable",
"elements": [],
"variables": [
{"name": "뒷길이", "type": "number", "unit": "㎝", "example": 30},
{
"name": "돌",
"type": "choice",
"unit": "",
"choices": ["깬돌", "견치돌"],
"example": "깬돌",
},
],
},
),
box("o1", "output", "노무비", unit="원/㎡", output={"cost": "노무비", "unit": "원/㎡"}),
],
}
def test_흐름_로직_고르기는_출력_상자와_설계_때_정할_것():
ref = call("masterRef", "GF000219", FLOW, False, "원/㎡")
assert ref["key"] == "GF000219"
assert ref["outputs"] == [{"name": "노무비", "unit": "원/㎡"}]
assert ref["designs"] == [{"name": "뒷길이", "unit": "㎝"}, {"name": "돌", "unit": "-"}]
assert ref["examples"] == {"뒷길이": 30, "돌": "깬돌"}
def test_옛_로직_변환은_노무비_재료비_경비_계_넷():
ref = call("masterRef", "GF000219", FLOW, True, "원/㎡")
assert [o["name"] for o in ref["outputs"]] == ["노무비", "재료비", "경비", "계"]
assert {o["unit"] for o in ref["outputs"]} == {"원/㎡"}
assert [d["name"] for d in ref["designs"]] == ["뒷길이", "돌"] # 받는 알약은 흐름 그대로
def test_이어지지_않은_다른_로직_받는_알약도_설계_때_정할_것():
inner = {
"mode": "logic",
"elements": [],
"variables": [],
"logic": {
"key": "GF000250",
"outputs": [{"name": "작업량", "unit": "㎥/시간"}],
"designs": [{"name": "지역", "unit": "-"}, {"name": "기계", "unit": "-"}],
"examples": {"지역": "서울"},
},
}
flow = {**FLOW, "boxes": [*FLOW["boxes"], box("b2", "input", "작업량", input=inner)]}
flow["links"] = [
{"id": "l1", "from": {"box": "b1", "name": "돌"}, "to": {"box": "b2", "name": "기계"}}
]
ref = call("logicRefOf", "X", flow)
names = [d["name"] for d in ref["designs"]]
assert "지역" in names and "기계" not in names # 선이 이어진 받는 알약은 뺌
assert ref["examples"]["지역"] == "서울"
def test_자기_자신과_돌고_도는_로직은_막음():
graph = {
"A": {
"boxes": [
box(
"b",
"input",
"x",
input={"mode": "logic", "elements": [], "variables": [], "logic": {"key": "B"}},
)
]
},
"B": {
"boxes": [
box(
"b",
"input",
"x",
input={
"mode": "logic",
"elements": [],
"variables": [],
"logic": {"key": "SELF"},
},
)
]
},
"C": {"boxes": []},
}
assert call("wouldCycle", "SELF", "SELF", graph) is True # 자기 자신
assert call("wouldCycle", "SELF", "A", graph) is True # A → B → SELF
assert call("wouldCycle", "SELF", "C", graph) is False
assert call("wouldCycle", None, "A", graph) is False # 새 로직은 아직 키가 없어 돌 수 없음
assert call("wouldCycle", "SELF", "없음", graph) is False # 못 읽으면 막지 않음