From ec6b51f2a8c4d58852ee13440d4556fb7230d222 Mon Sep 17 00:00:00 2001 From: curry772 Date: Thu, 3 Sep 2026 10:20:13 +0900 Subject: [PATCH] =?UTF-8?q?=EC=A0=95=EC=83=81=20=EC=9D=91=EB=8B=B5=20MSG?= =?UTF-8?q?=20=EC=BD=94=EB=93=9C/=EB=A9=94=EC=8B=9C=EC=A7=80=EB=A5=BC=20?= =?UTF-8?q?=EB=A0=88=EC=9D=B4=EC=95=84=EC=9B=83=20=EA=B8=B0=EB=B3=B8?= =?UTF-8?q?=EA=B0=92=20=EB=8C=80=EC=8B=A0=20=EB=AA=85=EC=8B=9C=EC=A0=81?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit outp_msg_cd(NCMM00001) / outp_msg_ctnt(정상처리되었습니다.) 가 표준전문 레이아웃의 기본값으로 붙어 있어서, "아무도 채우지 않은 상태" 와 "정상이라고 판정한 상태" 가 값만으로 구분되지 않았다. 2026-09 사고에서 MSG 부 파싱이 통째로 스킵됐는데도 오류가 "정상처리되었습니다." 로 보고된 것이 이 때문이다. 레이아웃 기본값을 없애고, StandardMessageCoordinatorDJB.coordinateBeforeResponse 에서 정상 응답일 때만 채운다. 이 메서드는 모든 동기 응답이 거치는 마지막 관문이라 (RequestProcessor 에서 호출) 경로를 빠짐없이 덮는다. 두 조건을 함께 본다. - 정상 응답(procs_rslt_dvcd=S) 일 때만. 오류 응답에 정상 메시지를 채우면 사고 재현이다. - 값이 비어 있을 때만(setIfBlank). 대외계가 표준응답으로 보낸 MSG 가 우선이며 덮어쓰면 JsonReader 의 MSG 부 보존 수정이 무의미해진다. 테스트: 기존 3건이 coordinateAfterRecvNonStdSyncResponse 직후에 단정하고 있었으나 값은 이제 마지막 관문에서 채워지므로, 실제 흐름대로 coordinateBeforeResponse 까지 거치도록 고치고 단정도 "공백 아님" 에서 상수 일치로 강화했다. 설계 조건을 지키는 회귀 테스트 2건을 추가했다. - 오류응답에는_정상메시지가_채워지지_않는다 - 대외계가_보낸_MSG_값은_덮어쓰지_않는다 주의: 이 CSV 는 layout.file.type=CSV 인 테스트 설정에서만 쓰인다. 운영은 DB 레이아웃을 읽으므로 DB 의 두 항목 기본값도 함께 비워야 실제 효과가 있다. 비우기 전까지는 setIfBlank 가 아무것도 하지 않아 기존과 동일하게 동작한다. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01UnybKuxcuPafGhVGh4wqkZ --- .../StandardMessageCoordinatorDJB.java | 42 ++++++++++++-- src/main/resources/standard-layout-djb.csv | 4 +- .../StandardMessageCoordinatorDJBTest.java | 55 +++++++++++++++++-- .../message/StandardMessageJsonFlowTest.java | 6 +- 4 files changed, 94 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/eactive/eai/custom/message/StandardMessageCoordinatorDJB.java b/src/main/java/com/eactive/eai/custom/message/StandardMessageCoordinatorDJB.java index eb2ef73..b2c3551 100644 --- a/src/main/java/com/eactive/eai/custom/message/StandardMessageCoordinatorDJB.java +++ b/src/main/java/com/eactive/eai/custom/message/StandardMessageCoordinatorDJB.java @@ -77,7 +77,15 @@ public class StandardMessageCoordinatorDJB extends DefaultStandardMessageCoordin // DATA 영역 경로 public static final String DATA_SCOP_LEN = "DATA.data_scop_len"; - + + // 정상 응답 메시지. + // 예전에는 표준전문 레이아웃의 기본값으로 붙였으나, 그렇게 두면 "아무도 채우지 않은 상태" 와 + // "정상이라고 판정한 상태" 가 구분되지 않는다. 실제로 2026-09 사고에서 MSG 부 파싱이 통째로 + // 스킵됐는데도 오류가 "정상처리되었습니다." 로 보고됐다. + // 이제 레이아웃 기본값을 없애고, 정상이라고 판정한 지점에서만 명시적으로 설정한다. + public static final String NORMAL_OUTP_MSG_CD = "NCMM00001"; + public static final String NORMAL_OUTP_MSG_CTNT = "정상처리되었습니다."; + private String[] netInfo; @Override @@ -156,7 +164,21 @@ public class StandardMessageCoordinatorDJB extends DefaultStandardMessageCoordin standardMessage.setData(HEAD_MESG_RSPN_DT, now.format(FMT_DATE)); standardMessage.setData(HEAD_MESG_RSPN_TIME, now.format(FMT_TIME_MILLIS)); - + // 정상 응답의 MSG 코드/메시지를 여기서 채운다. 레이아웃 기본값을 없앴으므로 + // 이 지점이 정상 응답 메시지의 유일한 출처다. + // coordinateBeforeResponse 는 모든 동기 응답이 거치는 마지막 관문이라 + // (RequestProcessor 에서 호출) 경로를 빠짐없이 덮는다. + // + // 반드시 두 조건을 함께 본다. + // 1) 정상 응답일 때만. 오류 응답에 "정상처리되었습니다." 를 채우면 2026-09 사고 + // (MSG 부가 버려졌는데 오류가 정상으로 보고됨)를 코드로 재현하는 셈이다. + // 2) 값이 비어 있을 때만. 대외계가 표준응답으로 MSG 를 채워 보낸 경우 그 값이 + // 우선이며 덮어쓰면 안 된다. + if (STDMessageKeys.RESPONSE_TYPE_CODE_N.equals(mapper.getResponseType(standardMessage))) { + setIfBlank(standardMessage, MSG_OUTP_MSG_CD, NORMAL_OUTP_MSG_CD); + setIfBlank(standardMessage, MSG_OUTP_MSG_CTNT, NORMAL_OUTP_MSG_CTNT); + } + StandardItem msgListRowCnt = standardMessage.findItem(StandardMessageCoordinatorDJB.MSG_LIST_ROWCNT); if (msgListRowCnt == null || "0".equals(msgListRowCnt.getValue()) || "".equals(msgListRowCnt.getValue())) { StandardItem msgPart = standardMessage.findItem("MSG"); @@ -249,8 +271,9 @@ public class StandardMessageCoordinatorDJB extends DefaultStandardMessageCoordin mainMsgItem.setSize(1); } - // msg_dvcd=NM, outp_atrb_cd=0, outp_msg_cd, outp_msg_ctnt, msg_list_rowcnt=0 - // → 모두 CSV 기본값 사용 + // msg_dvcd=NM, outp_atrb_cd=0, msg_list_rowcnt=0 → 레이아웃 기본값 사용 + // outp_msg_cd, outp_msg_ctnt → 레이아웃 기본값을 없앴으므로 여기서는 비워 두고, + // 모든 동기 응답이 거치는 coordinateBeforeResponse 에서 정상 응답일 때만 채운다. makeMsgScopLen(responseMessage); } @@ -270,6 +293,17 @@ public class StandardMessageCoordinatorDJB extends DefaultStandardMessageCoordin } } + /** + * 항목이 존재하고 값이 비어 있을 때만 설정한다. + * 이미 채워진 값(대외계가 보낸 MSG, 오류 경로가 설정한 값 등)은 그대로 보존한다. + */ + private void setIfBlank(StandardMessage standardMessage, String path, String value) { + StandardItem item = standardMessage.findItem(path); + if (item != null && StringUtils.isBlank(item.getValue())) { + item.setValue(value); + } + } + private void resolveCallPropRefValues(StandardMessage standardMessage, Properties prop) { if (prop == null) return; resolveCallPropInChilds(standardMessage.getChilds(), prop); diff --git a/src/main/resources/standard-layout-djb.csv b/src/main/resources/standard-layout-djb.csv index 955efef..0415e23 100644 --- a/src/main/resources/standard-layout-djb.csv +++ b/src/main/resources/standard-layout-djb.csv @@ -151,8 +151,8 @@ msg_dvcd,2,2,0,1,2,1,,,NM msg_scop_len,2,2,0,1,8,2,,, MAIN_MSG,2,3,0,1,0,1,,, outp_atrb_cd,3,2,1,1,1,1,,,0 -outp_msg_cd,3,2,1,1,12,1,,,NCMM00001 -outp_msg_ctnt,3,2,1,1,200,1,,,정상처리되었습니다. +outp_msg_cd,3,2,1,1,12,1,,, +outp_msg_ctnt,3,2,1,1,200,1,,, outp_msg_desc,3,2,1,1,300,1,,, mngm_msg_cd,3,2,1,1,12,1,,, msg_list_rowcnt,3,2,0,1,5,2,,,0 diff --git a/src/test/java/com/eactive/eai/custom/message/StandardMessageCoordinatorDJBTest.java b/src/test/java/com/eactive/eai/custom/message/StandardMessageCoordinatorDJBTest.java index d78e251..d530eb7 100644 --- a/src/test/java/com/eactive/eai/custom/message/StandardMessageCoordinatorDJBTest.java +++ b/src/test/java/com/eactive/eai/custom/message/StandardMessageCoordinatorDJBTest.java @@ -324,20 +324,59 @@ class StandardMessageCoordinatorDJBTest { "정상 수신 시 msg_dvcd는 NM이어야 함"); } + // outp_msg_cd / outp_msg_ctnt 는 레이아웃 기본값을 없앴으므로 이 시점에는 비어 있고, + // 실제 흐름대로 coordinateBeforeResponse 까지 거쳐야 채워진다. @Test - void 정상수신시_outp_msg_cd_설정됨() { + void 정상수신시_outp_msg_cd_는_응답조정후_설정됨() { coordinator.coordinateAfterRecvNonStdSyncResponse(message); + assertTrue(isBlankValue(message.findItemValue("MSG.MAIN_MSG.outp_msg_cd")), + "수신 조정 단계에서는 아직 비어 있어야 함 (레이아웃 기본값이 없어야 한다)"); - String msgCd = message.findItemValue("MSG.MAIN_MSG.outp_msg_cd"); - assertFalse(msgCd == null || msgCd.trim().isEmpty(), "outp_msg_cd가 설정되어야 함"); + coordinator.coordinateBeforeResponse(message, null, null, "utf-8"); + + assertEquals(StandardMessageCoordinatorDJB.NORMAL_OUTP_MSG_CD, + message.findItemValue("MSG.MAIN_MSG.outp_msg_cd"), + "정상 응답의 outp_msg_cd 가 설정되어야 함"); } @Test - void 정상수신시_outp_msg_ctnt_설정됨() { + void 정상수신시_outp_msg_ctnt_는_응답조정후_설정됨() { coordinator.coordinateAfterRecvNonStdSyncResponse(message); + assertTrue(isBlankValue(message.findItemValue("MSG.MAIN_MSG.outp_msg_ctnt")), + "수신 조정 단계에서는 아직 비어 있어야 함 (레이아웃 기본값이 없어야 한다)"); - String ctnt = message.findItemValue("MSG.MAIN_MSG.outp_msg_ctnt"); - assertFalse(ctnt == null || ctnt.trim().isEmpty(), "outp_msg_ctnt가 설정되어야 함"); + coordinator.coordinateBeforeResponse(message, null, null, "utf-8"); + + assertEquals(StandardMessageCoordinatorDJB.NORMAL_OUTP_MSG_CTNT, + message.findItemValue("MSG.MAIN_MSG.outp_msg_ctnt"), + "정상 응답의 outp_msg_ctnt 가 설정되어야 함"); + } + + @Test + void 오류응답에는_정상메시지가_채워지지_않는다() { + coordinator.coordinateSetStandardMessageError(message, mapper, "RECEAIINA001", "테스트오류"); + coordinator.coordinateBeforeResponse(message, null, null, "utf-8"); + + assertNotEquals(StandardMessageCoordinatorDJB.NORMAL_OUTP_MSG_CD, + message.findItemValue("MSG.MAIN_MSG.outp_msg_cd"), + "오류 응답에 정상코드가 채워지면 안 됨"); + assertNotEquals(StandardMessageCoordinatorDJB.NORMAL_OUTP_MSG_CTNT, + message.findItemValue("MSG.MAIN_MSG.outp_msg_ctnt"), + "오류 응답에 '정상처리되었습니다.' 가 채워지면 안 됨 — 2026-09 사고의 재현"); + } + + @Test + void 대외계가_보낸_MSG_값은_덮어쓰지_않는다() { + coordinator.coordinateAfterRecvNonStdSyncResponse(message); + message.setData("MSG.MAIN_MSG.outp_msg_cd", "ABC00001"); + message.setData("MSG.MAIN_MSG.outp_msg_ctnt", "상대가 보낸 메시지"); + + coordinator.coordinateBeforeResponse(message, null, null, "utf-8"); + + assertEquals("ABC00001", message.findItemValue("MSG.MAIN_MSG.outp_msg_cd"), + "이미 채워진 outp_msg_cd 를 덮어쓰면 안 됨"); + assertEquals("상대가 보낸 메시지", message.findItemValue("MSG.MAIN_MSG.outp_msg_ctnt"), + "이미 채워진 outp_msg_ctnt 를 덮어쓰면 안 됨"); } @Test @@ -496,4 +535,8 @@ class StandardMessageCoordinatorDJBTest { f.setAccessible(true); f.set(item, refValue); } + + private static boolean isBlankValue(String value) { + return value == null || value.trim().isEmpty(); + } } diff --git a/src/test/java/com/eactive/eai/custom/message/StandardMessageJsonFlowTest.java b/src/test/java/com/eactive/eai/custom/message/StandardMessageJsonFlowTest.java index 29d2c61..fee5e50 100644 --- a/src/test/java/com/eactive/eai/custom/message/StandardMessageJsonFlowTest.java +++ b/src/test/java/com/eactive/eai/custom/message/StandardMessageJsonFlowTest.java @@ -443,11 +443,15 @@ class StandardMessageJsonFlowTest { void 비표준수신_정상응답_JSON_직렬화_outp_msg_ctnt_설정됨() throws Exception { StandardMessage msg = manager.getStandardMessage(); coordinator.coordinateAfterRecvNonStdSyncResponse(msg); + // outp_msg_cd/ctnt 는 레이아웃 기본값이 없으므로, 실제 흐름대로 응답 직전 조정까지 + // 거쳐야 채워진다 (StandardMessageCoordinatorDJB.coordinateBeforeResponse) + coordinator.coordinateBeforeResponse(msg, null, null, "utf-8"); String json = msg.getDataString(MessageType.JSON, "utf-8"); JsonNode root = jacksonMapper.readTree(json); String ctnt = root.path("MSG").path("MAIN_MSG").path("outp_msg_ctnt").asText(); - assertFalse(ctnt.isEmpty(), "정상응답 JSON의 outp_msg_ctnt가 설정되어야 함"); + assertEquals(StandardMessageCoordinatorDJB.NORMAL_OUTP_MSG_CTNT, ctnt, + "정상응답 JSON의 outp_msg_ctnt가 설정되어야 함"); } // ================================================================