605 lines
38 KiB
Python
605 lines
38 KiB
Python
import json
|
|
import subprocess
|
|
from pathlib import Path
|
|
from test_est_rea_parity import run
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
|
|
|
|
def test_jdl_duplicate_sources_and_internal_codes_do_not_change_estimate_amounts():
|
|
project = run("math", "EstNative.empty()")
|
|
script = """const fs=require('fs'),j=require('./tools/est-test/jdl.cjs'),a=require('./tools/est-test/archive.cjs');
|
|
const empty=JSON.parse(fs.readFileSync(0,'utf8'));
|
|
const duplicate=structuredClone(empty);duplicate.name='duplicate';
|
|
duplicate.tables.StuffcDat=[{Code:'M1',Name:'first',TC:100},{Code:'M1',Name:'second',TC:200},{Code:'M1',Name:'third',TC:300}];
|
|
duplicate.tables.EstMainDat=[{Code:'M1',Name:'item',LinkIndex:5,GN:2,GU:200,GC:400,TC:400},{LinkIndex:54}];
|
|
const first=j.output(duplicate),loaded=j.read(first.bytes,empty);
|
|
const collision=structuredClone(empty);collision.name='collision';
|
|
collision.tables.EstiMateDat=[{Code:'LITERAL2',Name:'existing',GC:999,TC:999}];
|
|
collision.tables.EstMainDat=[{Code:'fixed',Name:'fixed',CalcIndex:1,GN:0,GC:80,TC:80},{LinkIndex:54}];
|
|
const second=j.output(collision),files=a.unpack(second.bytes),parts=j.parse('mainpart.txt',files.get('mainpart.txt'));
|
|
const sources=j.parse('w.txt',files.get('w.txt')),target=sources.find(r=>r.F_CODE===parts[0].F_CODE1);
|
|
process.stdout.write(JSON.stringify({duplicateTotal:loaded.tables.EstMainDat.at(-1).TC,
|
|
duplicateSources:j.parse('m.txt',a.unpack(first.bytes).get('m.txt')).length,
|
|
warning:first.warnings.some(x=>x.includes('중복')),fixedPrice:target.F_MPRICE,sourceCount:sources.length}));"""
|
|
result = json.loads(
|
|
subprocess.check_output(
|
|
["node", "-e", script], input=json.dumps(project).encode(), cwd=ROOT
|
|
)
|
|
)
|
|
assert result == {
|
|
"duplicateTotal": 400,
|
|
"duplicateSources": 3,
|
|
"warning": True,
|
|
"fixedPrice": 80,
|
|
"sourceCount": 2,
|
|
}
|
|
|
|
|
|
def test_jdl_duplicate_unit_codes_do_not_select_a_wrong_unit_group():
|
|
project = run("math", "EstNative.empty()")
|
|
script = """const fs=require('fs'),j=require('./tools/est-test/jdl.cjs'),a=require('./tools/est-test/archive.cjs');
|
|
const empty=JSON.parse(fs.readFileSync(0,'utf8')),p=structuredClone(empty);p.name='units';
|
|
p.tables.StuffcDat=[{Code:'M',Name:'material',TC:100}];
|
|
p.tables.HeadTitleDat=[{Code:'U',Name:'first',GC:200},{Code:'U',Name:'second',GC:300}];
|
|
p.tables.HeadMainDat=[{Index:1,Code:'U'},{Index:0,Code:'M',LinkIndex:5,GN:2,GU:100,GC:200}];
|
|
p.tables.EstMainDat=[{Code:'U',Name:'item',LinkIndex:103,GN:1,GU:300,GC:300,TC:300},{LinkIndex:54}];
|
|
const out=j.output(p),files=a.unpack(out.bytes),loaded=j.read(out.bytes,empty);
|
|
process.stdout.write(JSON.stringify({units:j.parse('unit.txt',files.get('unit.txt')).length,
|
|
total:loaded.tables.EstMainDat.at(-1).TC,warning:out.warnings.some(x=>x.includes('중복'))}));"""
|
|
result = json.loads(
|
|
subprocess.check_output(
|
|
["node", "-e", script], input=json.dumps(project).encode(), cwd=ROOT
|
|
)
|
|
)
|
|
assert result == {"units": 0, "total": 300, "warning": True}
|
|
|
|
|
|
def test_jdl_duplicate_unit_detail_groups_do_not_select_the_last_group():
|
|
project = run("math", "EstNative.empty()")
|
|
script = """const fs=require('fs'),j=require('./tools/est-test/jdl.cjs'),a=require('./tools/est-test/archive.cjs');
|
|
const p=JSON.parse(fs.readFileSync(0,'utf8')),empty=structuredClone(p);p.name='groups';
|
|
p.tables.StuffcDat=[{Code:'M',Name:'material',TC:100}];
|
|
p.tables.HeadTitleDat=[{Code:'U',Name:'unit',GC:200,TC:200}];
|
|
p.tables.HeadMainDat=[{Index:1,Code:'U'},{Index:0,Code:'M',LinkIndex:5,GN:2,GU:100,GC:200},
|
|
{Index:1,Code:'U'},{Index:0,Code:'M',LinkIndex:5,GN:1,GU:100,GC:100}];
|
|
p.tables.EstMainDat=[{Code:'U',Name:'item',LinkIndex:103,GN:1,GU:200,GC:200,TC:200},{LinkIndex:54}];
|
|
const out=j.output(p),files=a.unpack(out.bytes),q=j.read(out.bytes,empty);
|
|
process.stdout.write(JSON.stringify({units:j.parse('unit.txt',files.get('unit.txt')).length,
|
|
total:q.tables.EstMainDat.at(-1).TC}));"""
|
|
result = json.loads(
|
|
subprocess.check_output(
|
|
["node", "-e", script], input=json.dumps(project).encode(), cwd=ROOT
|
|
)
|
|
)
|
|
assert result == {"units": 0, "total": 200}
|
|
|
|
|
|
def test_jdl_basic_tables_use_native_separators_encoding_and_preserve_direct_money():
|
|
p = run(
|
|
"math",
|
|
"""(()=>{
|
|
const p=EstNative.empty();p.name='JDL test';p.options.SourceRefresh=false;
|
|
p.tables.StuffcDat=[{PkID:1,Code:'M',Name:'material',InputTC:100,ValueIndex:4}];
|
|
p.tables.EstMainDat=[{PkID:1,Code:'M',Name:'material',LinkIndex:5,GN:10,GU:100},
|
|
{PkID:2,Code:'FIX',Name:'fixed',GN:0,GC:80,TC:80,CalcIndex:1},
|
|
{PkID:3,Code:'SKIP',Name:'excluded',GN:1,GC:999,TC:999,CalcIndex:3},{PkID:4,LinkIndex:54}];
|
|
p.tables.Wonga=[{Gubun:'G',Name:'material',BaseField:'GC'},{Gubun:'T',Name:'total',VSusic:'G'}];
|
|
return EstNative.calculate(p);
|
|
})()""",
|
|
)
|
|
script = """const fs=require('fs'),j=require('./tools/est-test/jdl.cjs'),a=require('./tools/est-test/archive.cjs');
|
|
const p=JSON.parse(fs.readFileSync(0,'utf8')),empty=structuredClone(p);for(const name of Object.keys(empty.tables))empty.tables[name]=[];
|
|
const out=j.output(p),files=a.unpack(out.bytes),next=j.read(out.bytes,empty);
|
|
const raw=files.get('mainpart.txt');
|
|
process.stdout.write(JSON.stringify({next,files:[...files.keys()],tabs:raw.includes(9),terminator:raw.includes(5),warnings:out.warnings}));"""
|
|
result = json.loads(
|
|
subprocess.check_output(
|
|
["node", "-e", script],
|
|
input=json.dumps(p, ensure_ascii=False).encode("utf-8"),
|
|
cwd=ROOT,
|
|
).decode("utf-8")
|
|
)
|
|
assert result["tabs"] and result["terminator"]
|
|
assert {"Global.ini", "Main.txt", "mainpart.txt", "CostAccount.txt"}.issubset(result["files"])
|
|
assert result["next"]["tables"]["EstMainDat"][-1]["TC"] == 1080
|
|
assert result["next"]["tables"]["Wonga"][-1]["Cost"] == 1080
|
|
assert result["next"]["options"]["AutoCalc"] is False
|
|
assert result["warnings"]
|
|
|
|
|
|
def test_large_jdl_import_keeps_group_order_and_rejects_orphans_and_duplicates():
|
|
project = run("math", "EstNative.empty()")
|
|
script = """const fs=require('fs'),j=require('./tools/est-test/jdl.cjs'),a=require('./tools/est-test/archive.cjs'),schema=require('./tools/est-test/jdl-schema.json');
|
|
const empty=JSON.parse(fs.readFileSync(0,'utf8'));
|
|
const table=(name,rows)=>Buffer.from(rows.map(r=>schema[name].map(f=>r[f.name]??(f.kind===6?'0':'')).join('\\t')+'\\x05\\r\\n').join(''));
|
|
const n=3000,groups=Array.from({length:n},(_,i)=>({F_CODE:String(i),F_DES:'group '+i}));
|
|
const parts=groups.slice().reverse().flatMap(g=>[{F_CODE:g.F_CODE,F_SEQ:'1',F_CODE1:'M',F_DES:'first',F_QQTY:1,F_MPRICE:3},
|
|
{F_CODE:g.F_CODE,F_SEQ:'2',F_CODE1:'M',F_DES:'second',F_QQTY:2,F_MPRICE:3}]);
|
|
function archive(gs,ps){return a.pack(new Map([['Global.ini',Buffer.from('[BASIC]')],['Main.txt',Buffer.from('0\\tSample\\t02\\x05\\r\\n')],
|
|
['m.txt',table('m.txt',[{F_CODE:'M',F_DES:'material',F_MPRICE:3}])],['gongjong.txt',table('gongjong.txt',gs)],['mainpart.txt',table('mainpart.txt',ps)]]))}
|
|
const result=j.read(archive(groups,parts),structuredClone(empty));let orphan=false,duplicate=false;
|
|
try{j.read(archive(groups.slice(0,1),parts.slice(0,1)),structuredClone(empty))}catch{orphan=true}
|
|
try{j.read(archive([groups[0],groups[0]],[]),structuredClone(empty))}catch{duplicate=true}
|
|
const rows=result.tables.EstMainDat;
|
|
process.stdout.write(JSON.stringify({count:rows.length,first:rows.slice(0,3).map(r=>r.Name),total:rows.at(-1).TC,orphan,duplicate}));"""
|
|
result = json.loads(
|
|
subprocess.check_output(
|
|
["node", "-e", script], input=json.dumps(project).encode(), cwd=ROOT, timeout=30
|
|
)
|
|
)
|
|
assert result == {
|
|
"count": 9001,
|
|
"first": ["group 0", "first", "second"],
|
|
"total": 27000,
|
|
"orphan": True,
|
|
"duplicate": True,
|
|
}
|
|
|
|
|
|
def test_unchanged_import_preserves_original_jdl_and_edited_work_is_not_silently_discarded():
|
|
project = run("math", "EstNative.empty()")
|
|
script = """const fs=require('fs'),j=require('./tools/est-test/jdl.cjs'),a=require('./tools/est-test/archive.cjs'),s=require('./tools/est-test/server.cjs');
|
|
const empty=JSON.parse(fs.readFileSync(0,'utf8'));
|
|
const original=a.pack(new Map([['Global.ini',Buffer.from('[BASIC]\\r\\n')],
|
|
['Main.txt',Buffer.from('0\\tExample\\t02\\x05\\r\\n')],['extension.bin',Buffer.from([1,2,3,4])]]));
|
|
const p=j.markOriginal(s.calculate(j.read(original,empty)));p.id='0123456789abcdef01234567';
|
|
const unchanged=j.output(s.calculate(structuredClone(p)));
|
|
p.name='Changed';const changed=j.output(s.calculate(p)),files=a.unpack(changed.bytes);
|
|
process.stdout.write(JSON.stringify({unchanged:original.equals(unchanged.bytes),changed:!original.equals(changed.bytes),
|
|
name:files.get('Main.txt').toString().includes('Changed'),warning:changed.warnings.length>0}));"""
|
|
result = json.loads(
|
|
subprocess.check_output(
|
|
["node", "-e", script],
|
|
input=json.dumps(project, ensure_ascii=False).encode("utf-8"),
|
|
cwd=ROOT,
|
|
).decode("utf-8")
|
|
)
|
|
assert result == {"unchanged": True, "changed": True, "name": True, "warning": True}
|
|
|
|
|
|
def test_basic_jdl_unit_price_parts_import_with_recorded_costs_and_warn_for_special_parts():
|
|
project = run("math", "EstNative.empty()")
|
|
script = """const fs=require('fs'),j=require('./tools/est-test/jdl.cjs'),a=require('./tools/est-test/archive.cjs'),schema=require('./tools/est-test/jdl-schema.json'),s=require('./tools/est-test/server.cjs');
|
|
const empty=JSON.parse(fs.readFileSync(0,'utf8'));
|
|
const table=(name,rows)=>Buffer.from(rows.map(r=>schema[name].map(f=>r[f.name]??(f.kind===6?'0':'')).join('\\t')+'\\x05\\r\\n').join(''));
|
|
const common={F_CODE:'U1',F_CODE1:'M1',F_DES:'material',F_QQTY:2,F_MPRICE:100,F_MCOST:200,F_LOADBM:'T',F_SAVEBM:'T',F_SPROPER:'0',F_KWAN:'0',F_CLASS:'0'};
|
|
const files=new Map([['Global.ini',Buffer.from('[BASIC]')],['Main.txt',Buffer.from('0\\tSample\\t02\\x05\\r\\n')],
|
|
['m.txt',table('m.txt',[{F_CODE:'M1',F_DES:'material',F_MPRICE:100}])],
|
|
['unit.txt',table('unit.txt',[{F_CODE:'U1',F_DES:'unit',F_MPRICE:300}])],
|
|
['unitpart.txt',table('unitpart.txt',[{...common,F_SEQ:'1'},{...common,F_SEQ:'2',F_SPROPER:'1'}])]]);
|
|
const out=s.calculate(j.read(a.pack(files),empty));out.options.AutoCalc=true;out.options.SourceRefresh=true;
|
|
const recalculated=s.calculate(structuredClone(out));process.stdout.write(JSON.stringify({rows:out.tables.HeadMainDat,warnings:out.importWarnings,title:recalculated.tables.HeadTitleDat[0]}));"""
|
|
result = json.loads(
|
|
subprocess.check_output(
|
|
["node", "-e", script],
|
|
input=json.dumps(project, ensure_ascii=False).encode("utf-8"),
|
|
cwd=ROOT,
|
|
).decode("utf-8")
|
|
)
|
|
header, detail, remainder = result["rows"]
|
|
assert header["Index"] == 1 and header["TC"] == 300
|
|
assert detail["LinkIndex"] == 5 and detail["GN"] == 2 and detail["GC"] == 200
|
|
assert remainder["CalcIndex"] == 1 and remainder["GC"] == 100
|
|
assert result["title"]["TC"] == 300
|
|
assert any("특수 구성" in warning for warning in result["warnings"])
|
|
|
|
|
|
def test_basic_jdl_unit_price_parts_export_and_reimport():
|
|
project = run("math", "EstNative.empty()")
|
|
script = """const fs=require('fs'),j=require('./tools/est-test/jdl.cjs'),a=require('./tools/est-test/archive.cjs');
|
|
const p=JSON.parse(fs.readFileSync(0,'utf8'));p.name='unit';
|
|
p.tables.StuffcDat=[{Code:'M1',Name:'material',TC:100}];
|
|
p.tables.HeadTitleDat=[{Code:'U1',Name:'unit',GC:200,NC:0,JC:0,SC:0}];
|
|
p.tables.HeadMainDat=[{Index:1,Code:'U1',Name:'unit',GC:200,TC:200},
|
|
{Index:0,Code:'M1',Name:'material',LinkIndex:5,GN:2,GU:100,GC:200,TC:200}];
|
|
p.tables.EstMainDat=[{Code:'U1',Name:'unit',LinkIndex:103,GN:1,GU:200,GC:200,TC:200},{LinkIndex:54}];
|
|
const out=j.output(p),files=a.unpack(out.bytes),empty=structuredClone(p);for(const k of Object.keys(empty.tables))empty.tables[k]=[];
|
|
const q=j.read(out.bytes,empty);p.tables.HeadMainDat[1].CalcIndex=1;
|
|
const special=j.output(p),specialFiles=a.unpack(special.bytes);
|
|
process.stdout.write(JSON.stringify({unit:j.parse('unit.txt',files.get('unit.txt')),
|
|
parts:j.parse('unitpart.txt',files.get('unitpart.txt')),rows:q.tables.HeadMainDat,main:q.tables.EstMainDat,
|
|
specialUnits:j.parse('unit.txt',specialFiles.get('unit.txt')).length,specialWarnings:special.warnings.length}));"""
|
|
result = json.loads(
|
|
subprocess.check_output(
|
|
["node", "-e", script],
|
|
input=json.dumps(project, ensure_ascii=False).encode("utf-8"),
|
|
cwd=ROOT,
|
|
).decode("utf-8")
|
|
)
|
|
assert len(result["unit"]) == len(result["parts"]) == 1
|
|
assert result["parts"][0]["F_QQTY"] == 2 and result["parts"][0]["F_MCOST"] == 200
|
|
assert result["rows"][1]["LinkIndex"] == 5 and result["rows"][1]["GC"] == 200
|
|
assert result["main"][1]["LinkIndex"] == 103
|
|
assert result["specialUnits"] == 0 and result["specialWarnings"] > 0
|
|
|
|
|
|
def test_jdl_unit_price_with_machine_detail_survives_round_trip():
|
|
project = run("math", "EstNative.empty()")
|
|
script = """const fs=require('fs'),j=require('./tools/est-test/jdl.cjs'),a=require('./tools/est-test/archive.cjs');
|
|
const p=JSON.parse(fs.readFileSync(0,'utf8')),empty=structuredClone(p);p.name='machine unit';
|
|
p.tables.MachTitleDat=[{Code:'J',Name:'machine',GC:100,NC:50,TC:150}];
|
|
p.tables.MachMainDat=[{Index:1,Code:'J',Name:'machine',JdlSummary:true},
|
|
{Index:0,CalcIndex:1,JdlSummary:true,Name:'fuel',GC:100,NC:50,TC:150}];
|
|
p.tables.HeadTitleDat=[{Code:'U',Name:'unit',GC:100,NC:50,TC:150}];
|
|
p.tables.HeadMainDat=[{Index:1,Code:'U'},
|
|
{Index:0,Code:'J',Name:'machine',LinkIndex:105,GN:1,GU:100,NU:50,GC:100,NC:50,TC:150}];
|
|
p.tables.EstMainDat=[{Code:'U',Name:'unit',LinkIndex:103,GN:2,GU:100,NU:50,GC:200,NC:100,TC:300},{LinkIndex:54}];
|
|
const out=j.output(p),files=a.unpack(out.bytes),q=j.read(out.bytes,empty);
|
|
process.stdout.write(JSON.stringify({unit:j.parse('unitpart.txt',files.get('unitpart.txt')),
|
|
rows:q.tables.HeadMainDat,machine:q.tables.MachMainDat,total:q.tables.EstMainDat.at(-1).TC}));"""
|
|
result = json.loads(
|
|
subprocess.check_output(
|
|
["node", "-e", script], input=json.dumps(project).encode(), cwd=ROOT
|
|
)
|
|
)
|
|
assert result["unit"][0]["F_CODE1"].startswith("J")
|
|
assert result["rows"][1]["LinkIndex"] == 105
|
|
assert result["machine"][1]["Name"] == "fuel"
|
|
assert result["total"] == 300
|
|
|
|
|
|
def test_jdl_unit_price_preserves_other_source_costs_as_fixed_resources():
|
|
project = run("math", "EstNative.empty()")
|
|
script = """const fs=require('fs'),j=require('./tools/est-test/jdl.cjs'),a=require('./tools/est-test/archive.cjs');
|
|
const p=JSON.parse(fs.readFileSync(0,'utf8')),empty=structuredClone(p);p.name='sources';
|
|
for(const [table,code] of [['SilJukDat','S'],['EstiMateDat','E'],['CostTitleDat','D'],['MachTitleDat','J']])
|
|
p.tables[table]=[{Code:code,Name:code,GC:10,TC:10}];
|
|
p.tables.HeadTitleDat=[{Code:'U',Name:'unit',GC:40,TC:40}];
|
|
p.tables.HeadMainDat=[{Index:1,Code:'U'},...[['S',21],['E',101],['D',104],['J',105]].map(([Code,LinkIndex])=>
|
|
({Index:0,Code,Name:Code,LinkIndex,GN:1,GU:10,GC:10,TC:10}))];
|
|
p.tables.EstMainDat=[{Code:'U',Name:'unit',LinkIndex:103,GN:1,GU:40,GC:40,TC:40},{LinkIndex:54}];
|
|
const out=j.output(p),files=a.unpack(out.bytes),q=j.read(out.bytes,empty);
|
|
process.stdout.write(JSON.stringify({parts:j.parse('unitpart.txt',files.get('unitpart.txt')).length,
|
|
links:q.tables.HeadMainDat.slice(1).map(r=>r.LinkIndex),total:q.tables.EstMainDat.at(-1).TC}));"""
|
|
result = json.loads(
|
|
subprocess.check_output(
|
|
["node", "-e", script], input=json.dumps(project).encode(), cwd=ROOT
|
|
)
|
|
)
|
|
assert result == {"parts": 4, "links": [101, 101, 104, 101], "total": 40}
|
|
|
|
|
|
def test_jdl_cost_title_survives_direct_and_unit_price_links():
|
|
project = run("math", "EstNative.empty()")
|
|
script = """const fs=require('fs'),j=require('./tools/est-test/jdl.cjs'),a=require('./tools/est-test/archive.cjs');
|
|
const p=JSON.parse(fs.readFileSync(0,'utf8')),empty=structuredClone(p);p.name='cost';
|
|
p.tables.CostTitleDat=[{Code:'D',Name:'cost',GC:100,NC:20,JC:5,TC:125}];
|
|
p.tables.HeadTitleDat=[{Code:'U',Name:'unit',GC:100,NC:20,JC:5,TC:125}];
|
|
p.tables.HeadMainDat=[{Index:1,Code:'U'},
|
|
{Index:0,Code:'D',Name:'cost',LinkIndex:104,GN:1,GU:100,NU:20,JU:5,GC:100,NC:20,JC:5,TC:125}];
|
|
p.tables.EstMainDat=[{Code:'D',Name:'direct',LinkIndex:104,GN:2,GU:100,NU:20,JU:5,GC:200,NC:40,JC:10,TC:250},
|
|
{Code:'U',Name:'unit',LinkIndex:103,GN:3,GU:100,NU:20,JU:5,GC:300,NC:60,JC:15,TC:375},{LinkIndex:54}];
|
|
const out=j.output(p),files=a.unpack(out.bytes),q=j.read(out.bytes,empty);
|
|
process.stdout.write(JSON.stringify({danga:j.parse('danga.txt',files.get('danga.txt')).length,
|
|
costs:q.tables.CostMainDat,unit:q.tables.HeadMainDat[1].LinkIndex,
|
|
direct:q.tables.EstMainDat[1].LinkIndex,total:q.tables.EstMainDat.at(-1).TC,warnings:out.warnings}));"""
|
|
result = json.loads(
|
|
subprocess.check_output(
|
|
["node", "-e", script], input=json.dumps(project).encode(), cwd=ROOT
|
|
)
|
|
)
|
|
assert result["danga"] == 1
|
|
assert [row["TC"] for row in result["costs"][1:]] == [100, 20, 5]
|
|
assert result["unit"] == result["direct"] == 104
|
|
assert result["total"] == 625
|
|
assert result["warnings"]
|
|
|
|
|
|
def test_jdl_duplicate_cost_titles_fall_back_without_wrong_link():
|
|
project = run("math", "EstNative.empty()")
|
|
script = """const fs=require('fs'),j=require('./tools/est-test/jdl.cjs'),a=require('./tools/est-test/archive.cjs');
|
|
const p=JSON.parse(fs.readFileSync(0,'utf8')),empty=structuredClone(p);p.name='duplicates';
|
|
p.tables.CostTitleDat=[{Code:'D',Name:'one',GC:10},{Code:'D',Name:'two',GC:20}];
|
|
p.tables.EstMainDat=[{Code:'D',Name:'item',LinkIndex:104,GN:2,GU:20,GC:40,TC:40},{LinkIndex:54}];
|
|
const out=j.output(p),files=a.unpack(out.bytes),q=j.read(out.bytes,empty);
|
|
process.stdout.write(JSON.stringify({danga:j.parse('danga.txt',files.get('danga.txt')).length,
|
|
kind:q.tables.EstMainDat[1].LinkIndex,total:q.tables.EstMainDat.at(-1).TC,
|
|
warning:out.warnings.some(x=>x.includes('중복'))}));"""
|
|
result = json.loads(
|
|
subprocess.check_output(
|
|
["node", "-e", script], input=json.dumps(project).encode(), cwd=ROOT
|
|
)
|
|
)
|
|
assert result == {"danga": 0, "kind": 101, "total": 40, "warning": True}
|
|
|
|
|
|
def test_jdl_nested_units_preserve_detail_or_warn_on_forward_reference():
|
|
project = run("math", "EstNative.empty()")
|
|
script = """const fs=require('fs'),j=require('./tools/est-test/jdl.cjs'),a=require('./tools/est-test/archive.cjs');
|
|
const blank=JSON.parse(fs.readFileSync(0,'utf8'));
|
|
function convert(order,self=false,cycle=false){const p=structuredClone(blank);p.name='nested';
|
|
p.tables.StuffcDat=[{Code:'M',Name:'material',TC:10}];
|
|
const titles={A:{Code:'A',Name:'parent',GC:60,TC:60},B:{Code:'B',Name:'child',GC:20,TC:20}};
|
|
p.tables.HeadTitleDat=order.map(code=>titles[code]);
|
|
p.tables.HeadMainDat=[{Index:1,Code:'B'}, {Index:0,Code:cycle?'A':'M',Name:'material',LinkIndex:cycle?103:5,GN:2,GU:10,GC:20},
|
|
{Index:1,Code:'A'}, {Index:0,Code:self?'A':'B',Name:'child',LinkIndex:103,GN:3,GU:20,GC:60}];
|
|
p.tables.EstMainDat=[{Code:'A',Name:'parent',LinkIndex:103,GN:2,GU:60,GC:120,TC:120},{LinkIndex:54}];
|
|
const out=j.output(p),files=a.unpack(out.bytes),q=j.read(out.bytes,structuredClone(blank));
|
|
return {parts:j.parse('unitpart.txt',files.get('unitpart.txt')),total:q.tables.EstMainDat.at(-1).TC,
|
|
units:j.parse('unit.txt',files.get('unit.txt')),warnings:out.warnings};}
|
|
process.stdout.write(JSON.stringify({ordered:convert(['B','A']),forward:convert(['A','B']),
|
|
self:convert(['A','B'],true),cycle:convert(['A','B'],false,true)}));"""
|
|
result = json.loads(
|
|
subprocess.check_output(
|
|
["node", "-e", script], input=json.dumps(project).encode(), cwd=ROOT
|
|
)
|
|
)
|
|
assert result["ordered"]["total"] == result["forward"]["total"] == 120
|
|
assert result["ordered"]["parts"][-1]["F_CODE1"].startswith("U")
|
|
assert result["forward"]["parts"][-1]["F_CODE1"].startswith("U")
|
|
assert not any("하위 일위대가" in warning for warning in result["forward"]["warnings"])
|
|
assert len(result["self"]["units"]) == 1
|
|
assert result["cycle"]["total"] == 120
|
|
assert any("하위 일위대가" in warning for warning in result["cycle"]["warnings"])
|
|
|
|
|
|
def test_jdl_nested_unit_order_handles_deep_dependencies():
|
|
script = """const {orderUnits}=require('./tools/est-test/jdl-unit-order.cjs'),n=15000;
|
|
const titles=Array.from({length:n},(_,i)=>({Code:String(i)})),groups=new Map();
|
|
for(let i=0;i<n-1;i++)groups.set(String(i),[{LinkIndex:103,Code:String(i+1)}]);
|
|
const result=orderUnits(titles,groups);
|
|
process.stdout.write(JSON.stringify([result.length,result[0].Code,result.at(-1).Code]));"""
|
|
result = json.loads(subprocess.check_output(["node", "-e", script], cwd=ROOT, timeout=30))
|
|
assert result == [15000, "14999", "0"]
|
|
|
|
|
|
def test_jdl_machine_saved_amount_is_editable_and_survives_recalculation():
|
|
project = run("math", "EstNative.empty()")
|
|
script = """const fs=require('fs'),j=require('./tools/est-test/jdl.cjs'),a=require('./tools/est-test/archive.cjs'),schema=require('./tools/est-test/jdl-schema.json'),s=require('./tools/est-test/server.cjs');
|
|
const empty=JSON.parse(fs.readFileSync(0,'utf8'));
|
|
const table=(name,rows)=>Buffer.from(rows.map(r=>schema[name].map(f=>r[f.name]??(f.kind===6?'0':'')).join('\\t')+'\\x05\\r\\n').join(''));
|
|
const files=new Map([['Global.ini',Buffer.from('[BASIC]')],['Main.txt',Buffer.from('0\\tSample\\t02\\x05\\r\\n')],
|
|
['junit.txt',table('junit.txt',[{F_CODE:'J1',F_DES:'machine',F_MPRICE:100.25,F_LPRICE:50.12,F_EPRICE:25.03}])],
|
|
['jpart.txt',table('jpart.txt',[{F_CODE:'J1',F_SEQ:'1',F_CODE1:'M1'}])],
|
|
['gongjong.txt',table('gongjong.txt',[{F_CODE:'01',F_DES:'work'}])],
|
|
['mainpart.txt',table('mainpart.txt',[{F_CODE:'01',F_SEQ:'1',F_CODE1:'J1',F_DES:'machine',F_QQTY:2,F_MPRICE:100,F_LPRICE:50,F_EPRICE:25}])]]);
|
|
const original=a.pack(files),p=j.markOriginal(s.calculate(j.read(original,empty)));
|
|
const same=original.equals(j.output(s.calculate(structuredClone(p))).bytes);
|
|
p.tables.MachMainDat[1].GC=120.25;
|
|
p.options.AutoCalc=true;p.options.SourceRefresh=true;
|
|
const q=s.calculate(JSON.parse(JSON.stringify(p)));
|
|
process.stdout.write(JSON.stringify({same,rows:q.tables.MachMainDat,title:q.tables.MachTitleDat[0],
|
|
estimate:q.tables.EstMainDat[1],warnings:p.importWarnings,errors:q.calculation.errors}));"""
|
|
result = json.loads(
|
|
subprocess.check_output(
|
|
["node", "-e", script],
|
|
input=json.dumps(project, ensure_ascii=False).encode("utf-8"),
|
|
cwd=ROOT,
|
|
).decode("utf-8")
|
|
)
|
|
assert result["same"]
|
|
assert result["rows"][1]["CalcIndex"] == 1
|
|
assert abs(result["rows"][1]["TC"] - 195.4) < 1e-8
|
|
assert abs(result["title"]["TC"] - 195.4) < 1e-8
|
|
assert result["estimate"]["TC"] == 390
|
|
assert result["warnings"] and not result["errors"]
|
|
|
|
|
|
def test_jdl_machine_simple_detail_is_editable_and_mismatches_remain_summary():
|
|
project = run("math", "EstNative.empty()")
|
|
script = """const fs=require('fs'),j=require('./tools/est-test/jdl.cjs'),a=require('./tools/est-test/archive.cjs'),schema=require('./tools/est-test/jdl-schema.json'),s=require('./tools/est-test/server.cjs');
|
|
const empty=JSON.parse(fs.readFileSync(0,'utf8'));
|
|
const table=(name,rows)=>Buffer.from(rows.map(r=>schema[name].map(f=>r[f.name]??(f.kind===6?'0':'')).join('\\t')+'\\x05\\r\\n').join(''));
|
|
const files=new Map([['Global.ini',Buffer.from('[BASIC]')],['Main.txt',Buffer.from('0\\tSample\\t02\\x05\\r\\n')],
|
|
['junit.txt',table('junit.txt',[{F_CODE:'J1',F_DES:'machine',F_MPRICE:100,F_LPRICE:20,F_EPRICE:10},
|
|
{F_CODE:'J2',F_DES:'mismatch',F_MPRICE:50},{F_CODE:'J3',F_DES:'quantity',F_MPRICE:50}])],
|
|
['jpart.txt',table('jpart.txt',[{F_CODE:'J1',F_SEQ:'1',F_CODE1:'M1',F_DES:'fuel',F_QQTY:1,F_MPRICE:60,F_EPRICE:10},
|
|
{F_CODE:'J1',F_SEQ:'2',F_CODE1:'L1',F_DES:'operator',F_QQTY:1,F_MPRICE:40,F_LPRICE:20},
|
|
{F_CODE:'J2',F_SEQ:'1',F_DES:'incomplete',F_QQTY:1,F_MPRICE:10},
|
|
{F_CODE:'J3',F_SEQ:'1',F_DES:'unknown quantity semantics',F_QQTY:2,F_MPRICE:50}])]]);
|
|
const p=j.read(a.pack(files),empty);p.options.AutoCalc=true;p.options.SourceRefresh=true;
|
|
p.tables.MachMainDat[1].GC=70;const q=s.calculate(p);
|
|
process.stdout.write(JSON.stringify({rows:q.tables.MachMainDat,titles:q.tables.MachTitleDat,errors:q.calculation.errors,warnings:p.importWarnings}));"""
|
|
result = json.loads(
|
|
subprocess.check_output(
|
|
["node", "-e", script],
|
|
input=json.dumps(project, ensure_ascii=False).encode("utf-8"),
|
|
cwd=ROOT,
|
|
).decode("utf-8")
|
|
)
|
|
assert [row["Name"] for row in result["rows"][:3]] == ["machine", "fuel", "operator"]
|
|
assert result["titles"][0]["TC"] == 140
|
|
assert result["titles"][1]["TC"] == 50
|
|
assert result["titles"][2]["TC"] == 50
|
|
assert "상세 산식 미변환" in result["rows"][-1]["Name"]
|
|
assert result["warnings"] and not result["errors"]
|
|
|
|
|
|
def test_jdl_machine_quantity_detail_uses_consistent_amounts_only():
|
|
project = run("math", "EstNative.empty()")
|
|
script = """const fs=require('fs'),j=require('./tools/est-test/jdl.cjs'),a=require('./tools/est-test/archive.cjs'),schema=require('./tools/est-test/jdl-schema.json'),s=require('./tools/est-test/server.cjs');
|
|
const empty=JSON.parse(fs.readFileSync(0,'utf8')),blank=structuredClone(empty);
|
|
const table=(name,rows)=>Buffer.from(rows.map(r=>schema[name].map(f=>r[f.name]??(f.kind===6?'0':'')).join('\\t')+'\\x05\\r\\n').join(''));
|
|
const files=new Map([['Global.ini',Buffer.from('[BASIC]')],['Main.txt',Buffer.from('0\\tSample\\t02\\x05\\r\\n')],
|
|
['junit.txt',table('junit.txt',[{F_CODE:'J1',F_DES:'machine',F_MPRICE:100,F_LPRICE:50},
|
|
{F_CODE:'J2',F_DES:'ambiguous',F_MPRICE:50},{F_CODE:'J3',F_DES:'both match',F_MPRICE:150}])],
|
|
['jpart.txt',table('jpart.txt',[{F_CODE:'J1',F_SEQ:'1',F_DES:'part',F_QQTY:2,F_MPRICE:50,F_LPRICE:25},
|
|
{F_CODE:'J2',F_SEQ:'1',F_DES:'uncertain',F_QQTY:2,F_MPRICE:50},
|
|
{F_CODE:'J3',F_SEQ:'1',F_DES:'first',F_QQTY:2,F_MPRICE:50},
|
|
{F_CODE:'J3',F_SEQ:'2',F_DES:'second',F_QQTY:0.5,F_MPRICE:100}])],
|
|
['gongjong.txt',table('gongjong.txt',[{F_CODE:'01',F_DES:'work'}])],
|
|
['mainpart.txt',table('mainpart.txt',[{F_CODE:'01',F_SEQ:'1',F_CODE1:'J1',F_DES:'machine',F_QQTY:2,F_MPRICE:100,F_LPRICE:50}])]]);
|
|
const p=j.read(a.pack(files),empty),before=structuredClone(p.tables.MachMainDat);
|
|
p.options.AutoCalc=true;p.options.SourceRefresh=true;p.tables.MachMainDat[1].GC=110;const q=s.calculate(p);
|
|
const warnings=p.importWarnings.slice(),out=j.output(q),back=j.read(out.bytes,blank);
|
|
process.stdout.write(JSON.stringify({before,rows:q.tables.MachMainDat,titles:q.tables.MachTitleDat,
|
|
saved:back.tables.MachMainDat,total:back.tables.EstMainDat.at(-1).TC,warnings,errors:q.calculation.errors}));"""
|
|
result = json.loads(
|
|
subprocess.check_output(
|
|
["node", "-e", script], input=json.dumps(project).encode(), cwd=ROOT
|
|
)
|
|
)
|
|
assert result["before"][1]["GC"] == 100
|
|
assert result["before"][1]["NC"] == 50
|
|
assert result["before"][1]["JdlSourceQuantity"] == 2
|
|
assert result["titles"][0]["TC"] == 160
|
|
assert result["saved"][1]["Name"] == "part"
|
|
assert result["saved"][1]["GC"] == 110
|
|
assert result["saved"][1]["NC"] == 50
|
|
assert result["total"] == 320
|
|
assert "상세 산식 미변환" in result["rows"][-3]["Name"]
|
|
assert "상세 산식 미변환" in result["rows"][-1]["Name"]
|
|
assert any("수량×단가" in warning for warning in result["warnings"])
|
|
assert not result["errors"]
|
|
|
|
|
|
def test_jdl_machine_export_rejects_ambiguous_or_inconsistent_detail_groups():
|
|
project = run("math", "EstNative.empty()")
|
|
script = """const fs=require('fs'),j=require('./tools/est-test/jdl.cjs'),a=require('./tools/est-test/archive.cjs');
|
|
const p=JSON.parse(fs.readFileSync(0,'utf8'));p.name='machine';
|
|
p.tables.MachTitleDat=[{Code:'J1',Name:'duplicate',GC:10},{Code:'J2',Name:'mismatch',GC:20}];
|
|
p.tables.MachMainDat=[{Index:1,Code:'J1'},{Index:0,CalcIndex:1,JdlSummary:true,GC:10},
|
|
{Index:1,Code:'J1'},{Index:0,CalcIndex:1,JdlSummary:true,GC:10},
|
|
{Index:1,Code:'J2'},{Index:0,CalcIndex:1,JdlSummary:true,GC:5}];
|
|
const out=j.output(p),files=a.unpack(out.bytes);
|
|
process.stdout.write(JSON.stringify({units:j.parse('junit.txt',files.get('junit.txt')).length,
|
|
parts:j.parse('jpart.txt',files.get('jpart.txt')).length}));"""
|
|
result = json.loads(
|
|
subprocess.check_output(
|
|
["node", "-e", script], input=json.dumps(project).encode(), cwd=ROOT
|
|
)
|
|
)
|
|
assert result == {"units": 0, "parts": 0}
|
|
|
|
|
|
def test_jdl_cost_saved_amount_calculates_and_can_be_edited():
|
|
project = run("math", "EstNative.empty()")
|
|
script = """const fs=require('fs'),j=require('./tools/est-test/jdl.cjs'),a=require('./tools/est-test/archive.cjs'),schema=require('./tools/est-test/jdl-schema.json'),s=require('./tools/est-test/server.cjs');
|
|
const empty=JSON.parse(fs.readFileSync(0,'utf8'));
|
|
const table=(name,rows)=>Buffer.from(rows.map(r=>schema[name].map(f=>r[f.name]??(f.kind===6?'0':'')).join('\\t')+'\\x05\\r\\n').join(''));
|
|
const files=new Map([['Global.ini',Buffer.from('[BASIC]')],['Main.txt',Buffer.from('0\\tSample\\t02\\x05\\r\\n')],
|
|
['danga.txt',table('danga.txt',[{F_CODE:'D1',F_DES:'cost',F_MPRICE:100.25,F_LPRICE:50.12,F_EPRICE:25.03,F_MEMO:'source'},{F_CODE:'D2',F_EPRICE:0.000000000001}])],
|
|
['gongjong.txt',table('gongjong.txt',[{F_CODE:'01',F_DES:'work'}])],
|
|
['mainpart.txt',table('mainpart.txt',[{F_CODE:'01',F_SEQ:'1',F_CODE1:'D1',F_DES:'cost',F_QQTY:2,F_MPRICE:100,F_LPRICE:50,F_EPRICE:25}])]]);
|
|
const p=j.read(a.pack(files),empty);p.tables.CostMainDat[1].LineData='120.25 = \\x12100.25\\x12';
|
|
p.options.AutoCalc=true;p.options.SourceRefresh=true;const q=s.calculate(JSON.parse(JSON.stringify(p)));
|
|
process.stdout.write(JSON.stringify({title:q.tables.CostTitleDat[0],tiny:q.tables.CostTitleDat[1],estimate:q.tables.EstMainDat[1],errors:q.calculation.errors,warnings:p.importWarnings}));"""
|
|
result = json.loads(
|
|
subprocess.check_output(
|
|
["node", "-e", script],
|
|
input=json.dumps(project, ensure_ascii=False).encode("utf-8"),
|
|
cwd=ROOT,
|
|
).decode("utf-8")
|
|
)
|
|
assert abs(result["title"]["TC"] - 195.4) < 1e-8
|
|
assert result["tiny"]["JC"] == 0.000000000001
|
|
assert result["estimate"]["TC"] == 390
|
|
assert result["warnings"] and not result["errors"]
|
|
|
|
|
|
def test_jdl_complete_unit_preserves_difference_from_saved_title():
|
|
project = run("math", "EstNative.empty()")
|
|
script = """const fs=require('fs'),j=require('./tools/est-test/jdl.cjs'),a=require('./tools/est-test/archive.cjs'),schema=require('./tools/est-test/jdl-schema.json'),s=require('./tools/est-test/server.cjs');
|
|
const empty=JSON.parse(fs.readFileSync(0,'utf8'));
|
|
const table=(name,rows)=>Buffer.from(rows.map(r=>schema[name].map(f=>r[f.name]??(f.kind===6?'0':'')).join('\\t')+'\\x05\\r\\n').join(''));
|
|
const files=new Map([['Global.ini',Buffer.from('[BASIC]')],['Main.txt',Buffer.from('0\\tSample\\t02\\x05\\r\\n')],
|
|
['m.txt',table('m.txt',[{F_CODE:'M1',F_MPRICE:100}])],
|
|
['unit.txt',table('unit.txt',[{F_CODE:'U1',F_MPRICE:205.25},{F_CODE:'U2',F_LPRICE:30.12}])],
|
|
['unitpart.txt',table('unitpart.txt',[{F_CODE:'U1',F_CODE1:'M1',F_QQTY:2,F_MPRICE:100,F_MCOST:200}])]]);
|
|
const p=j.read(a.pack(files),empty);p.options.AutoCalc=true;p.options.SourceRefresh=true;
|
|
const q=s.calculate(p);process.stdout.write(JSON.stringify({rows:q.tables.HeadMainDat,titles:q.tables.HeadTitleDat}));"""
|
|
result = json.loads(
|
|
subprocess.check_output(
|
|
["node", "-e", script],
|
|
input=json.dumps(project, ensure_ascii=False).encode("utf-8"),
|
|
cwd=ROOT,
|
|
).decode("utf-8")
|
|
)
|
|
assert result["rows"][2]["GC"] == 5.25
|
|
assert [row["TC"] for row in result["titles"]] == [205.25, 30.12]
|
|
|
|
|
|
def test_jdl_fixed_unit_detail_and_saved_difference_survive_edit_and_export():
|
|
project = run("math", "EstNative.empty()")
|
|
script = """const fs=require('fs'),j=require('./tools/est-test/jdl.cjs'),a=require('./tools/est-test/archive.cjs'),schema=require('./tools/est-test/jdl-schema.json'),s=require('./tools/est-test/server.cjs');
|
|
const empty=JSON.parse(fs.readFileSync(0,'utf8')),blank=structuredClone(empty);
|
|
const table=(name,rows)=>Buffer.from(rows.map(r=>schema[name].map(f=>r[f.name]??(f.kind===6?'0':'')).join('\\t')+'\\x05\\r\\n').join(''));
|
|
const files=new Map([['Global.ini',Buffer.from('[BASIC]')],['Main.txt',Buffer.from('0\\tSample\\t02\\x05\\r\\n')],
|
|
['m.txt',table('m.txt',[{F_CODE:'M1',F_MPRICE:100}])],
|
|
['unit.txt',table('unit.txt',[{F_CODE:'U1',F_DES:'unit',F_MPRICE:205.25}])],
|
|
['unitpart.txt',table('unitpart.txt',[{F_CODE:'U1',F_SEQ:'1',F_CODE1:'M1',F_DES:'material',F_QQTY:2,F_MPRICE:100,F_MCOST:200}])],
|
|
['gongjong.txt',table('gongjong.txt',[{F_CODE:'01',F_DES:'work'}])],
|
|
['mainpart.txt',table('mainpart.txt',[{F_CODE:'01',F_SEQ:'1',F_CODE1:'U1',F_DES:'unit',F_QQTY:1,F_MPRICE:205.25}])]]);
|
|
const p=j.read(a.pack(files),empty);p.options.AutoCalc=true;p.options.SourceRefresh=true;
|
|
p.tables.HeadMainDat[2].GC=7;const q=s.calculate(p),out=j.output(q),entries=a.unpack(out.bytes),back=j.read(out.bytes,blank);
|
|
process.stdout.write(JSON.stringify({parts:j.parse('unitpart.txt',entries.get('unitpart.txt')),
|
|
rows:back.tables.HeadMainDat,total:back.tables.EstMainDat.at(-1).TC,errors:q.calculation.errors}));"""
|
|
result = json.loads(
|
|
subprocess.check_output(
|
|
["node", "-e", script], input=json.dumps(project).encode(), cwd=ROOT
|
|
)
|
|
)
|
|
assert len(result["parts"]) == 2
|
|
assert [row["GC"] for row in result["rows"][1:3]] == [200, 7]
|
|
assert result["total"] == 207
|
|
assert not result["errors"]
|
|
|
|
|
|
def test_jdl_unit_inconsistent_detail_keeps_recorded_amount_without_double_counting():
|
|
project = run("math", "EstNative.empty()")
|
|
script = """const fs=require('fs'),j=require('./tools/est-test/jdl.cjs'),a=require('./tools/est-test/archive.cjs'),schema=require('./tools/est-test/jdl-schema.json'),s=require('./tools/est-test/server.cjs');
|
|
const empty=JSON.parse(fs.readFileSync(0,'utf8'));
|
|
const table=(name,rows)=>Buffer.from(rows.map(r=>schema[name].map(f=>r[f.name]??(f.kind===6?'0':'')).join('\\t')+'\\x05\\r\\n').join(''));
|
|
const files=new Map([['Global.ini',Buffer.from('[BASIC]')],['Main.txt',Buffer.from('0\\tSample\\t02\\x05\\r\\n')],
|
|
['m.txt',table('m.txt',[{F_CODE:'M1',F_MPRICE:100}])],
|
|
['unit.txt',table('unit.txt',[{F_CODE:'U1',F_MPRICE:200}])],
|
|
['unitpart.txt',table('unitpart.txt',[{F_CODE:'U1',F_CODE1:'M1',F_QQTY:2,F_MPRICE:100,F_MCOST:0}])]]);
|
|
const p=j.read(a.pack(files),empty);p.options.AutoCalc=true;p.options.SourceRefresh=true;
|
|
const q=s.calculate(p);process.stdout.write(JSON.stringify({rows:q.tables.HeadMainDat,title:q.tables.HeadTitleDat[0],warnings:p.importWarnings}));"""
|
|
result = json.loads(
|
|
subprocess.check_output(
|
|
["node", "-e", script],
|
|
input=json.dumps(project, ensure_ascii=False).encode("utf-8"),
|
|
cwd=ROOT,
|
|
).decode("utf-8")
|
|
)
|
|
assert result["rows"][1]["CalcIndex"] == 1
|
|
assert result["rows"][2]["GC"] == 200
|
|
assert result["title"]["TC"] == 200
|
|
assert any("고정" in warning for warning in result["warnings"])
|
|
|
|
|
|
def test_edge_jdl_machine_amount_edit():
|
|
from playwright.sync_api import sync_playwright
|
|
import pytest
|
|
|
|
script = """const a=require('./tools/est-test/archive.cjs'),schema=require('./tools/est-test/jdl-schema.json');
|
|
const table=(name,rows)=>Buffer.from(rows.map(r=>schema[name].map(f=>r[f.name]??(f.kind===6?'0':'')).join('\\t')+'\\x05\\r\\n').join(''));
|
|
process.stdout.write(a.pack(new Map([['Global.ini',Buffer.from('[BASIC]')],['Main.txt',Buffer.from('0\\tSample\\t02\\x05\\r\\n')],
|
|
['junit.txt',table('junit.txt',[{F_CODE:'J1',F_DES:'machine',F_MPRICE:100,F_LPRICE:50,F_EPRICE:25}])],
|
|
['danga.txt',table('danga.txt',[{F_CODE:'D1',F_DES:'cost',F_MPRICE:100,F_LPRICE:50,F_EPRICE:25}])]])));"""
|
|
data = subprocess.check_output(["node", "-e", script], cwd=ROOT)
|
|
with sync_playwright() as p:
|
|
browser = p.chromium.launch(channel="msedge", headless=True)
|
|
page = browser.new_page()
|
|
try:
|
|
page.goto("http://100.65.141.15:5175", timeout=5000)
|
|
except Exception:
|
|
browser.close()
|
|
pytest.skip("EST server unavailable")
|
|
page.on("dialog", lambda dialog: dialog.accept())
|
|
page.wait_for_function("() => Boolean(EstApp.state.project)")
|
|
page.locator("#jdl-file").set_input_files(
|
|
{"name": "sample.jdl", "mimeType": "application/zip", "buffer": data}
|
|
)
|
|
page.wait_for_function("() => EstApp.state.project.tables.MachMainDat.length === 2")
|
|
assert page.locator("#dialog").is_visible()
|
|
assert "상세 산식" in page.locator("#dialog-content").inner_text()
|
|
page.locator("#dialog").evaluate("(dialog) => dialog.close()")
|
|
page.evaluate("""() => {
|
|
EstApp.state.project.options.AutoCalc=true;
|
|
EstApp.navigate('MachMainDat','J1');
|
|
}""")
|
|
amount = page.locator('[data-table="MachMainDat"][data-row="1"][data-field="GC"]')
|
|
amount.fill("120")
|
|
amount.press("Tab")
|
|
page.wait_for_function("() => EstApp.state.project.tables.MachTitleDat[0].TC === 195")
|
|
assert page.evaluate("EstApp.state.project.tables.MachMainDat[1].TC") == 195
|
|
page.evaluate("EstApp.navigate('CostMainDat','D1')")
|
|
formula = page.locator('[data-table="CostMainDat"][data-row="1"][data-field="LineData"]')
|
|
formula.fill(formula.input_value().replace("100", "130.25", 1))
|
|
formula.press("Tab")
|
|
page.wait_for_function("() => EstApp.state.project.tables.CostTitleDat[0].TC === 205.25")
|
|
browser.close()
|