Files
Aislo/resources/tester/test_est_standalone.py

194 lines
7.2 KiB
Python

import json
import subprocess
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
CALC = ROOT / "tools" / "est-test" / "calc.js"
def calculate(data):
script = 'const vm=require("vm"), fs=require("fs"); const ctx={}; vm.runInNewContext(fs.readFileSync(process.argv[1],"utf8")+";this.calc=EstCalc",ctx); console.log(JSON.stringify(ctx.calc.calculate(JSON.parse(process.argv[2]))));'
return json.loads(
subprocess.check_output(
["node", "-e", script, str(CALC), json.dumps(data)], text=True, encoding="utf-8"
)
)
def test_est_test_calculation_and_linked_price_change():
data = {
"sources": [{"id": "s", "kind": "재료비", "price": 10.129}],
"units": [{"id": "u", "lines": [{"sourceId": "s", "quantity": 2, "digits": 2}]}],
"estimate": [{"unitId": "u", "quantity": 3}],
"costs": [{"base": "직접공사비", "rate": 10}, {"base": "누계", "rate": 5}],
}
result = calculate(data)
assert result["units"]["u"]["lines"][0]["amount"] == 20.25
assert result["directTotal"] == 60
assert [row["amount"] for row in result["costs"]] == [6, 3]
assert result["total"] == 69
data["sources"][0]["price"] = 20
assert calculate(data)["total"] == 138
def test_est_test_unlinked_rows_contribute_zero():
data = {
"sources": [],
"units": [{"id": "u", "lines": [{"sourceId": "", "quantity": 2}]}],
"estimate": [{"unitId": "missing", "quantity": 4}],
"costs": [],
}
result = calculate(data)
assert result["units"]["u"]["total"] == result["total"] == 0
def test_source_selection_recursive_unit_and_percent():
data = {
"sources": [
{
"id": "s",
"kind": "실적",
"price": 900,
"prices": [0, 125, 155, 0, 0, 0],
"priceMode": "평균",
}
],
"units": [
{"id": "u1", "name": "기초", "lines": [{"sourceId": "s", "quantity": 2}]},
{
"id": "u2",
"name": "상위",
"lines": [
{"type": "일위대가", "sourceId": "", "unitId": "u1", "quantity": 1},
{
"type": "비율",
"sourceId": "",
"base": "실적",
"kind": "경비",
"quantity": 10,
},
],
},
],
"estimate": [{"unitId": "u2", "quantity": 2}],
"costs": [],
}
result = calculate(data)
assert result["units"]["u1"]["parts"]["실적"] == 280
assert result["units"]["u2"]["parts"]["경비"] == 28
assert result["directTotal"] == 616
def test_sample_percentage_row_and_cycle_error():
data = {
"sources": [{"id": "labor", "kind": "노무비", "price": 66841}],
"units": [
{
"id": "u",
"name": "호표",
"lines": [
{"sourceId": "labor", "quantity": 1},
{
"type": "비율",
"sourceId": "",
"base": "노무비",
"kind": "재료비",
"quantity": 2,
},
],
}
],
"estimate": [],
"costs": [],
}
result = calculate(data)
assert result["units"]["u"]["parts"]["재료비"] == 1336
data["units"][0]["lines"].append(
{"type": "일위대가", "sourceId": "", "unitId": "u", "quantity": 1}
)
assert result["errors"] == []
assert calculate(data)["errors"]
def test_progress_uses_approved_quantity_only_when_selected():
script = (
'const vm=require("vm"),fs=require("fs");const c={};'
'vm.runInNewContext(fs.readFileSync(process.argv[1],"utf8")+";this.calc=EstCalc",c);'
"console.log(JSON.stringify([c.calc.progress(100,2,3,1,false),"
"c.calc.progress(100,2,3,1,true)]));"
)
ordinary, approved = json.loads(
subprocess.check_output(["node", "-e", script, str(CALC)], text=True, encoding="utf-8")
)
assert ordinary["currentAmount"] == 300
assert ordinary["cumulativeAmount"] == 500
assert approved["cumulativeAmount"] == 300
def test_machine_components():
script = (
'const vm=require("vm"),fs=require("fs");const c={};'
'vm.runInNewContext(fs.readFileSync(process.argv[1],"utf8")+";this.calc=EstCalc",c);'
"console.log(JSON.stringify(c.calc.machine({purchase:1000000,rentalCoefficient:100,"
"fuelPrice:1000,fuelQuantity:2,suppliesRate:5,laborPrice:8000,laborFactor:0.125,laborHours:1})));"
)
result = json.loads(
subprocess.check_output(["node", "-e", script, str(CALC)], text=True, encoding="utf-8")
)
assert result == {"재료비": 2100, "노무비": 1000, "경비": 10, "실적": 0}
def test_formula_parser_rejects_invalid_expressions():
formula = ROOT / "tools" / "est-test" / "formula.js"
script = (
'const vm=require("vm"),fs=require("fs");const c={};'
'vm.runInNewContext(fs.readFileSync(process.argv[1],"utf8")+";this.f=EstFormula",c);'
'let failed=false;try{c.f.evaluate("1/0",{})}catch{failed=true};'
'console.log(JSON.stringify([c.f.evaluate("([금액]+2)*3",{"금액":8}),failed]));'
)
assert json.loads(
subprocess.check_output(["node", "-e", script, str(formula)], text=True, encoding="utf-8")
) == [30, True]
def test_escalation_comparison_branches():
workflows = ROOT / "tools" / "est-test" / "workflows.js"
script = (
'const vm=require("vm"),fs=require("fs");const c={};'
'vm.runInNewContext(fs.readFileSync(process.argv[1],"utf8")+";this.w=EstWorkflows",c);'
"console.log(JSON.stringify([c.w.escalation(100,80,120,2),"
"c.w.escalation(100,110,125,2),c.w.escalation(100,80,90,1),"
"c.w.excluded(3,5,4)]));"
)
a, b, lower, excluded = json.loads(
subprocess.check_output(["node", "-e", script, str(workflows)], text=True, encoding="utf-8")
)
assert (a["rate"], a["difference"], a["amount"]) == (20, 16, 192)
assert (b["difference"], b["amount"]) == (15, 250)
assert lower["difference"] == 0
assert excluded == 5
def test_resource_quantities_follow_nested_units():
workflows = ROOT / "tools" / "est-test" / "workflows.js"
script = (
'const vm=require("vm"),fs=require("fs");const c={};'
'vm.runInNewContext(fs.readFileSync(process.argv[1],"utf8")+";this.w=EstWorkflows",c);'
"console.log(JSON.stringify(c.w.resources(JSON.parse(process.argv[2]))));"
)
data = {
"sources": [{"id": "s", "name": "자재"}],
"units": [
{"id": "a", "lines": [{"sourceId": "s", "quantity": 2}]},
{"id": "b", "lines": [{"type": "일위대가", "unitId": "a", "quantity": 3}]},
],
"estimate": [{"unitId": "b", "quantity": 4}],
}
result = json.loads(
subprocess.check_output(
["node", "-e", script, str(workflows), json.dumps(data)], text=True, encoding="utf-8"
)
)
assert result["rows"][0]["quantity"] == 24
assert result["errors"] == []