"""EST 테스트 앱 연결 자료와 함께 복사·붙여넣기(원본 TFRMOTHERLOAD).""" import json import subprocess from pathlib import Path ROOT = Path(__file__).resolve().parents[2] FILES = [ "native-math.js", "native-formula.js", "native-machine.js", "native-rows.js", "native-text.js", "native-quantity.cjs", "native-wonga.js", "native-engine.js", "native-row-match.js", "native-escalation.js", "native-progress.js", "native-business.js", "native-review.js", "native-resources.js", "native-schedule.js", "native-model.js", "native-verify.js", "native-transfer.js", ] def run_js(code: str): runner = f""" const fs = require('fs'), vm = require('vm'); const ctx = vm.createContext({{ structuredClone, console }}); for (const f of {json.dumps(FILES)}) vm.runInContext(fs.readFileSync('tools/est-test/' + f, 'utf8'), ctx, {{ filename: f }}); ctx.source = JSON.parse(fs.readFileSync('tools/est-test/sample.json', 'utf8')); process.stdout.write(JSON.stringify(vm.runInContext({json.dumps(code)}, ctx))); """ out = subprocess.check_output(["node", "-e", runner], cwd=ROOT) return json.loads(out.decode("utf-8")) PICK = """ const calc = (p) => EstNative.calculate(EstModel.validate(p)); const src = calc(source); const linked = src.tables.EstMainDat.filter((r) => r.Code && !Number(r.Level)); const picked = [5, 103, 104].flatMap((k) => linked.filter((r) => Number(r.LinkIndex) === k).slice(0, 2)); const deps = EstTransfer.collect(src, picked); """ def test_copy_into_empty_project_keeps_prices_and_links(): result = run_js( "(() => {" + PICK + """ const target = EstNative.empty(); target.options = structuredClone(src.options); const merged = EstTransfer.merge(target, structuredClone(deps)); EstTransfer.paste(target, picked, 0); const done = calc(target); const pasted = done.tables.EstMainDat.slice(0, picked.length); return { kinds: [...new Set(picked.map((r) => Number(r.LinkIndex)))].sort((a, b) => a - b), groups: Object.keys(deps.groups).sort(), nested: (deps.flat.LaborcDat || []).length > 0, missing: done.calculation.errors.filter((e) => e.includes('연결 자료 없음') || e.includes('하위 자료 없음')), same: pasted.every((r, i) => Math.abs(Number(r.TC) - Number(picked[i].TC)) < 1), added: merged.added.length > 0, kept: merged.kept.length, criteria: merged.criteria, unique: new Set(done.tables.HeadMainDat.map((r) => r.PkID)).size === done.tables.HeadMainDat.length, }; })()""" ) assert result["kinds"] == [5, 103, 104] assert result["groups"] == ["CostMainDat", "HeadMainDat", "MachMainDat"] assert result["nested"] is True assert result["missing"] == [] assert result["same"] is True assert result["added"] is True assert result["kept"] == 0 assert result["criteria"] is True assert result["unique"] is True def test_same_code_in_target_is_kept_not_overwritten(): result = run_js( "(() => {" + PICK + """ const target = structuredClone(source); const labor = target.tables.LaborcDat[0]; labor.Cost1 = Number(labor.Cost1) * 2; labor.TC = Number(labor.TC) * 2; const before = target.tables.HeadMainDat.length; target.tables.MachGijunDat[0].TC = 12345; const merged = EstTransfer.merge(target, structuredClone(deps)); return { added: merged.added.length, kept: merged.kept.length > 0, criteria: merged.criteria === false && target.tables.MachGijunDat[0].TC === 12345, heads: target.tables.HeadMainDat.length === before, labor: target.tables.LaborcDat[0].TC === labor.TC }; })()""" ) assert result == {"added": 0, "kept": True, "criteria": True, "heads": True, "labor": True} def test_project_add_merges_rows_above_net_cost(): result = run_js( """(() => { const calc = (p) => EstNative.calculate(EstModel.validate(p)); const target = calc(structuredClone(source)), other = calc(structuredClone(source)); const net = (p) => p.tables.EstMainDat.find((r) => Number(r.LinkIndex) === 54); const before = Number(net(target).TC), count = target.tables.EstMainDat.length; const boundary = target.tables.EstMainDat.findIndex((r) => Number(r.LinkIndex) === 54); const out = EstTransfer.addProject(target, other); const done = calc(target); let empty = ''; try { EstTransfer.addProject(target, { tables: { EstMainDat: [{ LinkIndex: 54 }] } }); } catch (e) { empty = e.message; } return { rows: out.rows === boundary, added: out.added.length, grew: done.tables.EstMainDat.length - count === boundary, doubled: Math.abs(Number(net(done).TC) - 2 * before) < 2, errors: done.calculation.errors.length, empty: empty.slice(0, 8) }; })()""" ) assert result == { "rows": True, "added": 0, "grew": True, "doubled": True, "errors": 0, "empty": "추가할 공사에 ", }