from pathlib import Path from lxml import etree from test_est_rea_parity import run ROOT = Path(__file__).resolve().parents[2] SCHEMA = next((ROOT / "resources/knowledge").rglob("조달청_공사원가호환규정.xsd")) def test_c3r_export_validates_against_the_supplied_schema_and_preserves_prices(): result = run( "math", """(()=>{ const p=EstNative.empty();p.name='C3R test';p.options.SourceRefresh=false; p.tables.StuffcDat=[{PkID:1,Code:'M1',Name:'material',TC:100,GC:100}]; p.tables.LaborcDat=[{PkID:1,Code:'L1',Name:'labor',TC:50,NC:50}]; p.tables.EstMainDat=[{PkID:1,Code:'M1',LinkIndex:5,Name:'material',GN:2,GU:100},{PkID:2,Code:'L1',LinkIndex:6,Name:'labor',GN:3,NU:50},{PkID:3,LinkIndex:54}]; p.tables.Wonga=[{Gubun:'G',Name:'material',BaseField:'GC'}, {Gubun:'N',Name:'labor',BaseField:'NC'},{Gubun:'T',Name:'total',VSusic:'G+N'}]; return EstC3r.output(EstNative.calculate(p),{company:'test',kind:'토목공사',date:'2026-11-06'}); })()""", ) schema = etree.XMLSchema(etree.parse(str(SCHEMA))) root = etree.fromstring(result["xml"].encode("utf-8")) assert schema.validate(root), str(schema.error_log) assert root.findtext(".//세부공종/재료비") == "200" assert root.findtext(".//세부공종/수량") == "2" assert root.findtext(".//작성정보/파일검증여부") == "false" assert result["warnings"] == [] def test_xml_excluded_rows_do_not_inflate_group_totals_and_invalid_codes_are_rejected(): result = run( "math", """(()=>{ const p=EstNative.empty();p.name='test';p.tables.StuffcDat=[{Code:'M1',Name:'material',TC:100}];p.tables.EstMainDat=[ {Code:'M1',Name:'normal',LinkIndex:5,GN:2,GU:100,GC:200,TC:200}, {Code:'X1',Name:'excluded',GN:1,GC:999,TC:999,CalcIndex:3},{LinkIndex:54}]; const meta={company:'test',kind:'토목공사',date:'2026-11-06'}; const result=EstC3r.output(p,meta);let blocked=false; p.tables.EstMainDat[0].Code='bad code';try{EstC3r.output(p,meta)}catch{blocked=true} return {...result,blocked}; })()""", ) root = etree.fromstring(result["xml"].encode("utf-8")) assert root.findtext(".//내역서/재료비") == "200" assert root.findall(".//세부공종")[1].findtext("계산제외") == "true" assert result["blocked"] def test_xml_keeps_parent_and_child_groups_instead_of_dropping_empty_parent_headers(): result = run( "math", """(()=>{ const p=EstNative.empty();p.name='test';p.tables.EstMainDat=[ {Code:'ROOT',Name:'parent',Level:3,GC:200,TC:200}, {Code:'CHILD',Name:'child',Level:1,GC:200,TC:200}, {Code:'FIX',Name:'fixed',GN:1,GC:200,TC:200,CalcIndex:1},{LinkIndex:54}]; return EstC3r.output(p,{company:'test',kind:'토목공사',date:'2026-11-06'}); })()""", ) root = etree.fromstring(result["xml"].encode("utf-8")) schema = etree.XMLSchema(etree.parse(str(SCHEMA))) assert schema.validate(root), str(schema.error_log) groups = root.findall(".//내역서") assert [group.findtext("내역서번호") for group in groups] == ["01", "0101"] assert [group.findtext("재료비") for group in groups] == ["200", "200"] assert len(groups[0].findall("세부공종")) == 0 assert len(groups[1].findall("세부공종")) == 1