From 35d3cc936c91dc274755ea1ea8384456920d66d8 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Sat, 7 Nov 2026 08:28:29 +0900 Subject: [PATCH] fix(est): validate imported review targets --- resources/tester/test_est_exchange.py | 5 +++-- tools/est-test/native-exchange.js | 20 ++++++++++++++++++++ tools/est-test/native-review.js | 2 +- 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/resources/tester/test_est_exchange.py b/resources/tester/test_est_exchange.py index 4d153e615..1d6716dd0 100644 --- a/resources/tester/test_est_exchange.py +++ b/resources/tester/test_est_exchange.py @@ -255,14 +255,15 @@ def test_excel_exports_and_imports_contract_review_inputs(): const p=EstNative.empty();p.tables.EstMainDat=[{PkID:1,Code:'A',GN:2,GU:100}]; const row={RowKey:'row:1',GN:1,GU:80,Reason:'checked'}; const q=EstExchange.apply(p,{Review:[row]});let rejected=0; - for(const bad of [[row,row],[{...row,GU:'bad'}],[{...row,RowKey:'not-key'}]]) + for(const bad of [[row,row],[{...row,GU:'bad'}],[{...row,RowKey:'not-key'}], + [{...row,RowKey:'row:missing'}],[{RowKey:'row:1',GC:4}]]) try{EstExchange.apply(p,{Review:bad})}catch{rejected++} return {review:EstReview.calculate(q),edit:q.contractReview.edits['row:1'],rejected}; })()""", ) assert result["review"]["after"] == 80 assert result["edit"]["Reason"] == "checked" - assert result["rejected"] == 3 + assert result["rejected"] == 5 def test_excel_exports_only_unlocked_progress_and_annual_stages(): diff --git a/tools/est-test/native-exchange.js b/tools/est-test/native-exchange.js index e0e5a665f..813fe6409 100644 --- a/tools/est-test/native-exchange.js +++ b/tools/est-test/native-exchange.js @@ -390,6 +390,26 @@ const EstExchange = (() => { return merged; }); } + if (Object.hasOwn(tables, "Review")) { + const targets = new Map(); + next.tables.EstMainDat.forEach((row, index) => { + if (!EstReview.editable(row)) return; + const key = EstReview.rowKey(row, index), + matches = targets.get(key) || []; + matches.push(row); + targets.set(key, matches); + }); + for (const [key, edit] of Object.entries(next.contractReview.edits)) { + const matches = targets.get(key) || []; + if (matches.length !== 1) throw Error(`심사 내역이 없거나 중복됩니다: ${key}`); + if ( + Object.keys(edit).some( + (field) => field !== "Reason" && !EstReview.allowed(matches[0]).includes(field), + ) + ) + throw Error(`해당 내역에 사용할 수 없는 심사 입력: ${key}`); + } + } if (Object.hasOwn(tables, "ProgressDrafts")) { const counts = new Map(); next.tables.EstMainDat.forEach((row, index) => { diff --git a/tools/est-test/native-review.js b/tools/est-test/native-review.js index d2b63b24f..a18a516be 100644 --- a/tools/est-test/native-review.js +++ b/tools/est-test/native-review.js @@ -160,5 +160,5 @@ const EstReview = (() => { } }); } - return { validate, calculate, copy, rowKey, render, install }; + return { validate, calculate, copy, rowKey, editable, allowed, render, install }; })();