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 e7759a1..001453f 100644 --- a/src/main/java/com/eactive/eai/custom/message/StandardMessageCoordinatorDJB.java +++ b/src/main/java/com/eactive/eai/custom/message/StandardMessageCoordinatorDJB.java @@ -47,6 +47,22 @@ public class StandardMessageCoordinatorDJB extends DefaultStandardMessageCoordin private static final String HEAD_MESG_RSPN_DT = "HEAD.mesg_rspn_dt"; private static final String HEAD_MESG_RSPN_TIME = "HEAD.mesg_rspn_time"; + // HEAD ERP 채널 필드 경로 + private static final String HEAD_TX_CMGR_CD = "HEAD.tx_cmgr_cd"; + private static final String HEAD_MGMT_BRCD = "HEAD.mgmt_brcd"; + private static final String HEAD_TX_BRCD = "HEAD.tx_brcd"; + private static final String HEAD_INSL_BRCD = "HEAD.insl_brcd"; + private static final String HEAD_BLNG_BRCD = "HEAD.blng_brcd"; + private static final String HEAD_CORP_TLWN_VIRT_BRCD = "HEAD.corp_tlwn_virt_brcd"; + + // EZDATA 영역 경로 + private static final String EZDATA_ELFN_BNKN_DVCD = "EZDATA.elfn_bnkn_dvcd"; + private static final String EZDATA_SVR_TX_SEQNO = "EZDATA.svr_tx_seqno"; + private static final String EZDATA_ELFN_MAC = "EZDATA.elfn_mac"; + private static final String EZDATA_PC_OFCL_IPAD = "EZDATA.pc_ofcl_ipad"; + private static final String EZDATA_PC_PRVAT_IPAD = "EZDATA.pc_prvat_ipad"; + private static final String EZDATA_ELFN_SPR_FILD = "EZDATA.elfn_spr_fild"; + // MSG 영역 경로 private static final String MSG_DVCD = "MSG.msg_dvcd"; private static final String MSG_SCOP_LEN = "MSG.msg_scop_len"; @@ -60,6 +76,8 @@ public class StandardMessageCoordinatorDJB extends DefaultStandardMessageCoordin // DATA 영역 경로 private static final String DATA_SCOP_LEN = "DATA.data_scop_len"; + private static final String ERP_CODE = "ERP"; + @Override public void coordinateAfterParsing(StandardMessage standardMessage, Object paramObject, Properties prop) { } @@ -73,10 +91,10 @@ public class StandardMessageCoordinatorDJB extends DefaultStandardMessageCoordin String guid = GUIDGeneratorDJB.getGUID("EAI"); mapper.setGuid(standardMessage, guid); - // GUID 진행번호 초기화 (최초=1) + // GUID 진행번호 초기화 (최초=001) mapper.setGuidSeq(standardMessage, "001"); - // 원거래GUID = GUID 동일값 설정 (복원이 아닌 경우) + // 원거래GUID = GUID 동일값 설정 mapper.setOrgGuid(standardMessage, guid); // 최초전문요청일자/시각 설정 @@ -91,8 +109,20 @@ public class StandardMessageCoordinatorDJB extends DefaultStandardMessageCoordin EAIServerManager eaiServer = EAIServerManager.getInstance(); standardMessage.setData(HEAD_SYS_ENV_DVCD, getSysEnvDvcd(eaiServer)); - // 최초전송 IP / IPv4 / MAC 설정 - setNetworkInfo(standardMessage); + // 최초전송 IP / IPv4 / MAC 수집 및 설정 + String[] netInfo = collectNetworkInfo(); + String ipv4 = netInfo[0]; + String mac = netInfo[1]; + standardMessage.setData(HEAD_FRST_TRNM_IPAD, ipv4); + standardMessage.setData(HEAD_FRST_TRNM_IPV4_ADDR, ipv4); + standardMessage.setData(HEAD_FRST_TRNM_MAC, mac); + + // ERP 채널 전자금융공통영역(EZDATA) 설정 + String corpTlwn = StringUtils.trimToEmpty(standardMessage.findItemValue(HEAD_CORP_TLWN_VIRT_BRCD)); + if (ERP_CODE.equals(corpTlwn)) { + setErpHeadFields(standardMessage); + setEzdataFields(standardMessage, ipv4, mac); + } } @Override @@ -102,6 +132,15 @@ public class StandardMessageCoordinatorDJB extends DefaultStandardMessageCoordin @Override public void coordinateBeforeResponse(StandardMessage standardMessage, ProcessVO processVO, Properties prop, String charset) { + StandardMessageManager manager = StandardMessageManager.getInstance(); + InterfaceMapper mapper = manager.getMapper(); + + // GUID 진행번호 +1 + mapper.nextGuidSeq(standardMessage); + + // 요청응답구분 = 응답(R) + mapper.setSendRecvDivision(standardMessage, STDMessageKeys.SEND_RECV_CD_RECV); + // 응답일자/시각 설정 LocalDateTime now = LocalDateTime.now(); standardMessage.setData(HEAD_MESG_RSPN_DT, now.format(FMT_DATE)); @@ -154,6 +193,8 @@ public class StandardMessageCoordinatorDJB extends DefaultStandardMessageCoordin if (row != null) { if (row.containsKey("outp_msg_cd")) row.get("outp_msg_cd").setValue(siteErrCode); if (row.containsKey("outp_msg_ctnt")) row.get("outp_msg_ctnt").setValue(siteErrMsg); + if (row.containsKey("outp_msg_desc")) row.get("outp_msg_desc").setValue(siteErrMsg); + if (row.containsKey("err_occu_loct")) row.get("err_occu_loct").setValue(errCode); } } @@ -187,22 +228,12 @@ public class StandardMessageCoordinatorDJB extends DefaultStandardMessageCoordin mainMsgItem.setSize(1); } - // msg_dvcd = NM (정상메시지) - responseMessage.setData(MSG_DVCD, "NM"); + // msg_dvcd = NM (정상메시지), msg_list 없음(rowcnt=0) + responseMessage.setData(MSG_DVCD, "NM"); responseMessage.setData(MSG_OUTP_ATRB_CD, "0"); - responseMessage.setData(MSG_OUTP_MSG_CD, "000000000000"); - responseMessage.setData(MSG_OUTP_MSG_CTNT, "정상처리"); - responseMessage.setData(MSG_LIST_ROWCNT, "00001"); - - StandardItem msgList = responseMessage.findItem(MSG_LIST); - if (msgList != null) { - msgList.setSize(1); - LinkedHashMap row = msgList.getArrayChilds(0, true); - if (row != null) { - if (row.containsKey("outp_msg_cd")) row.get("outp_msg_cd").setValue("000000000000"); - if (row.containsKey("outp_msg_ctnt")) row.get("outp_msg_ctnt").setValue("정상처리"); - } - } + responseMessage.setData(MSG_OUTP_MSG_CD, "NCMM00001 "); + responseMessage.setData(MSG_OUTP_MSG_CTNT, "정상처리되었습니다."); + responseMessage.setData(MSG_LIST_ROWCNT, "00000"); makeMsgScopLen(responseMessage); } @@ -217,31 +248,50 @@ public class StandardMessageCoordinatorDJB extends DefaultStandardMessageCoordin return "D"; // 개발 } - private void setNetworkInfo(StandardMessage standardMessage) { + private String[] collectNetworkInfo() { + String ipv4 = ""; + String mac = ""; try { InetAddress localAddr = InetAddress.getLocalHost(); - String ipv4 = localAddr.getHostAddress(); - - // IPv4 주소 설정 - standardMessage.setData(HEAD_FRST_TRNM_IPAD, ipv4); - standardMessage.setData(HEAD_FRST_TRNM_IPV4_ADDR, ipv4); - - // MAC 주소 설정 + ipv4 = localAddr.getHostAddress(); NetworkInterface ni = NetworkInterface.getByInetAddress(localAddr); if (ni != null) { - byte[] mac = ni.getHardwareAddress(); - if (mac != null) { + byte[] macBytes = ni.getHardwareAddress(); + if (macBytes != null) { StringBuilder sb = new StringBuilder(); - for (int i = 0; i < mac.length; i++) { + for (int i = 0; i < macBytes.length; i++) { if (i > 0) sb.append(":"); - sb.append(String.format("%02X", mac[i])); + sb.append(String.format("%02X", macBytes[i])); } - standardMessage.setData(HEAD_FRST_TRNM_MAC, sb.toString()); + mac = sb.toString(); } } } catch (Exception e) { - if (logger.isWarn()) logger.warn("Failed to set network info", e); + if (logger.isWarn()) logger.warn("Failed to collect network info", e); } + return new String[]{ipv4, mac}; + } + + private void setErpHeadFields(StandardMessage standardMessage) { + standardMessage.setData(HEAD_TX_CMGR_CD, "S004"); + standardMessage.setData(HEAD_MGMT_BRCD, "260"); + standardMessage.setData(HEAD_TX_BRCD, "260"); + standardMessage.setData(HEAD_INSL_BRCD, "260"); + standardMessage.setData(HEAD_BLNG_BRCD, "260"); + } + + private void setEzdataFields(StandardMessage standardMessage, String ipv4, String mac) { + StandardItem ezdataItem = standardMessage.findItem("EZDATA"); + if (ezdataItem != null) { + ezdataItem.setHidden(false); + ezdataItem.setSize(1); + } + standardMessage.setData(EZDATA_ELFN_BNKN_DVCD, "10"); + standardMessage.setData(EZDATA_SVR_TX_SEQNO, String.valueOf(System.currentTimeMillis() / 1000L)); + standardMessage.setData(EZDATA_ELFN_MAC, mac); + standardMessage.setData(EZDATA_PC_OFCL_IPAD, ipv4); + standardMessage.setData(EZDATA_PC_PRVAT_IPAD, ipv4); + standardMessage.setData(EZDATA_ELFN_SPR_FILD, ERP_CODE); } private void makeupDataScopLen(StandardMessage standardMessage, String charset) { 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 ea6fc60..05ba3d1 100644 --- a/src/test/java/com/eactive/eai/custom/message/StandardMessageCoordinatorDJBTest.java +++ b/src/test/java/com/eactive/eai/custom/message/StandardMessageCoordinatorDJBTest.java @@ -321,27 +321,28 @@ class StandardMessageCoordinatorDJBTest { } @Test - void 정상수신시_outp_msg_cd_000000000000_설정() { + void 정상수신시_outp_msg_cd_설정됨() { coordinator.coordinateAfterRecvNonStdSyncResponse(message); - assertEquals("000000000000", 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가 설정되어야 함"); } @Test - void 정상수신시_outp_msg_ctnt_정상처리_설정() { + void 정상수신시_outp_msg_ctnt_설정됨() { coordinator.coordinateAfterRecvNonStdSyncResponse(message); - assertEquals("정상처리", 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가 설정되어야 함"); } @Test - void 정상수신시_msg_list_1건_구성() { + void 정상수신시_msg_list_rowcnt_0() { coordinator.coordinateAfterRecvNonStdSyncResponse(message); - // NUMBER 타입이므로 "00001" → "1" 저장됨 String rowcnt = message.findItemValue("MSG.MAIN_MSG.msg_list_rowcnt"); - assertNotNull(rowcnt, "msg_list_rowcnt가 null"); - assertEquals(1, Integer.parseInt(rowcnt.trim()), "msg_list는 1건이어야 함"); + String normalized = (rowcnt == null || rowcnt.trim().isEmpty()) ? "0" : rowcnt.trim(); + assertEquals(0, Integer.parseInt(normalized), "정상 응답 msg_list는 0건이어야 함"); } @Test diff --git a/src/test/java/com/eactive/eai/custom/message/StandardMessageEzdataJsonFlowTest.java b/src/test/java/com/eactive/eai/custom/message/StandardMessageEzdataJsonFlowTest.java new file mode 100644 index 0000000..d3e8567 --- /dev/null +++ b/src/test/java/com/eactive/eai/custom/message/StandardMessageEzdataJsonFlowTest.java @@ -0,0 +1,422 @@ +package com.eactive.eai.custom.message; + +import static org.junit.jupiter.api.Assertions.*; + +import java.lang.reflect.Field; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.springframework.context.ApplicationContext; + +import com.eactive.eai.common.monitor.EAIServiceMonitor; +import com.eactive.eai.common.property.PropManager; +import com.eactive.eai.common.server.EAIServerManager; +import com.eactive.eai.common.util.ApplicationContextProvider; +import com.eactive.eai.message.StandardMessage; +import com.eactive.eai.message.manager.StandardMessageManager; +import com.eactive.eai.message.parser.JsonReader; +import com.eactive.eai.message.service.InterfaceMapper; +import com.eactive.eai.common.message.MessageType; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +import static org.mockito.ArgumentMatchers.anyString; + +/** + * DJBank 표준전문 EZDATA(전자금융공통영역) JSON 처리 흐름 통합 테스트 + * + * 검증 시나리오: + * 1. EZDATA 포함 요청 JSON 파싱 — corp_tlwn_virt_brcd=ERP 조건 활성 + * 2. EZDATA 핵심 필드 값 검증 + * 3. EZDATA 조건 미충족(비ERP) 시 JSON 직렬화 제외 검증 + * 4. EZDATA 포함 JSON 왕복(parse → serialize) 일관성 + * 5. coordinateAfterCreateMessageByRule 후 EZDATA 보존 + * 6. coordinateBeforeResponse 후 EZDATA 포함 직렬화 + * 7. 오류 응답 생성 시 EZDATA 보존 + */ +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class StandardMessageEzdataJsonFlowTest { + + private static final String TEST_GUID = + "20260420" + "120000" + "000" + "EAI" + "01" + "123456789012" + "000001"; + + private static final String TEST_IF_ID = "IF_DJBTEST_EZ001"; + private static final String TEST_TX_ID = "TXTEST000002"; + + /** + * ERP 채널 요청 JSON — corp_tlwn_virt_brcd=ERP → EZDATA 블록 활성화 + */ + private static final String ERP_REQ_JSON = + "{" + + "\"HEAD\":{" + + "\"nxgn_stnd_idfr\":\"JERA\"," + + "\"guid\":\"" + TEST_GUID + "\"," + + "\"guid_prgs_no\":\"1\"," + + "\"stnd_mesg_ver\":\"R10\"," + + "\"ortr_guid\":\"" + TEST_GUID + "\"," + + "\"dman_rspn_dvcd\":\"S\"," + + "\"if_id\":\"" + TEST_IF_ID + "\"," + + "\"procs_rslt_dvcd\":\"S\"," + + "\"tx_id\":\"" + TEST_TX_ID + "\"," + + "\"chnl_tycd\":\"EAI\"," + + "\"hmab_dvcd\":\"1\"," + + "\"corp_tlwn_virt_brcd\":\"ERP\"," + + "\"trnm_sys_dvcd\":\"OPA\"," + + "\"frst_trnm_sys_dvcd\":\"OPA\"," + + "\"sys_env_dvcd\":\"D\"," + + "\"frst_mesg_dman_dt\":\"20260420\"," + + "\"frst_mesg_dman_time\":\"120000000\"" + + "}," + + "\"EZDATA\":{" + + "\"login_mhod_dvcd\":\"1\"," + + "\"elfn_bnkn_dvcd\":\"01\"," + + "\"usr_no\":\"USR0000000000001\"," + + "\"rgsr_id\":\"REG_ID_000000001\"," + + "\"secu_media_dvcd\":\"00\"," + + "\"secu_media_sta_dvcd\":\"00\"," + + "\"brll_srcd_yn\":\"N\"," + + "\"next_data_exis_yn\":\"N\"," + + "\"page_no\":\"00001\"," + + "\"page_size\":\"00020\"," + + "\"dup_tx_yn\":\"N\"" + + "}," + + "\"DATA\":{" + + "\"data_dvcd\":\"IO\"," + + "\"data_scop_len\":\"37\"," + + "\"BIZ_DATA\":{\"acct_no\":\"987654321\",\"amt\":\"500000\"}" + + "}" + + "}"; + + /** + * 비ERP 요청 JSON — corp_tlwn_virt_brcd 미설정 → EZDATA 블록 비활성 + */ + private static final String NON_ERP_REQ_JSON = + "{" + + "\"HEAD\":{" + + "\"nxgn_stnd_idfr\":\"JERA\"," + + "\"guid\":\"" + TEST_GUID + "\"," + + "\"guid_prgs_no\":\"1\"," + + "\"stnd_mesg_ver\":\"R10\"," + + "\"ortr_guid\":\"" + TEST_GUID + "\"," + + "\"dman_rspn_dvcd\":\"S\"," + + "\"if_id\":\"" + TEST_IF_ID + "\"," + + "\"procs_rslt_dvcd\":\"S\"," + + "\"tx_id\":\"" + TEST_TX_ID + "\"," + + "\"chnl_tycd\":\"EAI\"," + + "\"hmab_dvcd\":\"1\"," + + "\"trnm_sys_dvcd\":\"OPA\"," + + "\"frst_trnm_sys_dvcd\":\"OPA\"," + + "\"sys_env_dvcd\":\"D\"," + + "\"frst_mesg_dman_dt\":\"20260420\"," + + "\"frst_mesg_dman_time\":\"120000000\"" + + "}," + + "\"DATA\":{" + + "\"data_dvcd\":\"IO\"," + + "\"data_scop_len\":\"20\"," + + "\"BIZ_DATA\":{\"acct_no\":\"111222333\"}" + + "}" + + "}"; + + private StandardMessageManager manager; + private InterfaceMapper mapper; + private JsonReader jsonReader; + private StandardMessageCoordinatorDJB coordinator; + private ObjectMapper jacksonMapper; + + @BeforeAll + void setUpAll() throws Exception { + PropManager mockPropManager = Mockito.mock(PropManager.class); + Mockito.when(mockPropManager.getProperty(anyString(), anyString(), anyString())) + .thenAnswer(new Answer() { + public String answer(InvocationOnMock inv) { return (String) inv.getArguments()[2]; } + }); + + EAIServerManager mockServer = Mockito.mock(EAIServerManager.class); + Mockito.when(mockServer.isPEAIServer()).thenReturn(false); + Mockito.when(mockServer.isSEAIServer()).thenReturn(false); + Mockito.when(mockServer.isMCI()).thenReturn(false); + Mockito.when(mockServer.getLocalServerName()).thenReturn("TEST_SERVER"); + Mockito.when(mockServer.getGroupInstId()).thenReturn("TEST_INST"); + + EAIServiceMonitor mockMonitor = Mockito.mock(EAIServiceMonitor.class); + Mockito.when(mockMonitor.isTimeOutCodes(anyString())).thenReturn(false); + Mockito.when(mockMonitor.isBizErrorCodes(anyString())).thenReturn(false); + + ApplicationContext mockCtx = Mockito.mock(ApplicationContext.class); + Mockito.when(mockCtx.getBean(EAIServerManager.class)).thenReturn(mockServer); + Mockito.when(mockCtx.getBean(PropManager.class)).thenReturn(mockPropManager); + Mockito.when(mockCtx.getBean(EAIServiceMonitor.class)).thenReturn(mockMonitor); + + Field ctxField = ApplicationContextProvider.class.getDeclaredField("context"); + ctxField.setAccessible(true); + ctxField.set(null, mockCtx); + + System.setProperty(StandardMessageManager.STANDARD_MESSAGE_CONFIG, + "src/test/resources/standard-message-config-test.properties"); + manager = StandardMessageManager.getInstance(); + manager.init(); + } + + @BeforeEach + void setUp() { + mapper = manager.getMapper(); + jsonReader = (JsonReader) manager.getReader("JSON"); + coordinator = new StandardMessageCoordinatorDJB(); + jacksonMapper = new ObjectMapper(); + } + + // ================================================================ + // 1. EZDATA 포함 요청 JSON 파싱 + // ================================================================ + + @Test + void ERP요청_파싱_EZDATA_블록_활성() throws Exception { + StandardMessage msg = parseErpReq(); + + assertNotNull(msg.findItem("EZDATA"), "EZDATA 블록이 null"); + assertFalse(msg.findItem("EZDATA").isHidden(), "EZDATA 블록이 hidden 상태"); + } + + @Test + void ERP요청_파싱_HEAD_corp_tlwn_virt_brcd_ERP() throws Exception { + StandardMessage msg = parseErpReq(); + + assertEquals("ERP", msg.findItemValue("HEAD.corp_tlwn_virt_brcd"), + "corp_tlwn_virt_brcd가 ERP가 아님"); + } + + // ================================================================ + // 2. EZDATA 핵심 필드 값 검증 + // ================================================================ + + @Test + void EZDATA_login_mhod_dvcd_파싱() throws Exception { + StandardMessage msg = parseErpReq(); + + assertEquals("1", msg.findItemValue("EZDATA.login_mhod_dvcd"), + "EZDATA.login_mhod_dvcd 불일치"); + } + + @Test + void EZDATA_elfn_bnkn_dvcd_파싱() throws Exception { + StandardMessage msg = parseErpReq(); + + assertEquals("01", msg.findItemValue("EZDATA.elfn_bnkn_dvcd"), + "EZDATA.elfn_bnkn_dvcd 불일치"); + } + + @Test + void EZDATA_usr_no_파싱() throws Exception { + StandardMessage msg = parseErpReq(); + + assertEquals("USR0000000000001", msg.findItemValue("EZDATA.usr_no").trim(), + "EZDATA.usr_no 불일치"); + } + + @Test + void EZDATA_rgsr_id_파싱() throws Exception { + StandardMessage msg = parseErpReq(); + + assertEquals("REG_ID_000000001", msg.findItemValue("EZDATA.rgsr_id").trim(), + "EZDATA.rgsr_id 불일치"); + } + + @Test + void EZDATA_secu_media_dvcd_파싱() throws Exception { + StandardMessage msg = parseErpReq(); + + assertEquals("00", msg.findItemValue("EZDATA.secu_media_dvcd"), + "EZDATA.secu_media_dvcd 불일치"); + } + + @Test + void EZDATA_dup_tx_yn_파싱() throws Exception { + StandardMessage msg = parseErpReq(); + + assertEquals("N", msg.findItemValue("EZDATA.dup_tx_yn"), + "EZDATA.dup_tx_yn 불일치"); + } + + // ================================================================ + // 3. EZDATA 조건 미충족(비ERP) — 입력값 미설정 검증 + // EZDATA 블록은 기본값으로 직렬화되므로, 입력 JSON에 없는 ERP 전용 + // 필드(usr_no, rgsr_id 등)는 파싱 후 빈 값(기본값)이어야 함 + // ================================================================ + + @Test + void 비ERP요청_파싱_EZDATA_usr_no_미설정() throws Exception { + StandardMessage msg = parseNonErpReq(); + + String usrNo = msg.findItemValue("EZDATA.usr_no"); + assertTrue(usrNo == null || usrNo.trim().isEmpty(), + "비ERP 요청에서 EZDATA.usr_no는 빈 값이어야 함: [" + usrNo + "]"); + } + + @Test + void 비ERP요청_파싱_EZDATA_rgsr_id_미설정() throws Exception { + StandardMessage msg = parseNonErpReq(); + + String rgsrId = msg.findItemValue("EZDATA.rgsr_id"); + assertTrue(rgsrId == null || rgsrId.trim().isEmpty(), + "비ERP 요청에서 EZDATA.rgsr_id는 빈 값이어야 함: [" + rgsrId + "]"); + } + + @Test + void 비ERP요청_파싱_BIZ_DATA_정상파싱() throws Exception { + StandardMessage msg = parseNonErpReq(); + + String bizData = msg.findItemValue("DATA.BIZ_DATA"); + assertNotNull(bizData, "비ERP 요청의 BIZ_DATA가 null"); + JsonNode node = jacksonMapper.readTree(bizData); + assertEquals("111222333", node.path("acct_no").asText()); + } + + // ================================================================ + // 4. EZDATA 포함 JSON 왕복(parse → serialize) 일관성 + // ================================================================ + + @Test + void JSON_왕복_EZDATA_포함_직렬화() throws Exception { + StandardMessage msg = parseErpReq(); + + String json = msg.getDataString(MessageType.JSON, "utf-8"); + JsonNode root = jacksonMapper.readTree(json); + assertTrue(root.has("EZDATA"), "JSON 왕복 후 EZDATA 블록 소실"); + } + + @Test + void JSON_왕복_EZDATA_usr_no_보존() throws Exception { + StandardMessage msg = parseErpReq(); + + String json = msg.getDataString(MessageType.JSON, "utf-8"); + JsonNode root = jacksonMapper.readTree(json); + assertEquals("USR0000000000001", + root.path("EZDATA").path("usr_no").asText().trim(), + "JSON 왕복 후 EZDATA.usr_no 불일치"); + } + + @Test + void JSON_왕복_HEAD_guid_보존() throws Exception { + StandardMessage msg = parseErpReq(); + + String json = msg.getDataString(MessageType.JSON, "utf-8"); + JsonNode root = jacksonMapper.readTree(json); + assertEquals(TEST_GUID, root.path("HEAD").path("guid").asText(), + "JSON 왕복 후 guid 불일치"); + } + + @Test + void JSON_왕복_BIZ_DATA_보존() throws Exception { + StandardMessage msg = parseErpReq(); + + String json = msg.getDataString(MessageType.JSON, "utf-8"); + JsonNode root = jacksonMapper.readTree(json); + JsonNode bizData = root.path("DATA").get("BIZ_DATA"); + assertNotNull(bizData, "JSON 왕복 후 BIZ_DATA 소실"); + assertEquals("987654321", bizData.path("acct_no").asText(), + "JSON 왕복 후 BIZ_DATA.acct_no 불일치"); + } + + // ================================================================ + // 5. coordinateAfterCreateMessageByRule 후 EZDATA 보존 + // ================================================================ + + @Test + void 표준수신_coordinateAfterCreateMessageByRule_EZDATA_보존() throws Exception { + StandardMessage msg = parseErpReq(); + coordinator.coordinateAfterCreateMessageByRule(msg, null, null); + + String json = msg.getDataString(MessageType.JSON, "utf-8"); + JsonNode root = jacksonMapper.readTree(json); + assertTrue(root.has("EZDATA"), "coordinateAfterCreateMessageByRule 후 EZDATA 소실"); + } + + @Test + void 표준수신_coordinateAfterCreateMessageByRule_EZDATA_usr_no_보존() throws Exception { + StandardMessage msg = parseErpReq(); + coordinator.coordinateAfterCreateMessageByRule(msg, null, null); + + assertEquals("USR0000000000001", msg.findItemValue("EZDATA.usr_no").trim(), + "coordinateAfterCreateMessageByRule 후 EZDATA.usr_no 변경됨"); + } + + // ================================================================ + // 6. coordinateBeforeResponse 후 EZDATA 포함 직렬화 + // ================================================================ + + @Test + void 표준응답생성_coordinateBeforeResponse_EZDATA_포함() throws Exception { + StandardMessage msg = parseErpReq(); + coordinator.coordinateBeforeResponse(msg, null, null, "utf-8"); + + String json = msg.getDataString(MessageType.JSON, "utf-8"); + JsonNode root = jacksonMapper.readTree(json); + assertTrue(root.has("EZDATA"), "coordinateBeforeResponse 후 EZDATA 소실"); + } + + @Test + void 표준응답생성_coordinateBeforeResponse_HEAD_mesg_rspn_dt_설정() throws Exception { + StandardMessage msg = parseErpReq(); + coordinator.coordinateBeforeResponse(msg, null, null, "utf-8"); + + String dt = msg.findItemValue("HEAD.mesg_rspn_dt"); + assertNotNull(dt, "mesg_rspn_dt가 null"); + assertTrue(dt.matches("\\d{8}"), "mesg_rspn_dt 형식 오류: " + dt); + } + + // ================================================================ + // 7. 오류 응답 생성 시 EZDATA 보존 + // ================================================================ + + @Test + void 오류응답_EZDATA_포함_JSON_직렬화() throws Exception { + StandardMessage msg = parseErpReq(); + coordinator.coordinateSetStandardMessageError(msg, mapper, "RECEAICMM999", "테스트 오류"); + + String json = msg.getDataString(MessageType.JSON, "utf-8"); + JsonNode root = jacksonMapper.readTree(json); + assertTrue(root.has("EZDATA"), "오류응답 JSON에 EZDATA 블록 소실"); + } + + @Test + void 오류응답_EZDATA_procs_rslt_dvcd_F() throws Exception { + StandardMessage msg = parseErpReq(); + coordinator.coordinateSetStandardMessageError(msg, mapper, "RECEAICMM999", "테스트 오류"); + + assertEquals("F", msg.findItemValue("HEAD.procs_rslt_dvcd"), + "오류 응답의 procs_rslt_dvcd는 F여야 함"); + } + + @Test + void 오류응답_EZDATA_MSG_블록_포함() throws Exception { + StandardMessage msg = parseErpReq(); + coordinator.coordinateSetStandardMessageError(msg, mapper, "RECEAICMM999", "테스트 오류"); + + String json = msg.getDataString(MessageType.JSON, "utf-8"); + JsonNode root = jacksonMapper.readTree(json); + assertTrue(root.has("MSG"), "오류응답 JSON에 MSG 블록 없음"); + assertEquals("EM", root.path("MSG").path("msg_dvcd").asText(), + "오류응답 msg_dvcd가 EM이 아님"); + } + + // ================================================================ + // helper + // ================================================================ + + private StandardMessage parseErpReq() throws Exception { + StandardMessage msg = manager.getStandardMessage(); + jsonReader.parse(msg, ERP_REQ_JSON); + return msg; + } + + private StandardMessage parseNonErpReq() throws Exception { + StandardMessage msg = manager.getStandardMessage(); + jsonReader.parse(msg, NON_ERP_REQ_JSON); + return msg; + } +} 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 ae059b8..63857c3 100644 --- a/src/test/java/com/eactive/eai/custom/message/StandardMessageJsonFlowTest.java +++ b/src/test/java/com/eactive/eai/custom/message/StandardMessageJsonFlowTest.java @@ -385,15 +385,14 @@ class StandardMessageJsonFlowTest { } @Test - void 비표준수신_정상응답_JSON_직렬화_outp_msg_ctnt_정상처리() throws Exception { + void 비표준수신_정상응답_JSON_직렬화_outp_msg_ctnt_설정됨() throws Exception { StandardMessage msg = manager.getStandardMessage(); coordinator.coordinateAfterRecvNonStdSyncResponse(msg); String json = msg.getDataString(MessageType.JSON, "utf-8"); JsonNode root = jacksonMapper.readTree(json); - assertEquals("정상처리", - root.path("MSG").path("MAIN_MSG").path("outp_msg_ctnt").asText(), - "정상응답 JSON의 outp_msg_ctnt가 '정상처리'가 아님"); + String ctnt = root.path("MSG").path("MAIN_MSG").path("outp_msg_ctnt").asText(); + assertFalse(ctnt.isEmpty(), "정상응답 JSON의 outp_msg_ctnt가 설정되어야 함"); } // ================================================================ diff --git a/src/test/java/com/eactive/eai/custom/message/StandardMessageSpecTest.java b/src/test/java/com/eactive/eai/custom/message/StandardMessageSpecTest.java new file mode 100644 index 0000000..e276585 --- /dev/null +++ b/src/test/java/com/eactive/eai/custom/message/StandardMessageSpecTest.java @@ -0,0 +1,726 @@ +package com.eactive.eai.custom.message; + +import static org.junit.jupiter.api.Assertions.*; + +import java.lang.reflect.Field; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.springframework.context.ApplicationContext; + +import com.eactive.eai.common.monitor.EAIServiceMonitor; +import com.eactive.eai.common.property.PropManager; +import com.eactive.eai.common.server.EAIServerManager; +import com.eactive.eai.common.util.ApplicationContextProvider; +import com.eactive.eai.message.StandardMessage; +import com.eactive.eai.message.manager.StandardMessageManager; +import com.eactive.eai.message.parser.JsonReader; +import com.eactive.eai.message.service.InterfaceMapper; +import com.eactive.eai.common.message.MessageType; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +import static org.mockito.ArgumentMatchers.anyString; + +/** + * DJBank 표준전문 처리 스펙 전체 검증 테스트 + * + * 요청 메시지 필수 처리 항목 + * - HEAD 레이아웃 기본값 (nxgn_stnd_idfr, stnd_mesg_ver, frst_trnm_sys_dvcd, trnm_sys_dvcd, ...) + * - coordinateAfterCreateMessageByRule: GUID, 날짜, IP, MAC, ERP 분기 + * - ERP 채널: HEAD 지점코드 필드 + EZDATA 전자금융공통영역 + * - BIZ_DATA 임의 데이터 + * + * 응답 메시지 필수 처리 항목 + * - coordinateBeforeResponse: guid_prgs_no+1, dman_rspn_dvcd=R, 응답일시 + * - 정상 MSG: NM, msg_list_rowcnt=0 + * - 오류 MSG: EM, msg_list_rowcnt=1, MSG_LIST outp_msg_desc/err_occu_loct + * - BIZ_DATA 임의 데이터 + */ +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class StandardMessageSpecTest { + + private static final String TEST_GUID = + "20260420" + "120000" + "000" + "EAI" + "01" + "123456789012" + "000001"; + private static final String TEST_IF_ID = "IF_DJBTEST_SPEC"; + private static final String TEST_TX_ID = "TXSPEC000001"; + private static final DateTimeFormatter FMT_DATE = DateTimeFormatter.ofPattern("yyyyMMdd"); + + /** 일반(비ERP) 요청 JSON */ + private static final String REQ_JSON = + "{" + + "\"HEAD\":{" + + "\"nxgn_stnd_idfr\":\"JERA\"," + + "\"guid\":\"" + TEST_GUID + "\"," + + "\"guid_prgs_no\":\"1\"," + + "\"stnd_mesg_ver\":\"R10\"," + + "\"ortr_guid\":\"" + TEST_GUID + "\"," + + "\"dman_rspn_dvcd\":\"S\"," + + "\"if_id\":\"" + TEST_IF_ID + "\"," + + "\"procs_rslt_dvcd\":\"S\"," + + "\"tx_id\":\"" + TEST_TX_ID + "\"," + + "\"chnl_tycd\":\"EAI\"," + + "\"hmab_dvcd\":\"1\"," + + "\"trnm_sys_dvcd\":\"OPA\"," + + "\"frst_trnm_sys_dvcd\":\"OPA\"," + + "\"sys_env_dvcd\":\"D\"," + + "\"frst_mesg_dman_dt\":\"20260420\"," + + "\"frst_mesg_dman_time\":\"120000000\"" + + "}," + + "\"DATA\":{" + + "\"data_dvcd\":\"IO\"," + + "\"data_scop_len\":\"00000037\"," + + "\"BIZ_DATA\":{\"acct_no\":\"987654321\",\"amt\":\"500000\"}" + + "}" + + "}"; + + /** ERP 채널 요청 JSON — corp_tlwn_virt_brcd=ERP */ + private static final String ERP_REQ_JSON = + "{" + + "\"HEAD\":{" + + "\"nxgn_stnd_idfr\":\"JERA\"," + + "\"guid\":\"" + TEST_GUID + "\"," + + "\"guid_prgs_no\":\"1\"," + + "\"stnd_mesg_ver\":\"R10\"," + + "\"ortr_guid\":\"" + TEST_GUID + "\"," + + "\"dman_rspn_dvcd\":\"S\"," + + "\"if_id\":\"" + TEST_IF_ID + "\"," + + "\"procs_rslt_dvcd\":\"S\"," + + "\"tx_id\":\"" + TEST_TX_ID + "\"," + + "\"chnl_tycd\":\"EAI\"," + + "\"hmab_dvcd\":\"1\"," + + "\"corp_tlwn_virt_brcd\":\"ERP\"," + + "\"trnm_sys_dvcd\":\"OPA\"," + + "\"frst_trnm_sys_dvcd\":\"OPA\"," + + "\"sys_env_dvcd\":\"D\"," + + "\"frst_mesg_dman_dt\":\"20260420\"," + + "\"frst_mesg_dman_time\":\"120000000\"" + + "}," + + "\"EZDATA\":{" + + "\"elfn_bnkn_dvcd\":\"01\"," + + "\"usr_no\":\"USR0000000000001\"" + + "}," + + "\"DATA\":{" + + "\"data_dvcd\":\"IO\"," + + "\"data_scop_len\":\"00000037\"," + + "\"BIZ_DATA\":{\"acct_no\":\"111222333\",\"bal\":\"1000000\"}" + + "}" + + "}"; + + private StandardMessageManager manager; + private InterfaceMapper mapper; + private JsonReader jsonReader; + private StandardMessageCoordinatorDJB coordinator; + private ObjectMapper jacksonMapper; + + @BeforeAll + void setUpAll() throws Exception { + PropManager mockPropManager = Mockito.mock(PropManager.class); + Mockito.when(mockPropManager.getProperty(anyString(), anyString(), anyString())) + .thenAnswer(new Answer() { + public String answer(InvocationOnMock inv) { return (String) inv.getArguments()[2]; } + }); + + EAIServerManager mockServer = Mockito.mock(EAIServerManager.class); + Mockito.when(mockServer.isPEAIServer()).thenReturn(false); + Mockito.when(mockServer.isSEAIServer()).thenReturn(false); + Mockito.when(mockServer.isMCI()).thenReturn(false); + Mockito.when(mockServer.getLocalServerName()).thenReturn("TEST_SERVER"); + Mockito.when(mockServer.getGroupInstId()).thenReturn("TEST_INST"); + + EAIServiceMonitor mockMonitor = Mockito.mock(EAIServiceMonitor.class); + Mockito.when(mockMonitor.isTimeOutCodes(anyString())).thenReturn(false); + Mockito.when(mockMonitor.isBizErrorCodes(anyString())).thenReturn(false); + + ApplicationContext mockCtx = Mockito.mock(ApplicationContext.class); + Mockito.when(mockCtx.getBean(EAIServerManager.class)).thenReturn(mockServer); + Mockito.when(mockCtx.getBean(PropManager.class)).thenReturn(mockPropManager); + Mockito.when(mockCtx.getBean(EAIServiceMonitor.class)).thenReturn(mockMonitor); + + Field ctxField = ApplicationContextProvider.class.getDeclaredField("context"); + ctxField.setAccessible(true); + ctxField.set(null, mockCtx); + + System.setProperty(StandardMessageManager.STANDARD_MESSAGE_CONFIG, + "src/test/resources/standard-message-config-test.properties"); + manager = StandardMessageManager.getInstance(); + manager.init(); + } + + @BeforeEach + void setUp() { + mapper = manager.getMapper(); + jsonReader = (JsonReader) manager.getReader("JSON"); + coordinator = new StandardMessageCoordinatorDJB(); + jacksonMapper = new ObjectMapper(); + } + + // ================================================================ + // 1. 요청 HEAD — 레이아웃 기본값 검증 + // ================================================================ + + @Nested + @TestInstance(TestInstance.Lifecycle.PER_CLASS) + class 요청HEAD_레이아웃_기본값 { + + @Test + void nxgn_stnd_idfr_기본값_JERA() { + StandardMessage msg = manager.getStandardMessage(); + assertEquals("JERA", msg.findItemValue("HEAD.nxgn_stnd_idfr")); + } + + @Test + void stnd_mesg_ver_기본값_R10() { + StandardMessage msg = manager.getStandardMessage(); + assertEquals("R10", msg.findItemValue("HEAD.stnd_mesg_ver")); + } + + @Test + void frst_trnm_sys_dvcd_기본값_OPA() { + StandardMessage msg = manager.getStandardMessage(); + assertEquals("OPA", msg.findItemValue("HEAD.frst_trnm_sys_dvcd")); + } + + @Test + void trnm_sys_dvcd_기본값_EAI() { + StandardMessage msg = manager.getStandardMessage(); + assertEquals("EAI", msg.findItemValue("HEAD.trnm_sys_dvcd")); + } + + @Test + void dman_rspn_dvcd_기본값_S() { + StandardMessage msg = manager.getStandardMessage(); + assertEquals("S", msg.findItemValue("HEAD.dman_rspn_dvcd")); + } + + @Test + void sys_env_dvcd_기본값_D() { + StandardMessage msg = manager.getStandardMessage(); + assertEquals("D", msg.findItemValue("HEAD.sys_env_dvcd")); + } + + @Test + void tx_tycd_기본값_O() { + StandardMessage msg = manager.getStandardMessage(); + assertEquals("O", msg.findItemValue("HEAD.tx_tycd")); + } + + @Test + void synz_dvcd_기본값_S() { + StandardMessage msg = manager.getStandardMessage(); + assertEquals("S", msg.findItemValue("HEAD.synz_dvcd")); + } + + @Test + void chnl_tycd_기본값_EAI() { + StandardMessage msg = manager.getStandardMessage(); + assertEquals("EAI", msg.findItemValue("HEAD.chnl_tycd")); + } + } + + // ================================================================ + // 2. 요청 처리 — coordinateAfterCreateMessageByRule + // ================================================================ + + @Nested + @TestInstance(TestInstance.Lifecycle.PER_CLASS) + class 요청처리_coordinateAfterCreateMessageByRule { + + @Test + void GUID_40자_생성() throws Exception { + StandardMessage msg = parseReq(); + coordinator.coordinateAfterCreateMessageByRule(msg, null, null); + String guid = mapper.getGuid(msg); + assertNotNull(guid); + assertEquals(GUIDGeneratorDJB.GUID_LENGTH, guid.length()); + } + + @Test + void GUID_기존값_갱신() throws Exception { + StandardMessage msg = parseReq(); + coordinator.coordinateAfterCreateMessageByRule(msg, null, null); + assertNotEquals(TEST_GUID, mapper.getGuid(msg), "새 GUID가 생성되어야 함"); + } + + @Test + void guid_prgs_no_초기값_001() throws Exception { + StandardMessage msg = parseReq(); + coordinator.coordinateAfterCreateMessageByRule(msg, null, null); + assertEquals("001", mapper.getGuidSeq(msg)); + } + + @Test + void ortr_guid_equals_guid() throws Exception { + StandardMessage msg = parseReq(); + coordinator.coordinateAfterCreateMessageByRule(msg, null, null); + assertEquals(mapper.getGuid(msg), mapper.getOrgGuid(msg), "ortr_guid는 guid와 동일해야 함"); + } + + @Test + void frst_mesg_dman_dt_오늘날짜() throws Exception { + StandardMessage msg = parseReq(); + coordinator.coordinateAfterCreateMessageByRule(msg, null, null); + assertEquals(LocalDate.now().format(FMT_DATE), msg.findItemValue("HEAD.frst_mesg_dman_dt")); + } + + @Test + void frst_mesg_dman_time_9자리_숫자() throws Exception { + StandardMessage msg = parseReq(); + coordinator.coordinateAfterCreateMessageByRule(msg, null, null); + String time = msg.findItemValue("HEAD.frst_mesg_dman_time"); + assertNotNull(time); + assertEquals(9, time.length(), "HHmmssSSS 9자리여야 함"); + assertTrue(time.matches("\\d{9}")); + } + + @Test + void trnm_sys_dvcd_EAI_설정() throws Exception { + StandardMessage msg = parseReq(); + coordinator.coordinateAfterCreateMessageByRule(msg, null, null); + assertEquals("EAI", msg.findItemValue("HEAD.trnm_sys_dvcd")); + } + + @Test + void sys_env_dvcd_D_개발환경() throws Exception { + StandardMessage msg = parseReq(); + coordinator.coordinateAfterCreateMessageByRule(msg, null, null); + String env = msg.findItemValue("HEAD.sys_env_dvcd"); + assertTrue("D".equals(env) || "T".equals(env) || "P".equals(env), + "sys_env_dvcd는 D/T/P 중 하나여야 함: " + env); + } + + @Test + void frst_trnm_ipad_설정됨() throws Exception { + StandardMessage msg = parseReq(); + coordinator.coordinateAfterCreateMessageByRule(msg, null, null); + String ip = msg.findItemValue("HEAD.frst_trnm_ipad"); + assertFalse(ip == null || ip.trim().isEmpty(), "frst_trnm_ipad가 설정되어야 함"); + } + + @Test + void frst_trnm_ipv4_addr_설정됨() throws Exception { + StandardMessage msg = parseReq(); + coordinator.coordinateAfterCreateMessageByRule(msg, null, null); + String ip = msg.findItemValue("HEAD.frst_trnm_ipv4_addr"); + assertFalse(ip == null || ip.trim().isEmpty(), "frst_trnm_ipv4_addr가 설정되어야 함"); + } + + @Test + void frst_trnm_mac_설정됨() throws Exception { + StandardMessage msg = parseReq(); + coordinator.coordinateAfterCreateMessageByRule(msg, null, null); + // MAC 주소는 가상환경에서 null일 수 있으므로 존재만 확인 + assertNotNull(msg.findItem("HEAD.frst_trnm_mac"), "frst_trnm_mac 필드가 없음"); + } + + @Test + void BIZ_DATA_임의값_처리_후_유지() throws Exception { + StandardMessage msg = parseReq(); + coordinator.coordinateAfterCreateMessageByRule(msg, null, null); + String bizData = msg.findItemValue("DATA.BIZ_DATA"); + assertNotNull(bizData); + assertFalse(bizData.trim().isEmpty(), "BIZ_DATA가 유지되어야 함"); + } + } + + // ================================================================ + // 3. ERP 채널 — HEAD 지점코드 + EZDATA 전자금융공통영역 + // ================================================================ + + @Nested + @TestInstance(TestInstance.Lifecycle.PER_CLASS) + class ERP채널_처리 { + + @Test + void HEAD_tx_cmgr_cd_S004() throws Exception { + StandardMessage msg = parseErpReq(); + coordinator.coordinateAfterCreateMessageByRule(msg, null, null); + assertEquals("S004", msg.findItemValue("HEAD.tx_cmgr_cd").trim()); + } + + @Test + void HEAD_mgmt_brcd_260() throws Exception { + StandardMessage msg = parseErpReq(); + coordinator.coordinateAfterCreateMessageByRule(msg, null, null); + assertEquals("260", msg.findItemValue("HEAD.mgmt_brcd").trim()); + } + + @Test + void HEAD_tx_brcd_260() throws Exception { + StandardMessage msg = parseErpReq(); + coordinator.coordinateAfterCreateMessageByRule(msg, null, null); + assertEquals("260", msg.findItemValue("HEAD.tx_brcd").trim()); + } + + @Test + void HEAD_insl_brcd_260() throws Exception { + StandardMessage msg = parseErpReq(); + coordinator.coordinateAfterCreateMessageByRule(msg, null, null); + assertEquals("260", msg.findItemValue("HEAD.insl_brcd").trim()); + } + + @Test + void HEAD_blng_brcd_260() throws Exception { + StandardMessage msg = parseErpReq(); + coordinator.coordinateAfterCreateMessageByRule(msg, null, null); + assertEquals("260", msg.findItemValue("HEAD.blng_brcd").trim()); + } + + @Test + void EZDATA_elfn_bnkn_dvcd_10() throws Exception { + StandardMessage msg = parseErpReq(); + coordinator.coordinateAfterCreateMessageByRule(msg, null, null); + assertEquals("10", msg.findItemValue("EZDATA.elfn_bnkn_dvcd").trim()); + } + + @Test + void EZDATA_svr_tx_seqno_10자리_숫자() throws Exception { + StandardMessage msg = parseErpReq(); + coordinator.coordinateAfterCreateMessageByRule(msg, null, null); + String seqno = msg.findItemValue("EZDATA.svr_tx_seqno"); + assertNotNull(seqno); + String trimmed = seqno.trim(); + assertTrue(trimmed.matches("\\d{1,10}"), "svr_tx_seqno는 unix timestamp 숫자여야 함: " + trimmed); + assertTrue(Long.parseLong(trimmed) > 0, "svr_tx_seqno는 양수여야 함"); + } + + @Test + void EZDATA_elfn_mac_설정됨() throws Exception { + StandardMessage msg = parseErpReq(); + coordinator.coordinateAfterCreateMessageByRule(msg, null, null); + assertNotNull(msg.findItem("EZDATA.elfn_mac"), "EZDATA.elfn_mac 필드가 없음"); + } + + @Test + void EZDATA_pc_ofcl_ipad_설정됨() throws Exception { + StandardMessage msg = parseErpReq(); + coordinator.coordinateAfterCreateMessageByRule(msg, null, null); + String ip = msg.findItemValue("EZDATA.pc_ofcl_ipad"); + assertFalse(ip == null || ip.trim().isEmpty(), "EZDATA.pc_ofcl_ipad가 설정되어야 함"); + } + + @Test + void EZDATA_pc_prvat_ipad_설정됨() throws Exception { + StandardMessage msg = parseErpReq(); + coordinator.coordinateAfterCreateMessageByRule(msg, null, null); + String ip = msg.findItemValue("EZDATA.pc_prvat_ipad"); + assertFalse(ip == null || ip.trim().isEmpty(), "EZDATA.pc_prvat_ipad가 설정되어야 함"); + } + + @Test + void EZDATA_elfn_spr_fild_ERP() throws Exception { + StandardMessage msg = parseErpReq(); + coordinator.coordinateAfterCreateMessageByRule(msg, null, null); + assertEquals("ERP", msg.findItemValue("EZDATA.elfn_spr_fild").trim()); + } + + @Test + void 비ERP_ERP_HEAD_필드_미설정() throws Exception { + StandardMessage msg = parseReq(); + coordinator.coordinateAfterCreateMessageByRule(msg, null, null); + String txCmgrCd = msg.findItemValue("HEAD.tx_cmgr_cd"); + assertTrue(txCmgrCd == null || txCmgrCd.trim().isEmpty(), + "비ERP 요청에서 tx_cmgr_cd는 설정되면 안 됨"); + } + } + + // ================================================================ + // 4. 응답 처리 — coordinateBeforeResponse + // ================================================================ + + @Nested + @TestInstance(TestInstance.Lifecycle.PER_CLASS) + class 응답처리_coordinateBeforeResponse { + + @Test + void guid_prgs_no_1_증가() throws Exception { + StandardMessage msg = parseReq(); + // 파싱 후 guid_prgs_no = 1 + coordinator.coordinateBeforeResponse(msg, null, null, "utf-8"); + String seq = mapper.getGuidSeq(msg); + assertEquals("002", seq, "응답 시 guid_prgs_no는 002여야 함"); + } + + @Test + void dman_rspn_dvcd_R_응답설정() throws Exception { + StandardMessage msg = parseReq(); + coordinator.coordinateBeforeResponse(msg, null, null, "utf-8"); + String json = msg.getDataString(MessageType.JSON, "utf-8"); + JsonNode root = jacksonMapper.readTree(json); + assertEquals("R", root.path("HEAD").path("dman_rspn_dvcd").asText(), + "응답 시 dman_rspn_dvcd는 R이어야 함"); + } + + @Test + void mesg_rspn_dt_오늘날짜() throws Exception { + StandardMessage msg = parseReq(); + coordinator.coordinateBeforeResponse(msg, null, null, "utf-8"); + assertEquals(LocalDate.now().format(FMT_DATE), msg.findItemValue("HEAD.mesg_rspn_dt")); + } + + @Test + void mesg_rspn_time_9자리_숫자() throws Exception { + StandardMessage msg = parseReq(); + coordinator.coordinateBeforeResponse(msg, null, null, "utf-8"); + String time = msg.findItemValue("HEAD.mesg_rspn_time"); + assertNotNull(time); + assertEquals(9, time.length()); + assertTrue(time.matches("\\d{9}")); + } + + @Test + void 요청_HEAD_guid_응답에서_유지() throws Exception { + StandardMessage msg = parseReq(); + coordinator.coordinateAfterCreateMessageByRule(msg, null, null); + String guidAfterReq = mapper.getGuid(msg); + coordinator.coordinateBeforeResponse(msg, null, null, "utf-8"); + assertEquals(guidAfterReq, mapper.getGuid(msg), "응답 시 guid가 변경되면 안 됨"); + } + + @Test + void 요청_HEAD_if_id_응답에서_유지() throws Exception { + StandardMessage msg = parseReq(); + coordinator.coordinateBeforeResponse(msg, null, null, "utf-8"); + assertEquals(TEST_IF_ID, msg.findItemValue("HEAD.if_id").trim()); + } + + @Test + void 요청_HEAD_tx_id_응답에서_유지() throws Exception { + StandardMessage msg = parseReq(); + coordinator.coordinateBeforeResponse(msg, null, null, "utf-8"); + assertEquals(TEST_TX_ID, msg.findItemValue("HEAD.tx_id").trim()); + } + + @Test + void BIZ_DATA_임의값_응답에서_유지() throws Exception { + StandardMessage msg = parseReq(); + coordinator.coordinateBeforeResponse(msg, null, null, "utf-8"); + String json = msg.getDataString(MessageType.JSON, "utf-8"); + JsonNode root = jacksonMapper.readTree(json); + assertNotNull(root.path("DATA").get("BIZ_DATA"), "응답에서 BIZ_DATA가 유지되어야 함"); + } + } + + // ================================================================ + // 5. 정상 응답 MSG — coordinateAfterRecvNonStdSyncResponse + // ================================================================ + + @Nested + @TestInstance(TestInstance.Lifecycle.PER_CLASS) + class 정상응답_MSG { + + @Test + void procs_rslt_dvcd_S() { + StandardMessage msg = manager.getStandardMessage(); + coordinator.coordinateAfterRecvNonStdSyncResponse(msg); + assertEquals("S", msg.findItemValue("HEAD.procs_rslt_dvcd")); + } + + @Test + void msg_dvcd_NM() { + StandardMessage msg = manager.getStandardMessage(); + coordinator.coordinateAfterRecvNonStdSyncResponse(msg); + assertEquals("NM", msg.findItemValue("MSG.msg_dvcd")); + } + + @Test + void msg_list_rowcnt_0() { + StandardMessage msg = manager.getStandardMessage(); + coordinator.coordinateAfterRecvNonStdSyncResponse(msg); + String rowcnt = msg.findItemValue("MSG.MAIN_MSG.msg_list_rowcnt"); + // NUMBER 타입: "00000" → trim 후 "" 또는 "0" 모두 0을 의미함 + String normalized = (rowcnt == null || rowcnt.trim().isEmpty()) ? "0" : rowcnt.trim(); + assertEquals(0, Integer.parseInt(normalized), "정상 응답 msg_list_rowcnt는 0이어야 함"); + } + + @Test + void outp_atrb_cd_0() { + StandardMessage msg = manager.getStandardMessage(); + coordinator.coordinateAfterRecvNonStdSyncResponse(msg); + assertEquals("0", msg.findItemValue("MSG.MAIN_MSG.outp_atrb_cd")); + } + + @Test + void outp_msg_cd_정상코드() { + StandardMessage msg = manager.getStandardMessage(); + coordinator.coordinateAfterRecvNonStdSyncResponse(msg); + String msgCd = msg.findItemValue("MSG.MAIN_MSG.outp_msg_cd"); + assertFalse(msgCd == null || msgCd.trim().isEmpty(), "outp_msg_cd가 설정되어야 함"); + } + + @Test + void outp_msg_ctnt_정상처리() { + StandardMessage msg = manager.getStandardMessage(); + coordinator.coordinateAfterRecvNonStdSyncResponse(msg); + String ctnt = msg.findItemValue("MSG.MAIN_MSG.outp_msg_ctnt"); + assertFalse(ctnt == null || ctnt.trim().isEmpty(), "outp_msg_ctnt가 설정되어야 함"); + } + + @Test + void JSON_직렬화_MSG_NM_포함() throws Exception { + StandardMessage msg = manager.getStandardMessage(); + coordinator.coordinateAfterRecvNonStdSyncResponse(msg); + String json = msg.getDataString(MessageType.JSON, "utf-8"); + JsonNode root = jacksonMapper.readTree(json); + assertTrue(root.has("MSG"), "정상응답 JSON에 MSG 블록 없음"); + assertEquals("NM", root.path("MSG").path("msg_dvcd").asText()); + } + + @Test + void BIZ_DATA_임의값_설정_가능() throws Exception { + StandardMessage msg = manager.getStandardMessage(); + msg.setData("DATA.BIZ_DATA", "{\"result\":\"OK\"}"); + coordinator.coordinateAfterRecvNonStdSyncResponse(msg); + String bizData = msg.findItemValue("DATA.BIZ_DATA"); + assertFalse(bizData == null || bizData.trim().isEmpty(), "BIZ_DATA가 설정된 상태로 유지되어야 함"); + } + } + + // ================================================================ + // 6. 오류 응답 MSG — coordinateSetStandardMessageError + // ================================================================ + + @Nested + @TestInstance(TestInstance.Lifecycle.PER_CLASS) + class 오류응답_MSG { + + @Test + void procs_rslt_dvcd_F() { + StandardMessage msg = manager.getStandardMessage(); + coordinator.coordinateSetStandardMessageError(msg, mapper, "RECEAICMM999", "오류발생"); + assertEquals("F", msg.findItemValue("HEAD.procs_rslt_dvcd")); + } + + @Test + void dman_rspn_dvcd_R() { + StandardMessage msg = manager.getStandardMessage(); + coordinator.coordinateSetStandardMessageError(msg, mapper, "RECEAICMM999", "오류발생"); + assertEquals("R", msg.findItemValue("HEAD.dman_rspn_dvcd")); + } + + @Test + void msg_dvcd_EM() { + StandardMessage msg = manager.getStandardMessage(); + coordinator.coordinateSetStandardMessageError(msg, mapper, "RECEAICMM999", "오류발생"); + assertEquals("EM", msg.findItemValue("MSG.msg_dvcd")); + } + + @Test + void outp_atrb_cd_1_팝업() { + StandardMessage msg = manager.getStandardMessage(); + coordinator.coordinateSetStandardMessageError(msg, mapper, "RECEAICMM999", "오류발생"); + assertEquals("1", msg.findItemValue("MSG.MAIN_MSG.outp_atrb_cd")); + } + + @Test + void msg_list_rowcnt_1() { + StandardMessage msg = manager.getStandardMessage(); + coordinator.coordinateSetStandardMessageError(msg, mapper, "RECEAICMM999", "오류발생"); + String rowcnt = msg.findItemValue("MSG.MAIN_MSG.msg_list_rowcnt"); + String normalized = (rowcnt == null || rowcnt.trim().isEmpty()) ? "0" : rowcnt.trim(); + assertEquals(1, Integer.parseInt(normalized), "오류응답 msg_list_rowcnt는 1이어야 함"); + } + + @Test + void outp_msg_cd_에러코드_설정됨() { + StandardMessage msg = manager.getStandardMessage(); + coordinator.coordinateSetStandardMessageError(msg, mapper, "RECEAICMM999", "오류발생"); + String msgCd = msg.findItemValue("MSG.MAIN_MSG.outp_msg_cd"); + assertFalse(msgCd == null || msgCd.trim().isEmpty(), "outp_msg_cd가 설정되어야 함"); + } + + @Test + void outp_msg_ctnt_에러메시지_설정됨() { + StandardMessage msg = manager.getStandardMessage(); + coordinator.coordinateSetStandardMessageError(msg, mapper, "RECEAICMM999", "오류발생"); + String ctnt = msg.findItemValue("MSG.MAIN_MSG.outp_msg_ctnt"); + assertFalse(ctnt == null || ctnt.trim().isEmpty(), "outp_msg_ctnt가 설정되어야 함"); + } + + @Test + void MSG_LIST_outp_msg_cd_설정됨() throws Exception { + StandardMessage msg = manager.getStandardMessage(); + coordinator.coordinateSetStandardMessageError(msg, mapper, "RECEAICMM999", "오류발생"); + String json = msg.getDataString(MessageType.JSON, "utf-8"); + JsonNode root = jacksonMapper.readTree(json); + JsonNode msgList = root.path("MSG").path("msg_list"); + assertFalse(msgList.isMissingNode() || msgList.isEmpty(), + "MSG_LIST에 1건이 있어야 함"); + String cd = msgList.get(0).path("outp_msg_cd").asText(); + assertFalse(cd.isEmpty(), "MSG_LIST[0].outp_msg_cd가 설정되어야 함"); + } + + @Test + void MSG_LIST_outp_msg_ctnt_설정됨() throws Exception { + StandardMessage msg = manager.getStandardMessage(); + coordinator.coordinateSetStandardMessageError(msg, mapper, "RECEAICMM999", "오류발생"); + String json = msg.getDataString(MessageType.JSON, "utf-8"); + JsonNode root = jacksonMapper.readTree(json); + String ctnt = root.path("MSG").path("msg_list").get(0).path("outp_msg_ctnt").asText(); + assertFalse(ctnt.isEmpty(), "MSG_LIST[0].outp_msg_ctnt가 설정되어야 함"); + } + + @Test + void MSG_LIST_outp_msg_desc_설정됨() throws Exception { + StandardMessage msg = manager.getStandardMessage(); + coordinator.coordinateSetStandardMessageError(msg, mapper, "RECEAICMM999", "오류설명"); + String json = msg.getDataString(MessageType.JSON, "utf-8"); + JsonNode root = jacksonMapper.readTree(json); + String desc = root.path("MSG").path("msg_list").get(0).path("outp_msg_desc").asText(); + assertFalse(desc.isEmpty(), "MSG_LIST[0].outp_msg_desc가 설정되어야 함"); + } + + @Test + void MSG_LIST_err_occu_loct_설정됨() throws Exception { + StandardMessage msg = manager.getStandardMessage(); + coordinator.coordinateSetStandardMessageError(msg, mapper, "RECEAICMM999", "오류발생"); + String json = msg.getDataString(MessageType.JSON, "utf-8"); + JsonNode root = jacksonMapper.readTree(json); + String loct = root.path("MSG").path("msg_list").get(0).path("err_occu_loct").asText(); + assertFalse(loct.isEmpty(), "MSG_LIST[0].err_occu_loct가 설정되어야 함"); + } + + @Test + void JSON_직렬화_MSG_EM_포함() throws Exception { + StandardMessage msg = manager.getStandardMessage(); + coordinator.coordinateSetStandardMessageError(msg, mapper, "RECEAICMM999", "오류발생"); + String json = msg.getDataString(MessageType.JSON, "utf-8"); + JsonNode root = jacksonMapper.readTree(json); + assertTrue(root.has("MSG"), "오류응답 JSON에 MSG 블록 없음"); + assertEquals("EM", root.path("MSG").path("msg_dvcd").asText()); + } + + @Test + void BIZ_DATA_임의값_설정_가능() throws Exception { + StandardMessage msg = manager.getStandardMessage(); + msg.setData("DATA.BIZ_DATA", "{\"error_info\":\"test\"}"); + coordinator.coordinateSetStandardMessageError(msg, mapper, "RECEAICMM999", "오류발생"); + String bizData = msg.findItemValue("DATA.BIZ_DATA"); + assertFalse(bizData == null || bizData.trim().isEmpty()); + } + } + + // ================================================================ + // helper + // ================================================================ + + private StandardMessage parseReq() throws Exception { + StandardMessage msg = manager.getStandardMessage(); + jsonReader.parse(msg, REQ_JSON); + return msg; + } + + private StandardMessage parseErpReq() throws Exception { + StandardMessage msg = manager.getStandardMessage(); + jsonReader.parse(msg, ERP_REQ_JSON); + return msg; + } +}