Files
Aislo/resources/tester/test_m02_labor_values.py
T

195 lines
8.7 KiB
Python

"""일위대가 넣을 값(39-3) — 입력 목록 길 `GET /api/m02/logic-lib/inputs` · 값 셈(TS 모델).
못박는 것
- 입력 목록 = 키 차례 · 겹친 키 한 번 · 입력마다 자동값 · 없는 키는 「빠진로직」.
- 입력 목록 몸 한 벌(`M02_Labor_Inputs`) · 옛 선택 입력은 없앰(39-7b).
- 값 셈 = 칸 글 → 저장 값(빈 글 = 자동값 · 범위 = 수 · 고르기 = 원래 모양) · 범위 밖 ·
모르는 이름 버림(사라진 값 알림) · 줄 아래 작은 글 · 빈 값 수(자동값도 없는 입력만).
화면 TS 는 그 자리에서 트랜스파일해 node 로 돌림 · 정본은 읽기만.
"""
from __future__ import annotations
import json
import shutil
import subprocess
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 M02_MasterTemplete import M02_Labor_Inputs as labor_inputs
from M02_MasterTemplete import M02_MasterTemplete_Router_Combos as combos_router
ROOT = Path(__file__).resolve().parents[2]
A, B = "GF000222", "GF000221" # 찰쌓기 인력 · 장비
@pytest.fixture()
def client() -> TestClient:
app = FastAPI()
app.include_router(combos_router.router)
app.dependency_overrides[verify_session] = lambda: {"user_id": 42, "role": "USER"}
return TestClient(app)
def test_입력_목록은_키_차례_겹침_한_번_자동값_빠진_로직(client: TestClient) -> None:
res = client.get("/api/m02/logic-lib/inputs", params={"keys": f"{B},{A},{B},GF999999,"})
assert res.status_code == 200, res.text
got = res.json()
assert [x["로직"] for x in got["로직"]] == [B, A]
assert got["빠진로직"] == ["GF999999"]
one = got["로직"][1]
assert one["이름"] and one["결과단위"]
assert all("출처" in x for x in got["로직"]) # 47-4 원문 패널
assert one["입력"] and all("자동값" in i and "이름" in i for i in one["입력"])
# 입력 목록 몸 한 벌(M02_Labor_Inputs)
assert got["로직"] == labor_inputs.logic_inputs([B, A])["로직"]
def test_빈_키는_빈_목록(client: TestClient) -> None:
got = client.get("/api/m02/logic-lib/inputs").json()
assert got == {"로직": [], "빠진로직": []}
SCRIPT = r"""
const fs = require("fs");
const path = require("path");
const ts = require(path.join(process.argv[1], "config", "node_modules", "typescript"));
const file = path.join(process.argv[1], "M02_MasterTemplete", "M02_Labor_Values_Model.ts");
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, require);
const m = box.exports;
const height = { 이름: "높이", 단위: "m", 범위: [0.5, 5], 자동값: 1.2 };
const kind = { 이름: "종류", 고르기: ["막돌", "견치돌"] };
const size = { 이름: "규격", 고르기: [25, 40] };
const area = { 이름: "지역" };
const inputs = [height, kind, size, area];
const machine = {
이름: "기계", 고르기: ["0201-0060", "9999"], 고르기이름: { "0201-0060": "굴착기(무한궤도) 0.6㎥" },
};
console.log(JSON.stringify({
stored: [
m.storedOf(height, ""), m.storedOf(height, "2.5"), m.storedOf(kind, "막돌"),
m.storedOf(size, "40"), m.storedOf(area, "서울"),
],
outside: [
m.isOutside(height, "9"), m.isOutside(height, "0.5"), m.isOutside(height, ""),
m.isOutside(height, "abc"), m.isOutside(area, "9"),
],
known: m.keepKnown(inputs, { 높이: 2, 종류: "", 없앤것: 3 }),
line: m.valueLine(inputs, { 높이: 2, 규격: 40 }),
lineEmpty: m.valueLine(inputs, {}),
named: [
m.choicesOf(machine).map((c) => [c.값, c.이름]), m.valueHint(machine),
m.valueLine([machine], { 기계: "0201-0060" }).text, m.labelOf(machine, "9999"),
m.storedOf(machine, "0201-0060"),
],
}));
"""
def test_값_셈() -> None:
node = shutil.which("node")
if not node:
pytest.skip("node 없음")
out = subprocess.run(
[node, "-e", SCRIPT, str(ROOT)], capture_output=True, text=True, encoding="utf-8"
)
assert out.returncode == 0, out.stderr
got = json.loads(out.stdout)
assert got["stored"] == [None, 2.5, "막돌", 40, "서울"]
assert got["outside"] == [True, False, False, True, False]
assert got["known"] == {"kept": {"높이": 2}, "lost": ["없앤것"]}
assert got["line"] == {"text": "높이 2m · 규격 40", "empty": 2} # 종류 · 지역 = 자동값 없음
assert got["lineEmpty"] == {"text": "", "empty": 3} # 높이는 자동값이 있어 빈 값 아님
# 85-1 — 보이는 글 = 이름 규격 · 모르는 번호는 그대로 · 저장 값은 번호
assert got["named"] == [
[["0201-0060", "굴착기(무한궤도) 0.6㎥"], ["9999", "9999"]],
"굴착기(무한궤도) 0.6㎥ / 9999",
"기계 굴착기(무한궤도) 0.6㎥",
"9999",
"0201-0060",
]
def test_고르기_원문번호에_이름표(client: TestClient) -> None:
"""85-1 — 기계 · 집게 = 「이름 규격」 · 직종(입력 이름에 「직종」) = 직종 이름 · 고르기는 번호 그대로."""
got = client.get("/api/m02/logic-lib/inputs", params={"keys": "GC000260"}).json()
specs = {i["이름"]: i for i in got["로직"][0]["입력"]}
assert specs["기계"]["고르기이름"]["0201-0060"] == "굴착기(무한궤도) 0.6㎥"
assert specs["집게"]["고르기이름"]["7206-0020"] == "부착용 집게 0.2㎥"
assert "0201-0060" in specs["기계"]["고르기"]
assert "고르기이름" not in specs["뒷길이"] and "고르기이름" not in specs["지역"]
whole = labor_inputs.m01.loaded()[1]
job = {"이름": "조종원직종", "고르기": ["1048", "없는번호"]}
assert labor_inputs._labels(whole, job) == {"1048": "건설기계운전사"}
ROWS_SCRIPT = r"""
const fs = require("fs");
const path = require("path");
const root = process.argv[1];
const ts = require(path.join(root, "config", "node_modules", "typescript"));
const stub = new Proxy({}, {
get: (_t, k) => (k === "currentLanguageIndex" ? 0 : () => undefined),
});
function load(f) {
const js = ts.transpileModule(fs.readFileSync(f, "utf8"), {
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 },
}).outputText;
const box = { exports: {} };
const req = (id) => {
if (id.startsWith(".") && !id.includes("M01_MasterData")) {
const p = path.resolve(path.dirname(f), id);
return fs.existsSync(p + ".ts") ? load(p + ".ts") : stub;
}
return stub;
};
new Function("exports", "module", "require", js)(box.exports, box, req);
return box.exports;
}
const m = load(path.join(root, "M02_MasterTemplete", "M02_MasterTemplete_Labor_Rows.ts"));
const cells = new Map([["돌쌓기|㎡", "B6"]]);
console.log(JSON.stringify([
m.baseQtyText({ 배: 10, 분모: "㎡" }),
m.baseQtyText({ 배: 1, 분모: "㎥" }),
m.baseQtyText({ 배: 1, 분모: null }),
m.qtyShort({ 수량: 5, 수량출처: "손", 알약: "돌쌓기|㎡" }, "㎥", cells),
m.qtyShort({ 수량: 12.5, 수량출처: "칸", 알약: "돌쌓기|㎡" }, "㎡", cells),
m.qtyShort({ 수량: null, 수량출처: "없음", 알약: "돌쌓기|㎡" }, "㎡", cells),
m.qtyShort({ 수량: null, 수량출처: "없음", 알약: "없는|㎡" }, "㎡", cells),
m.qtyText({ 수량: 20, 수량출처: "칸", 알약: "돌쌓기|㎡" }, "㎡", cells),
m.qtyText({ 수량: 20, 수량출처: "칸", 알약: "돌쌓기|㎡" }, "㎡"),
m.qtyText({ 수량: 3, 수량출처: "손", 알약: "돌쌓기|㎡" }, "㎡", cells),
m.qtyText({ 수량: null, 수량출처: "없음", 알약: "돌쌓기|㎡" }, "㎡", cells),
m.qtyText({ 수량: null, 수량출처: "없음", 알약: "돌쌓기|㎡" }, "㎡"),
]));
"""
def test_수량_글은_이은_칸_주소를_보임() -> None:
"""`Labor_View.setQty(수량, 칸)` 의 칸 주소로 「수량 20 ㎡(B6)」 — 없으면 「면적산출 칸」."""
node = shutil.which("node")
if not node:
pytest.skip("node 없음")
out = subprocess.run(
[node, "-e", ROWS_SCRIPT, str(ROOT)], capture_output=True, text=True, encoding="utf-8"
)
assert out.returncode == 0, out.stderr
got = json.loads(out.stdout)
# 수량산출 열(40-1) — 위 결과단위 기준 수량 · 아래 지금 수량 짧은 글
assert got[:7] == ["10 ㎡", "1 ㎥", "", "손 5 ㎥", "12.5(B6)", "빈 칸(B6)", "—"]
assert got[7:] == [
"수량 20 ㎡(B6)", # 44-6 — 수량(칸 주소)
"수량 20 ㎡(면적산출 칸)",
"수량 손 입력 3 ㎡",
"수량 빈 칸(B6)", # 이은 칸이 비었음 = 수량 없음(39-8 ② · 서버 area_values 와 같음)
"",
]