from test_est_rea_parity import run import subprocess from pathlib import Path def test_attachment_size_prediction_includes_utf8_and_base64_overhead(): result = run( "math", """(()=>{ const p=EstNative.empty();p.name='한글';const file={name:'파일.bin',type:'',size:4}; const expected={...p,id:'0'.repeat(24),attachments:[{id:'0'.repeat(36),...file,data:'AAECAw=='}]}; return {predicted:EstAttachments.requiredBytes(p,[file]),actual:new Blob([JSON.stringify(expected)]).size, large:EstAttachments.requiredBytes(p,[{name:'large',type:'',size:210000000}]),count:p.attachments.length}; })()""", ) assert result["predicted"] == result["actual"] assert result["large"] > 256 * 1024 * 1024 assert result["count"] == 0 def test_attachment_validation_keeps_valid_data_and_rejects_malformed_content(): result = run( "math", """(()=>{ const p=EstNative.empty();p.attachments=[{id:'one',name:'test.bin',type:'application/octet-stream',size:4,data:'AAECAw=='}]; const valid=EstModel.validate(p).attachments[0];let rejected=0; for(const item of [{...valid,data:'AAECAw=A'},{...valid,size:5},{...valid,id:''},{...valid,name:''}]) { const q=EstNative.empty();q.attachments=[item];try{EstModel.validate(q)}catch{rejected++} } const dup=EstNative.empty();dup.attachments=[valid,valid];try{EstModel.validate(dup)}catch{rejected++} const empty=EstNative.empty();empty.attachments=[{id:'zero',name:'empty',type:'',size:0,data:''}]; return {valid,rejected,empty:EstModel.validate(empty).attachments[0].size}; })()""", ) assert result["valid"]["data"] == "AAECAw==" assert result["rejected"] == 5 assert result["empty"] == 0 def test_estx_export_rejects_web_attachments_instead_of_dropping_them(): script = """try{require('./tools/est-test/export-estx.cjs').exportEstx({originalArchive:'AA==',attachments:[{}]});process.exit(1)}catch(e){process.stdout.write(e.message)}""" result = subprocess.run( ["node", "-e", script], cwd=Path(__file__).resolve().parents[2], capture_output=True, check=True, timeout=10, ) assert "JSON" in result.stdout.decode("utf-8") def test_estx_export_rejects_web_excel_links_instead_of_dropping_them(): script = """try{require('./tools/est-test/export-estx.cjs').exportEstx({originalArchive:'AA==',excelQuantityLinks:[{}]});process.exit(1)}catch(e){process.stdout.write(e.message)}""" result = subprocess.run( ["node", "-e", script], cwd=Path(__file__).resolve().parents[2], capture_output=True, check=True, timeout=10, ) assert "JSON" in result.stdout.decode("utf-8") def test_estx_export_rejects_web_schedule_additions_or_changes_instead_of_dropping_them(): # 1) 일반 항목 추가 script_add = """try{require('./tools/est-test/export-estx.cjs').exportEstx({originalArchive:'AA==',schedule:[{id:'1',Code:'A',Name:'새공정',start:'',end:'',weight:0,progress:0}]});process.exit(1)}catch(e){process.stdout.write(e.message)}""" result_add = subprocess.run( ["node", "-e", script_add], cwd=Path(__file__).resolve().parents[2], capture_output=True, check=True, timeout=10, ) assert "JSON" in result_add.stdout.decode("utf-8") assert "예정공정표" in result_add.stdout.decode("utf-8") # 2) 빈 문자열 및 0값만 있는 행도 차단 script_empty = """try{require('./tools/est-test/export-estx.cjs').exportEstx({originalArchive:'AA==',schedule:[{id:'2',Code:'',Name:'',start:'',end:'',weight:0,progress:0}]});process.exit(1)}catch(e){process.stdout.write(e.message)}""" result_empty = subprocess.run( ["node", "-e", script_empty], cwd=Path(__file__).resolve().parents[2], capture_output=True, check=True, timeout=10, ) assert "JSON" in result_empty.stdout.decode("utf-8") assert "예정공정표" in result_empty.stdout.decode("utf-8") # 3) 다른 필드만 있거나 빈 객체 행도 차단 script_blank = """try{require('./tools/est-test/export-estx.cjs').exportEstx({originalArchive:'AA==',schedule:[{custom:'field'}]});process.exit(1)}catch(e){process.stdout.write(e.message)}""" result_blank = subprocess.run( ["node", "-e", script_blank], cwd=Path(__file__).resolve().parents[2], capture_output=True, check=True, timeout=10, ) assert "JSON" in result_blank.stdout.decode("utf-8") assert "예정공정표" in result_blank.stdout.decode("utf-8") def test_estx_export_rejects_web_custom_documents_instead_of_dropping_them(): script_doc = """try{require('./tools/est-test/export-estx.cjs').exportEstx({originalArchive:'AA==',documents:{'173':'주요심사내용'}});process.exit(1)}catch(e){process.stdout.write(e.message)}""" result_doc = subprocess.run( ["node", "-e", script_doc], cwd=Path(__file__).resolve().parents[2], capture_output=True, check=True, timeout=10, ) assert "JSON" in result_doc.stdout.decode("utf-8") assert "서식 문서" in result_doc.stdout.decode("utf-8") # 공백 문자열(" ")도 사용자 입력으로 보존하기 위해 ESTX 저장 차단 script_space = """try{require('./tools/est-test/export-estx.cjs').exportEstx({originalArchive:'AA==',documents:{'173':' '}});process.exit(1)}catch(e){process.stdout.write(e.message)}""" result_space = subprocess.run( ["node", "-e", script_space], cwd=Path(__file__).resolve().parents[2], capture_output=True, check=True, timeout=10, ) assert "JSON" in result_space.stdout.decode("utf-8") assert "서식 문서" in result_space.stdout.decode("utf-8")