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 f310c03..e7759a1 100644 --- a/src/main/java/com/eactive/eai/custom/message/StandardMessageCoordinatorDJB.java +++ b/src/main/java/com/eactive/eai/custom/message/StandardMessageCoordinatorDJB.java @@ -129,6 +129,11 @@ public class StandardMessageCoordinatorDJB extends DefaultStandardMessageCoordin msgItem.setHidden(false); msgItem.setSize(1); } + StandardItem mainMsgItem = standardMessage.findItem(MSG_MAIN_MSG); + if (mainMsgItem != null) { + mainMsgItem.setHidden(false); + mainMsgItem.setSize(1); + } // msg_dvcd = EM (에러메시지) standardMessage.setData(MSG_DVCD, "EM"); @@ -176,6 +181,11 @@ public class StandardMessageCoordinatorDJB extends DefaultStandardMessageCoordin msgItem.setHidden(false); msgItem.setSize(1); } + StandardItem mainMsgItem = responseMessage.findItem(MSG_MAIN_MSG); + if (mainMsgItem != null) { + mainMsgItem.setHidden(false); + mainMsgItem.setSize(1); + } // msg_dvcd = NM (정상메시지) responseMessage.setData(MSG_DVCD, "NM"); diff --git a/src/main/resources/standard-layout-djb.csv b/src/main/resources/standard-layout-djb.csv index e76d376..fd3924e 100644 --- a/src/main/resources/standard-layout-djb.csv +++ b/src/main/resources/standard-layout-djb.csv @@ -5,7 +5,7 @@ # size // 0 : variable, > 0 fixed # fieldType; // 0: ELEMENT, 1: ATTRIBUTE, # length; -# dataType; // 1: String, 2: Number, 10: ZZ String, 11: LL Number(LL prefix auto), 12: LL_NUMBER +# dataType; // 1: String, 2: Number, 11: ZZ String, 12: LL_NUMBER(LL prefix auto) #----------------------------------------------------------------------------- # name ,level ,type ,size ,fieldType ,length ,dataType ,refPath, refValue, value #----------------------------------------------------------------------------- @@ -50,7 +50,6 @@ eai_node_dvcd,2,2,1,1,2,1,,, fep_node_dvcd,2,2,1,1,2,1,,, fep_sesn_id,2,2,1,1,8,1,,, ortr_restr_yn,2,2,1,1,1,1,,, -filler,2,2,1,1,40,1,,, input_mesg_tycd,2,2,1,1,1,1,,, outp_mesg_tycd,2,2,1,1,1,1,,, mesg_cnty_seqno,2,2,1,1,3,2,,, @@ -173,7 +172,3 @@ DATA,1,3,1,1,0,1,,, data_dvcd,2,2,1,1,2,1,,,IO data_scop_len,2,2,1,1,8,2,,, BIZ_DATA,2,9,1,1,0,1,,, -# ============================================================ -# 전문종료부 (@JJ, 3자 고정) -# ============================================================ -MESG_END_IDFR_VAL,1,2,1,1,3,10,,,@JJ diff --git a/src/test/java/com/eactive/eai/custom/message/StandardLayoutDJBTest.java b/src/test/java/com/eactive/eai/custom/message/StandardLayoutDJBTest.java index f0cf81f..eebcb4e 100644 --- a/src/test/java/com/eactive/eai/custom/message/StandardLayoutDJBTest.java +++ b/src/test/java/com/eactive/eai/custom/message/StandardLayoutDJBTest.java @@ -48,11 +48,6 @@ class StandardLayoutDJBTest { assertNotNull(message.findItem("DATA"), "DATA 블록 없음"); } - @Test - void 전문종료부_존재() { - assertNotNull(message.findItem("MESG_END_IDFR_VAL"), "전문종료부 없음"); - } - // ---------------------------------------------------------------- // HEAD 핵심 필드 검증 // ---------------------------------------------------------------- diff --git a/src/test/java/com/eactive/eai/custom/message/StandardMessageCoordinatorDJBTest.java b/src/test/java/com/eactive/eai/custom/message/StandardMessageCoordinatorDJBTest.java new file mode 100644 index 0000000..ea6fc60 --- /dev/null +++ b/src/test/java/com/eactive/eai/custom/message/StandardMessageCoordinatorDJBTest.java @@ -0,0 +1,356 @@ +package com.eactive.eai.custom.message; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.anyString; + +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.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.service.InterfaceMapper; +import com.ext.eai.common.stdmessage.STDMessageKeys; + +/** + * StandardMessageCoordinatorDJB 단위테스트 + * + * DB/Spring 없는 환경에서 EAIServerManager·PropManager를 Mockito로 stub 처리. + * ApplicationContextProvider.context에 mock ApplicationContext를 리플렉션으로 주입. + */ +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class StandardMessageCoordinatorDJBTest { + + private static final DateTimeFormatter FMT_DATE = DateTimeFormatter.ofPattern("yyyyMMdd"); + + private StandardMessageManager manager; + private StandardMessageCoordinatorDJB coordinator; + private InterfaceMapper mapper; + private StandardMessage message; + + @BeforeAll + void setUpAll() throws Exception { + // ① PropManager mock — STDMessageErrorKeys 정적 초기화가 getProperty(group,key,def)를 호출함. + // def(3번째 인자)를 그대로 반환하여 기본 에러코드 상수값이 설정되도록 함. + 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 mock — DB 미연결 환경 대응. + // isPEAIServer/isSEAIServer → false → getSysEnvDvcd()가 "D"(개발)를 반환. + EAIServerManager mockServer = Mockito.mock(EAIServerManager.class); + Mockito.when(mockServer.isPEAIServer()).thenReturn(false); + Mockito.when(mockServer.isSEAIServer()).thenReturn(false); + Mockito.when(mockServer.isMCI()).thenReturn(false); + + // ③ EAIServiceMonitor mock — MessageUtil.getTobeCode()가 사용. + // isTimeOutCodes/isBizErrorCodes → false → SYSTEM_ERROR_CODE 분기로 처리. + EAIServiceMonitor mockMonitor = Mockito.mock(EAIServiceMonitor.class); + Mockito.when(mockMonitor.isTimeOutCodes(anyString())).thenReturn(false); + Mockito.when(mockMonitor.isBizErrorCodes(anyString())).thenReturn(false); + + // ④ ApplicationContext mock → 세 빈을 반환하도록 설정 + 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); + + // ⑤ ApplicationContextProvider.context(static 필드)에 mock 주입 + Field ctxField = ApplicationContextProvider.class.getDeclaredField("context"); + ctxField.setAccessible(true); + ctxField.set(null, mockCtx); + + // ⑥ StandardMessageManager 초기화 (DJB 테스트용 설정) + System.setProperty(StandardMessageManager.STANDARD_MESSAGE_CONFIG, + "src/test/resources/standard-message-config-test.properties"); + manager = StandardMessageManager.getInstance(); + manager.init(); + } + + @BeforeEach + void setUp() { + message = manager.getStandardMessage(); + mapper = manager.getMapper(); + coordinator = new StandardMessageCoordinatorDJB(); + message.setReadPosition(0); + assertNotNull(mapper, "mapper가 null — StandardMessageManager.init() 실패 의심"); + } + + // ================================================================ + // coordinateAfterCreateMessageByRule + // ================================================================ + + @Test + void GUID_40자_생성_설정() { + coordinator.coordinateAfterCreateMessageByRule(message, null, null); + + String guid = mapper.getGuid(message); + assertNotNull(guid, "GUID가 null"); + assertEquals(GUIDGeneratorDJB.GUID_LENGTH, guid.length(), + "GUID 길이가 40자가 아님: " + guid.length()); + } + + @Test + void guid_prgs_no_001_초기화() { + coordinator.coordinateAfterCreateMessageByRule(message, null, null); + + assertEquals("001", mapper.getGuidSeq(message), "guid_prgs_no 초기값은 001이어야 함"); + } + + @Test + void ortr_guid_GUID_동일값_설정() { + coordinator.coordinateAfterCreateMessageByRule(message, null, null); + + String guid = mapper.getGuid(message); + String orgGuid = mapper.getOrgGuid(message); + assertEquals(guid, orgGuid, "원거래GUID는 GUID와 동일해야 함"); + } + + @Test + void frst_mesg_dman_dt_오늘날짜_설정() { + coordinator.coordinateAfterCreateMessageByRule(message, null, null); + + String expected = LocalDate.now().format(FMT_DATE); + assertEquals(expected, message.findItemValue("HEAD.frst_mesg_dman_dt"), + "frst_mesg_dman_dt가 오늘 날짜가 아님"); + } + + @Test + void frst_mesg_dman_time_설정_9자리() { + coordinator.coordinateAfterCreateMessageByRule(message, null, null); + + String time = message.findItemValue("HEAD.frst_mesg_dman_time"); + assertNotNull(time, "frst_mesg_dman_time이 null"); + assertEquals(9, time.length(), "frst_mesg_dman_time은 HHmmssSSS(9자)여야 함: " + time); + assertTrue(time.matches("\\d{9}"), "frst_mesg_dman_time 형식 오류: " + time); + } + + @Test + void trnm_sys_dvcd_EAI_설정() { + coordinator.coordinateAfterCreateMessageByRule(message, null, null); + + assertEquals("EAI", message.findItemValue("HEAD.trnm_sys_dvcd")); + } + + @Test + void sys_env_dvcd_D_설정_mock_isPEAIServer_false() { + // mock: isPEAIServer=false, isSEAIServer=false → "D"(개발) 반환 + coordinator.coordinateAfterCreateMessageByRule(message, null, null); + + assertEquals("D", message.findItemValue("HEAD.sys_env_dvcd"), + "mock 환경에서 sys_env_dvcd는 D여야 함"); + } + + @Test + void frst_trnm_ipad_설정() { + coordinator.coordinateAfterCreateMessageByRule(message, null, null); + + String ip = message.findItemValue("HEAD.frst_trnm_ipad"); + assertNotNull(ip, "frst_trnm_ipad가 null"); + assertFalse(ip.trim().isEmpty(), "frst_trnm_ipad가 공백"); + } + + // ================================================================ + // coordinateBeforeStandardMessageSend + // ================================================================ + + @Test + void data_scop_len_정수값으로_설정() { + coordinator.coordinateBeforeStandardMessageSend(message, "FLAT", "euc-kr"); + + String len = message.findItemValue("DATA.data_scop_len"); + assertNotNull(len, "DATA.data_scop_len이 null"); + // NUMBER 타입은 선행 0을 모두 제거: "00000000"(0 바이트) → "" 반환됨 + // 빈 문자열은 0 바이트를 의미하므로 0 이상 조건 통과 + int dataLen = (len.trim().isEmpty()) ? 0 : Integer.parseInt(len.trim()); + assertTrue(dataLen >= 0, "data_scop_len은 0 이상이어야 함: '" + len + "'"); + } + + @Test + void data_scop_len_BIZ_DATA_비어있을때_0() { + // BIZ_DATA에 데이터 미설정 → 0 바이트 → "00000000" → NUMBER 타입이 "" 반환 + coordinator.coordinateBeforeStandardMessageSend(message, "JSON", "utf-8"); + + String len = message.findItemValue("DATA.data_scop_len"); + assertNotNull(len, "DATA.data_scop_len이 null"); + int dataLen = (len.trim().isEmpty()) ? 0 : Integer.parseInt(len.trim()); + assertEquals(0, dataLen, "BIZ_DATA 미설정 시 data_scop_len은 0이어야 함"); + } + + // ================================================================ + // coordinateBeforeResponse + // ================================================================ + + @Test + void mesg_rspn_dt_오늘날짜_설정() { + coordinator.coordinateBeforeResponse(message, null, null, "euc-kr"); + + String expected = LocalDate.now().format(FMT_DATE); + assertEquals(expected, message.findItemValue("HEAD.mesg_rspn_dt"), + "mesg_rspn_dt가 오늘 날짜가 아님"); + } + + @Test + void mesg_rspn_time_9자리_설정() { + coordinator.coordinateBeforeResponse(message, null, null, "euc-kr"); + + String time = message.findItemValue("HEAD.mesg_rspn_time"); + assertNotNull(time, "mesg_rspn_time이 null"); + assertEquals(9, time.length(), "mesg_rspn_time은 HHmmssSSS(9자)여야 함: " + time); + assertTrue(time.matches("\\d{9}"), "mesg_rspn_time 형식 오류: " + time); + } + + @Test + void coordinateBeforeResponse_data_scop_len_설정() { + coordinator.coordinateBeforeResponse(message, null, null, "euc-kr"); + + String len = message.findItemValue("DATA.data_scop_len"); + assertNotNull(len); + int dataLen = (len.trim().isEmpty()) ? 0 : Integer.parseInt(len.trim()); + assertTrue(dataLen >= 0); + } + + // ================================================================ + // coordinateSetStandardMessageError + // ================================================================ + + @Test + void 에러시_procs_rslt_dvcd_F_설정() { + coordinator.coordinateSetStandardMessageError(message, mapper, "RECEAICMM999", "테스트 오류"); + + assertEquals("F", message.findItemValue("HEAD.procs_rslt_dvcd"), + "오류 시 procs_rslt_dvcd는 F여야 함"); + } + + @Test + void 에러시_dman_rspn_dvcd_R_설정() { + coordinator.coordinateSetStandardMessageError(message, mapper, "RECEAICMM999", "테스트 오류"); + + assertEquals(STDMessageKeys.SEND_RECV_CD_RECV, + message.findItemValue("HEAD.dman_rspn_dvcd"), + "오류 시 dman_rspn_dvcd는 R이어야 함"); + } + + @Test + void 에러시_MSG_msg_dvcd_EM_설정() { + coordinator.coordinateSetStandardMessageError(message, mapper, "RECEAICMM999", "테스트 오류"); + + assertEquals("EM", message.findItemValue("MSG.msg_dvcd"), + "오류 시 msg_dvcd는 EM이어야 함"); + } + + @Test + void 에러시_outp_atrb_cd_1_설정() { + coordinator.coordinateSetStandardMessageError(message, mapper, "RECEAICMM999", "테스트 오류"); + + assertEquals("1", message.findItemValue("MSG.MAIN_MSG.outp_atrb_cd"), + "오류 시 outp_atrb_cd는 1(팝업)이어야 함"); + } + + @Test + void 에러시_outp_msg_cd_설정됨() { + coordinator.coordinateSetStandardMessageError(message, mapper, "RECEAICMM999", "테스트 오류"); + + // mock PropManager가 defaultValue("900400")를 반환 → SYSTEM_ERROR_CODE_VALUE="900400" + String errCode = message.findItemValue("MSG.MAIN_MSG.outp_msg_cd"); + assertNotNull(errCode, "outp_msg_cd가 null"); + assertFalse(errCode.trim().isEmpty(), "outp_msg_cd가 공백"); + } + + @Test + void 에러시_outp_msg_ctnt_설정됨() { + coordinator.coordinateSetStandardMessageError(message, mapper, "RECEAICMM999", "테스트 오류"); + + String errMsg = message.findItemValue("MSG.MAIN_MSG.outp_msg_ctnt"); + assertNotNull(errMsg, "outp_msg_ctnt가 null"); + assertFalse(errMsg.trim().isEmpty(), "outp_msg_ctnt가 공백"); + } + + @Test + void 에러시_반환값_false() { + boolean result = coordinator.coordinateSetStandardMessageError(message, mapper, "RECEAICMM999", "테스트 오류"); + + assertFalse(result, "coordinateSetStandardMessageError는 false를 반환해야 함 (BIZ_DATA null 처리)"); + } + + @Test + void 에러시_msg_scop_len_정수값으로_설정() { + coordinator.coordinateSetStandardMessageError(message, mapper, "RECEAICMM999", "테스트 오류"); + + // NUMBER 타입이므로 선행 0 제거됨 — 숫자 파싱 가능 여부와 0 이상인지 검증 + String scopLen = message.findItemValue("MSG.msg_scop_len"); + assertNotNull(scopLen, "msg_scop_len이 null"); + assertTrue(Integer.parseInt(scopLen.trim()) >= 0, "msg_scop_len은 0 이상이어야 함: " + scopLen); + } + + // ================================================================ + // coordinateAfterRecvNonStdSyncResponse + // ================================================================ + + @Test + void 정상수신시_procs_rslt_dvcd_S_설정() { + coordinator.coordinateAfterRecvNonStdSyncResponse(message); + + assertEquals("S", message.findItemValue("HEAD.procs_rslt_dvcd"), + "정상 수신 시 procs_rslt_dvcd는 S여야 함"); + } + + @Test + void 정상수신시_msg_dvcd_NM_설정() { + coordinator.coordinateAfterRecvNonStdSyncResponse(message); + + assertEquals("NM", message.findItemValue("MSG.msg_dvcd"), + "정상 수신 시 msg_dvcd는 NM이어야 함"); + } + + @Test + void 정상수신시_outp_msg_cd_000000000000_설정() { + coordinator.coordinateAfterRecvNonStdSyncResponse(message); + + assertEquals("000000000000", message.findItemValue("MSG.MAIN_MSG.outp_msg_cd")); + } + + @Test + void 정상수신시_outp_msg_ctnt_정상처리_설정() { + coordinator.coordinateAfterRecvNonStdSyncResponse(message); + + assertEquals("정상처리", message.findItemValue("MSG.MAIN_MSG.outp_msg_ctnt")); + } + + @Test + void 정상수신시_msg_list_1건_구성() { + 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건이어야 함"); + } + + @Test + void 정상수신시_msg_scop_len_정수값으로_설정() { + coordinator.coordinateAfterRecvNonStdSyncResponse(message); + + // NUMBER 타입이므로 선행 0 제거됨 — 숫자 파싱 가능 여부와 0 이상인지 검증 + String scopLen = message.findItemValue("MSG.msg_scop_len"); + assertNotNull(scopLen, "msg_scop_len이 null"); + assertTrue(Integer.parseInt(scopLen.trim()) >= 0, "msg_scop_len은 0 이상이어야 함: " + scopLen); + } +} diff --git a/src/test/java/com/eactive/eai/custom/message/StandardMessageJsonFlowTest.java b/src/test/java/com/eactive/eai/custom/message/StandardMessageJsonFlowTest.java new file mode 100644 index 0000000..ae059b8 --- /dev/null +++ b/src/test/java/com/eactive/eai/custom/message/StandardMessageJsonFlowTest.java @@ -0,0 +1,509 @@ +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.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 표준전문 JSON 처리 흐름 통합 테스트 + * + * 검증 시나리오: + * 1. JSON → StandardMessage 파싱 (표준 요청 수신) + * 2. JSON → StandardMessage 파싱 (표준 응답 수신 — MSG 포함) + * 3. 표준 메시지 요청 수신 처리 (coordinateAfterCreateMessageByRule) + * 4. 표준 응답 생성 (coordinateBeforeResponse → JSON 직렬화) + * 5. 비표준 수신 → 정상 응답 표준 생성 (coordinateAfterRecvNonStdSyncResponse → JSON) + * 6. 오류 응답 표준 생성 (coordinateSetStandardMessageError → JSON) + * 7. JSON 왕복(parse → serialize) 일관성 + */ +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class StandardMessageJsonFlowTest { + + // ---------------------------------------------------------------- + // 테스트용 상수 (GUID 40자 고정) + // ---------------------------------------------------------------- + private static final String TEST_GUID = + "20260420" + "120000" + "000" + "EAI" + "01" + "123456789012" + "000001"; + // 8+6+3+3+2+12+6 = 40자 + + private static final String TEST_IF_ID = "IF_DJBTEST_001"; + private static final String TEST_TX_ID = "TXTEST000001"; + + private static final DateTimeFormatter FMT_DATE = DateTimeFormatter.ofPattern("yyyyMMdd"); + + /** + * 표준 요청 JSON (dman_rspn_dvcd=S → MSG 블록 미포함) + * BIZ_DATA는 JSON 오브젝트 — 파싱 후 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\":\"37\"," + + "\"BIZ_DATA\":{\"acct_no\":\"123456789\",\"amt\":\"100000\"}" + + "}" + + "}"; + + /** + * 표준 응답 JSON (dman_rspn_dvcd=R → MSG 블록 포함, 정상) + */ + private static final String RSP_JSON_OK = + "{" + + "\"HEAD\":{" + + "\"nxgn_stnd_idfr\":\"JERA\"," + + "\"guid\":\"" + TEST_GUID + "\"," + + "\"guid_prgs_no\":\"2\"," + + "\"stnd_mesg_ver\":\"R10\"," + + "\"ortr_guid\":\"" + TEST_GUID + "\"," + + "\"dman_rspn_dvcd\":\"R\"," + + "\"if_id\":\"" + TEST_IF_ID + "\"," + + "\"procs_rslt_dvcd\":\"S\"," + + "\"tx_id\":\"" + TEST_TX_ID + "\"," + + "\"chnl_tycd\":\"EAI\"," + + "\"hmab_dvcd\":\"1\"," + + "\"trnm_sys_dvcd\":\"EAI\"," + + "\"sys_env_dvcd\":\"D\"," + + "\"frst_mesg_dman_dt\":\"20260420\"," + + "\"frst_mesg_dman_time\":\"120000000\"," + + "\"mesg_rspn_dt\":\"20260420\"," + + "\"mesg_rspn_time\":\"120100000\"" + + "}," + + "\"MSG\":{" + + "\"msg_dvcd\":\"NM\"," + + "\"msg_scop_len\":\"0\"," + + "\"MAIN_MSG\":{" + + "\"outp_atrb_cd\":\"0\"," + + "\"outp_msg_cd\":\"000000000000\"," + + "\"outp_msg_ctnt\":\"정상처리\"," + + "\"msg_list_rowcnt\":\"1\"" + + "}" + + "}," + + "\"DATA\":{" + + "\"data_dvcd\":\"IO\"," + + "\"data_scop_len\":\"37\"," + + "\"BIZ_DATA\":{\"acct_no\":\"123456789\",\"bal\":\"900000\"}" + + "}" + + "}"; + + 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. 표준 요청 JSON 파싱 + // ================================================================ + + @Test + void 요청JSON_파싱_HEAD_guid_40자_정확() throws Exception { + StandardMessage msg = parseReq(); + + String guid = mapper.getGuid(msg); + assertEquals(TEST_GUID, guid, "파싱된 guid가 입력 JSON과 다름"); + assertEquals(GUIDGeneratorDJB.GUID_LENGTH, guid.length(), "GUID 길이가 40자가 아님"); + } + + @Test + void 요청JSON_파싱_HEAD_if_id_매핑() throws Exception { + StandardMessage msg = parseReq(); + + assertEquals(TEST_IF_ID, msg.findItemValue("HEAD.if_id").trim()); + } + + @Test + void 요청JSON_파싱_HEAD_dman_rspn_dvcd_S() throws Exception { + StandardMessage msg = parseReq(); + + assertEquals("S", msg.findItemValue("HEAD.dman_rspn_dvcd"), + "요청 방향은 dman_rspn_dvcd = S 여야 함"); + } + + @Test + void 요청JSON_파싱_DATA_BIZ_DATA_JSON_오브젝트_저장() throws Exception { + StandardMessage msg = parseReq(); + + String bizData = msg.findItemValue("DATA.BIZ_DATA"); + assertNotNull(bizData, "BIZ_DATA가 null"); + assertFalse(bizData.trim().isEmpty(), "BIZ_DATA가 공백"); + // JSON 오브젝트로 파싱 가능한지 검증 + JsonNode node = jacksonMapper.readTree(bizData); + assertEquals("123456789", node.path("acct_no").asText(), "BIZ_DATA.acct_no 불일치"); + } + + @Test + void 요청JSON_파싱_MSG_블록_사이즈_0_숨김() throws Exception { + StandardMessage msg = parseReq(); + + // dman_rspn_dvcd=S → MSG 블록은 조건 미충족 → size=0(숨김) + // JSON 직렬화 결과에 MSG 키가 없어야 함 + String json = msg.toJson(); + JsonNode root = jacksonMapper.readTree(json); + assertFalse(root.has("MSG"), "요청 전문 JSON에 MSG 블록이 포함되면 안 됨"); + } + + // ================================================================ + // 2. 표준 응답 JSON 파싱 (MSG 포함) + // ================================================================ + + @Test + void 응답JSON_파싱_MSG_msg_dvcd_NM() throws Exception { + StandardMessage msg = parseRsp(); + + assertEquals("NM", msg.findItemValue("MSG.msg_dvcd"), + "정상 응답의 msg_dvcd는 NM이어야 함"); + } + + @Test + void 응답JSON_파싱_MSG_MAIN_MSG_outp_msg_ctnt_정상처리() throws Exception { + StandardMessage msg = parseRsp(); + + assertEquals("정상처리", msg.findItemValue("MSG.MAIN_MSG.outp_msg_ctnt"), + "정상 응답의 outp_msg_ctnt는 '정상처리'여야 함"); + } + + @Test + void 응답JSON_파싱_HEAD_procs_rslt_dvcd_S() throws Exception { + StandardMessage msg = parseRsp(); + + assertEquals("S", msg.findItemValue("HEAD.procs_rslt_dvcd"), + "정상 응답의 procs_rslt_dvcd는 S여야 함"); + } + + // ================================================================ + // 3. 표준 메시지 수신 처리 — coordinateAfterCreateMessageByRule + // ================================================================ + + @Test + void 표준수신_coordinateAfterCreateMessageByRule_새GUID_40자_재설정() throws Exception { + StandardMessage msg = parseReq(); + coordinator.coordinateAfterCreateMessageByRule(msg, null, null); + + String newGuid = mapper.getGuid(msg); + assertNotNull(newGuid, "coordinateAfterCreateMessageByRule 후 GUID null"); + assertEquals(GUIDGeneratorDJB.GUID_LENGTH, newGuid.length(), "재설정된 GUID가 40자가 아님"); + // Coordinator는 새 GUID를 생성하므로 원본과 달라야 함 + assertNotEquals(TEST_GUID, newGuid, "Coordinator가 기존 GUID를 그대로 사용함"); + } + + @Test + void 표준수신_coordinateAfterCreateMessageByRule_frst_mesg_dman_dt_오늘날짜() throws Exception { + StandardMessage msg = parseReq(); + coordinator.coordinateAfterCreateMessageByRule(msg, null, null); + + String today = LocalDate.now().format(FMT_DATE); + assertEquals(today, msg.findItemValue("HEAD.frst_mesg_dman_dt"), + "frst_mesg_dman_dt는 오늘 날짜여야 함"); + } + + @Test + void 표준수신_coordinateAfterCreateMessageByRule_trnm_sys_dvcd_EAI() throws Exception { + StandardMessage msg = parseReq(); + coordinator.coordinateAfterCreateMessageByRule(msg, null, null); + + assertEquals("EAI", msg.findItemValue("HEAD.trnm_sys_dvcd"), + "coordinateAfterCreateMessageByRule 후 trnm_sys_dvcd는 EAI여야 함"); + } + + // ================================================================ + // 4. 표준 응답 생성 — coordinateBeforeResponse + JSON 직렬화 + // ================================================================ + + @Test + void 표준응답생성_coordinateBeforeResponse_mesg_rspn_dt_오늘날짜() throws Exception { + StandardMessage msg = parseReq(); + coordinator.coordinateBeforeResponse(msg, null, null, "utf-8"); + + String today = LocalDate.now().format(FMT_DATE); + assertEquals(today, msg.findItemValue("HEAD.mesg_rspn_dt"), + "mesg_rspn_dt는 오늘 날짜여야 함"); + } + + @Test + void 표준응답생성_coordinateBeforeResponse_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, "mesg_rspn_time이 null"); + assertEquals(9, time.length(), "mesg_rspn_time은 HHmmssSSS(9자)여야 함: " + time); + assertTrue(time.matches("\\d{9}"), "mesg_rspn_time 형식 오류"); + } + + @Test + void 표준응답생성_JSON_직렬화_HEAD_포함() throws Exception { + StandardMessage msg = parseReq(); + coordinator.coordinateBeforeResponse(msg, null, null, "utf-8"); + + String json = msg.getDataString(MessageType.JSON, "utf-8"); + assertNotNull(json, "직렬화 JSON이 null"); + JsonNode root = jacksonMapper.readTree(json); + assertTrue(root.has("HEAD"), "직렬화 JSON에 HEAD 블록 없음"); + } + + @Test + void 표준응답생성_JSON_직렬화_guid_값_보존() throws Exception { + StandardMessage msg = parseReq(); + // Coordinator 호출 없이 파싱된 상태 그대로 직렬화 + 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 = parseReq(); + coordinator.coordinateBeforeResponse(msg, null, null, "utf-8"); + + String json = msg.getDataString(MessageType.JSON, "utf-8"); + JsonNode root = jacksonMapper.readTree(json); + // BIZ_DATA는 JSON 문자열 또는 JSON 오브젝트로 들어갈 수 있음 + assertNotNull(root.path("DATA").get("BIZ_DATA"), + "직렬화 JSON에 DATA.BIZ_DATA 없음"); + } + + // ================================================================ + // 5. 비표준 수신 → 표준 정상 응답 생성 + // (외부 시스템에서 온 비표준 응답을 표준 성공 응답으로 변환) + // ================================================================ + + @Test + void 비표준수신_coordinateAfterRecvNonStdSyncResponse_procs_rslt_dvcd_S() throws Exception { + StandardMessage msg = manager.getStandardMessage(); + coordinator.coordinateAfterRecvNonStdSyncResponse(msg); + + assertEquals("S", msg.findItemValue("HEAD.procs_rslt_dvcd"), + "비표준 정상수신 후 procs_rslt_dvcd는 S여야 함"); + } + + @Test + void 비표준수신_coordinateAfterRecvNonStdSyncResponse_MSG_msg_dvcd_NM() throws Exception { + StandardMessage msg = manager.getStandardMessage(); + coordinator.coordinateAfterRecvNonStdSyncResponse(msg); + + assertEquals("NM", msg.findItemValue("MSG.msg_dvcd"), + "비표준 정상수신 후 msg_dvcd는 NM이어야 함"); + } + + @Test + void 비표준수신_정상응답_JSON_직렬화_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(), + "정상응답 JSON의 msg_dvcd가 NM이 아님"); + } + + @Test + 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가 '정상처리'가 아님"); + } + + // ================================================================ + // 6. 오류 응답 표준 생성 — coordinateSetStandardMessageError + JSON 직렬화 + // ================================================================ + + @Test + void 오류응답_coordinateSetStandardMessageError_procs_rslt_dvcd_F() throws Exception { + StandardMessage msg = manager.getStandardMessage(); + coordinator.coordinateSetStandardMessageError(msg, mapper, "RECEAICMM999", "테스트 오류"); + + assertEquals("F", msg.findItemValue("HEAD.procs_rslt_dvcd"), + "오류 응답의 procs_rslt_dvcd는 F여야 함"); + } + + @Test + void 오류응답_JSON_직렬화_MSG_블록_포함() 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 블록이 없음"); + } + + @Test + void 오류응답_JSON_직렬화_msg_dvcd_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); + assertEquals("EM", root.path("MSG").path("msg_dvcd").asText(), + "오류응답 JSON의 msg_dvcd가 EM이 아님"); + } + + @Test + void 오류응답_JSON_직렬화_outp_atrb_cd_1_팝업() throws Exception { + StandardMessage msg = manager.getStandardMessage(); + coordinator.coordinateSetStandardMessageError(msg, mapper, "RECEAICMM999", "테스트 오류"); + + String json = msg.getDataString(MessageType.JSON, "utf-8"); + JsonNode root = jacksonMapper.readTree(json); + assertEquals("1", + root.path("MSG").path("MAIN_MSG").path("outp_atrb_cd").asText(), + "오류응답 JSON의 outp_atrb_cd가 1(팝업)이 아님"); + } + + @Test + void 오류응답_JSON_직렬화_procs_rslt_dvcd_F_HEAD에_포함() throws Exception { + StandardMessage msg = manager.getStandardMessage(); + coordinator.coordinateSetStandardMessageError(msg, mapper, "RECEAICMM999", "테스트 오류"); + + String json = msg.getDataString(MessageType.JSON, "utf-8"); + JsonNode root = jacksonMapper.readTree(json); + assertEquals("F", root.path("HEAD").path("procs_rslt_dvcd").asText(), + "오류응답 JSON의 HEAD.procs_rslt_dvcd가 F가 아님"); + } + + // ================================================================ + // 7. JSON 왕복 일관성 (parse → serialize) + // ================================================================ + + @Test + void JSON_왕복_파싱후_직렬화_guid_일치() throws Exception { + StandardMessage msg = parseReq(); + + 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_왕복_파싱후_직렬화_if_id_일치() throws Exception { + StandardMessage msg = parseReq(); + + String json = msg.getDataString(MessageType.JSON, "utf-8"); + JsonNode root = jacksonMapper.readTree(json); + + assertEquals(TEST_IF_ID, root.path("HEAD").path("if_id").asText().trim(), + "JSON 왕복 후 if_id 불일치"); + } + + @Test + void JSON_왕복_응답JSON_파싱후_직렬화_MSG_보존() throws Exception { + StandardMessage msg = parseRsp(); + + 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(), + "응답 JSON 왕복 후 msg_dvcd 불일치"); + } + + // ================================================================ + // helper + // ================================================================ + + private StandardMessage parseReq() throws Exception { + StandardMessage msg = manager.getStandardMessage(); + jsonReader.parse(msg, REQ_JSON); + return msg; + } + + private StandardMessage parseRsp() throws Exception { + StandardMessage msg = manager.getStandardMessage(); + jsonReader.parse(msg, RSP_JSON_OK); + return msg; + } +} diff --git a/src/test/java/com/eactive/eai/inbound/processor/RequestProcessorStdMsgTest.java b/src/test/java/com/eactive/eai/inbound/processor/RequestProcessorStdMsgTest.java new file mode 100644 index 0000000..42578de --- /dev/null +++ b/src/test/java/com/eactive/eai/inbound/processor/RequestProcessorStdMsgTest.java @@ -0,0 +1,291 @@ +package com.eactive.eai.inbound.processor; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.anyString; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Properties; + +import org.junit.jupiter.api.BeforeAll; +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.adapter.Keys; +import com.eactive.eai.common.monitor.EAIServiceMonitor; +import com.eactive.eai.common.property.PropManager; +import com.eactive.eai.common.restrict.RestrictInfoVO; +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.service.InterfaceMapper; +import com.ext.eai.common.stdmessage.STDMessageKeys; + +/** + * RequestProcessor 단위테스트 — StandardMessageManager/Coordinator 연동 검증 + * + * 전략: + * - DB/Spring 없는 환경에서 Mockito + 리플렉션으로 ApplicationContextProvider 주입 + * - RequestProcessor 클래스 로드 전(static 블록 실행 전)에 mock ApplicationContext를 주입 + * - private 메서드(getInboundErrorResponseForStandard, getInboundErrorResponse)는 + * 리플렉션으로 직접 호출하여 Coordinator 연동 결과를 검증 + */ +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class RequestProcessorStdMsgTest { + + private StandardMessageManager stdMsgManager; + + @BeforeAll + void setUpAll() throws Exception { + // ① PropManager mock — STDMessageErrorKeys 정적 초기화용 (def 반환) + 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 mock — static 블록의 getLocalServerName/getGroupInstId 대응 + 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 mock — MessageUtil.getTobeCode() 사용 + EAIServiceMonitor mockMonitor = Mockito.mock(EAIServiceMonitor.class); + Mockito.when(mockMonitor.isTimeOutCodes(anyString())).thenReturn(false); + Mockito.when(mockMonitor.isBizErrorCodes(anyString())).thenReturn(false); + + // ④ ApplicationContext mock + 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); + + // ⑤ ApplicationContextProvider.context(static 필드)에 mock 주입 + Field ctxField = ApplicationContextProvider.class.getDeclaredField("context"); + ctxField.setAccessible(true); + ctxField.set(null, mockCtx); + + // ⑥ StandardMessageManager 초기화 + System.setProperty(StandardMessageManager.STANDARD_MESSAGE_CONFIG, + "src/test/resources/standard-message-config-test.properties"); + stdMsgManager = StandardMessageManager.getInstance(); + stdMsgManager.init(); + + // ⑦ RequestProcessor 클래스 강제 로드 — mock 주입 완료 후 static 블록 안전 실행 + Class.forName("com.eactive.eai.inbound.processor.RequestProcessor"); + } + + // ================================================================ + // getInboundErrorResponseForStandard (private) — Coordinator 연동 검증 + // + // 이 메서드 내부 흐름: + // mapper.nextGuidSeq(msg) + // mapper.setSendRecvDivision(msg, R) + // standardManager.getMessageCoordinator().coordinateSetStandardMessageError(...) + // standardMessage.getDataString(messageType, charset) + // ================================================================ + + @Test + void getInboundErrorResponseForStandard_procs_rslt_dvcd_F_설정() throws Exception { + StandardMessage msg = stdMsgManager.getStandardMessage(); + InterfaceMapper mapper = stdMsgManager.getMapper(); + + callGetInboundErrorResponseForStandard(msg, mapper, "RECEAICMM999", "테스트 오류", "euc-kr", "FLAT"); + + assertEquals("F", msg.findItemValue("HEAD.procs_rslt_dvcd"), + "오류 응답 생성 시 procs_rslt_dvcd는 F여야 함"); + } + + @Test + void getInboundErrorResponseForStandard_dman_rspn_dvcd_R_설정() throws Exception { + StandardMessage msg = stdMsgManager.getStandardMessage(); + InterfaceMapper mapper = stdMsgManager.getMapper(); + + callGetInboundErrorResponseForStandard(msg, mapper, "RECEAICMM999", "테스트 오류", "euc-kr", "FLAT"); + + assertEquals(STDMessageKeys.SEND_RECV_CD_RECV, msg.findItemValue("HEAD.dman_rspn_dvcd"), + "오류 응답 생성 시 dman_rspn_dvcd는 R이어야 함"); + } + + @Test + void getInboundErrorResponseForStandard_MSG_msg_dvcd_EM_설정() throws Exception { + StandardMessage msg = stdMsgManager.getStandardMessage(); + InterfaceMapper mapper = stdMsgManager.getMapper(); + + callGetInboundErrorResponseForStandard(msg, mapper, "RECEAICMM999", "테스트 오류", "euc-kr", "FLAT"); + + assertEquals("EM", msg.findItemValue("MSG.msg_dvcd"), + "오류 응답 생성 시 msg_dvcd는 EM이어야 함"); + } + + @Test + void getInboundErrorResponseForStandard_outp_atrb_cd_1_설정() throws Exception { + StandardMessage msg = stdMsgManager.getStandardMessage(); + InterfaceMapper mapper = stdMsgManager.getMapper(); + + callGetInboundErrorResponseForStandard(msg, mapper, "RECEAICMM999", "테스트 오류", "euc-kr", "FLAT"); + + assertEquals("1", msg.findItemValue("MSG.MAIN_MSG.outp_atrb_cd"), + "오류 시 outp_atrb_cd는 1(팝업)이어야 함"); + } + + @Test + void getInboundErrorResponseForStandard_outp_msg_cd_설정됨() throws Exception { + StandardMessage msg = stdMsgManager.getStandardMessage(); + InterfaceMapper mapper = stdMsgManager.getMapper(); + + callGetInboundErrorResponseForStandard(msg, mapper, "RECEAICMM999", "테스트 오류", "euc-kr", "FLAT"); + + String errCode = msg.findItemValue("MSG.MAIN_MSG.outp_msg_cd"); + assertNotNull(errCode, "outp_msg_cd가 null"); + assertFalse(errCode.trim().isEmpty(), "outp_msg_cd가 공백"); + } + + @Test + void getInboundErrorResponseForStandard_예외없이_완료() { + assertDoesNotThrow(() -> { + StandardMessage msg = stdMsgManager.getStandardMessage(); + InterfaceMapper mapper = stdMsgManager.getMapper(); + callGetInboundErrorResponseForStandard(msg, mapper, "RECEAICMM999", "테스트 오류", "euc-kr", "FLAT"); + }, "getInboundErrorResponseForStandard 실행 중 예외 발생"); + } + + // ================================================================ + // getInboundErrorResponse (private) — IF_STANDARD 분기 검증 + // + // IF_STANDARD 분기 흐름: + // coordinateBeforeInboundErrorResponse(standardMessage) ← StandardMessageCoordinatorDJB 호출 + // getInboundErrorResponseForStandard(...) ← 위 검증된 메서드 + // ================================================================ + + @Test + void getInboundErrorResponse_IF_STANDARD_procs_rslt_dvcd_F_설정() throws Exception { + StandardMessage msg = stdMsgManager.getStandardMessage(); + InterfaceMapper mapper = stdMsgManager.getMapper(); + + Properties prop = new Properties(); + prop.setProperty(Processor.STD_MESSAGE, Keys.IF_STANDARD); + prop.setProperty(Processor.MESSAGE_TYPE, "FLAT"); + + callGetInboundErrorResponse(prop, msg, mapper, "RECEAICMM999", "테스트 오류", "euc-kr"); + + assertEquals("F", msg.findItemValue("HEAD.procs_rslt_dvcd"), + "IF_STANDARD 오류응답 후 procs_rslt_dvcd는 F여야 함"); + } + + @Test + void getInboundErrorResponse_IF_STANDARD_msg_dvcd_EM_설정() throws Exception { + StandardMessage msg = stdMsgManager.getStandardMessage(); + InterfaceMapper mapper = stdMsgManager.getMapper(); + + Properties prop = new Properties(); + prop.setProperty(Processor.STD_MESSAGE, Keys.IF_STANDARD); + prop.setProperty(Processor.MESSAGE_TYPE, "FLAT"); + + callGetInboundErrorResponse(prop, msg, mapper, "RECEAICMM999", "테스트 오류", "euc-kr"); + + assertEquals("EM", msg.findItemValue("MSG.msg_dvcd"), + "IF_STANDARD 오류응답 후 msg_dvcd는 EM이어야 함"); + } + + @Test + void getInboundErrorResponse_standardMessage_null_비표준_예외없음() { + Properties prop = new Properties(); + prop.setProperty(Processor.STD_MESSAGE, "X"); // 비표준 → ExceptionHandler.handle(errMsg) + prop.setProperty(Processor.MESSAGE_TYPE, "FLAT"); + + assertDoesNotThrow(() -> + callGetInboundErrorResponse(prop, null, null, "RECEAICMM999", "테스트 오류", "euc-kr"), + "standardMessage null 비표준 케이스에서 예외 발생" + ); + } + + // ================================================================ + // checkRestrictTime (public) + // ================================================================ + + @Test + void checkRestrictTime_start_end_미설정_항상_true() throws Exception { + RequestProcessor rp = new RequestProcessor(); + RestrictInfoVO rVo = new RestrictInfoVO(); + // params 길이 < 6 → start/end 모두 null → true 반환 + rVo.setEAICtrlDsticCtnt("a|b|c|d|e"); + + assertTrue(rp.checkRestrictTime(rVo), "start/end 미설정 시 true 기대"); + } + + @Test + void checkRestrictTime_start_0000_end_0000_항상_true() throws Exception { + RequestProcessor rp = new RequestProcessor(); + RestrictInfoVO rVo = new RestrictInfoVO(); + // end="0000" 이면 코드상 종료시간 체크 블록 건너뜀 → start 0000 이후면 true + rVo.setEAICtrlDsticCtnt("a|b|c|d|e|0000|0000"); + + assertTrue(rp.checkRestrictTime(rVo), "start=0000/end=0000 설정 시 true 기대"); + } + + @Test + void checkRestrictTime_end_0001_현재시간_이후_false() throws Exception { + RequestProcessor rp = new RequestProcessor(); + RestrictInfoVO rVo = new RestrictInfoVO(); + // 종료시간이 00:01이면 00:01 이후(= 거의 항상)는 false 반환 + // 단, 자정(00:00) 실행 시에는 통과 — 허용 오차 + rVo.setEAICtrlDsticCtnt("a|b|c|d|e|0000|0001"); + + java.util.Calendar cal = java.util.Calendar.getInstance(); + int hour = cal.get(java.util.Calendar.HOUR_OF_DAY); + int minute = cal.get(java.util.Calendar.MINUTE); + + boolean result = rp.checkRestrictTime(rVo); + // 00:01 이후(99% 이상의 시간)에는 false여야 함 + if (hour > 0 || minute >= 1) { + assertFalse(result, "종료시간(00:01) 이후에는 false여야 함"); + } + // 정확히 00:00에 실행되면 검증 생략 (허용) + } + + // ================================================================ + // helper + // ================================================================ + + private Object callGetInboundErrorResponseForStandard( + StandardMessage msg, InterfaceMapper mapper, + String errCode, String errMsg, String charset, String messageType) throws Exception { + Method method = RequestProcessor.class.getDeclaredMethod( + "getInboundErrorResponseForStandard", + StandardMessage.class, InterfaceMapper.class, + String.class, String.class, String.class, String.class); + method.setAccessible(true); + try { + return method.invoke(new RequestProcessor(), msg, mapper, errCode, errMsg, charset, messageType); + } catch (InvocationTargetException e) { + throw (Exception) e.getCause(); + } + } + + private Object callGetInboundErrorResponse( + Properties prop, StandardMessage msg, InterfaceMapper mapper, + String errCode, String errMsg, String charset) throws Exception { + Method method = RequestProcessor.class.getDeclaredMethod( + "getInboundErrorResponse", + Properties.class, StandardMessage.class, InterfaceMapper.class, + String.class, String.class, String.class); + method.setAccessible(true); + try { + return method.invoke(new RequestProcessor(), prop, msg, mapper, errCode, errMsg, charset); + } catch (InvocationTargetException e) { + throw (Exception) e.getCause(); + } + } +} diff --git a/src/test/java/com/eactive/eai/message/manager/StandardMessageManagerInitTest.java b/src/test/java/com/eactive/eai/message/manager/StandardMessageManagerInitTest.java new file mode 100644 index 0000000..a9e7a11 --- /dev/null +++ b/src/test/java/com/eactive/eai/message/manager/StandardMessageManagerInitTest.java @@ -0,0 +1,188 @@ +package com.eactive.eai.message.manager; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; + +import com.eactive.eai.custom.message.GUIDGeneratorDJB; +import com.eactive.eai.custom.message.InterfaceMapperDJB; +import com.eactive.eai.custom.message.StandardMessageCoordinatorDJB; +import com.eactive.eai.message.StandardMessage; +import com.eactive.eai.message.parser.StandardReader; + +/** + * StandardMessageManager.init() 단위테스트 + * + * Singleton 특성상 getInstance()로 접근, init()을 직접 호출하여 검증. + * System.property를 미설정 → 클래스패스의 standard-message-config.properties 로드. + */ +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class StandardMessageManagerInitTest { + + private StandardMessageManager manager; + + @BeforeAll + void setUp() throws Exception { + // 테스트용 config: layout.file.path에 src/main/resources 상대경로 사용 + // (StandardMessageUtil.generateMessageFromCsvFile은 파일시스템 상대경로로 읽음) + System.setProperty(StandardMessageManager.STANDARD_MESSAGE_CONFIG, + "src/test/resources/standard-message-config-test.properties"); + manager = StandardMessageManager.getInstance(); + manager.init(); + } + + // ---------------------------------------------------------------- + // 1. 표준전문 레이아웃 (StandardMessage) + // ---------------------------------------------------------------- + + @Test + void standardMessage_정상_로드() { + StandardMessage msg = manager.getStandardMessage(); + assertNotNull(msg, "StandardMessage가 null — CSV 로드 실패"); + } + + @Test + void standardMessage_HEAD_블록_존재() { + StandardMessage msg = manager.getStandardMessage(); + assertNotNull(msg.findItem("HEAD"), "HEAD 블록 없음"); + } + + @Test + void standardMessage_HEAD_guid_40자() { + StandardMessage msg = manager.getStandardMessage(); + assertEquals(GUIDGeneratorDJB.GUID_LENGTH, msg.findItem("HEAD.guid").getLength(), + "HEAD.guid 길이는 40자여야 함"); + } + + @Test + void standardMessage_HEAD_if_id_30자() { + StandardMessage msg = manager.getStandardMessage(); + assertEquals(30, msg.findItem("HEAD.if_id").getLength(), + "HEAD.if_id 길이는 30자여야 함"); + } + + @Test + void standardMessage_DATA_BIZ_DATA_존재() { + StandardMessage msg = manager.getStandardMessage(); + assertNotNull(msg.findItem("DATA.BIZ_DATA"), "DATA.BIZ_DATA 없음"); + } + + // ---------------------------------------------------------------- + // 2. InterfaceMapper + // ---------------------------------------------------------------- + + @Test + void mapper_InterfaceMapperDJB_인스턴스() { + assertNotNull(manager.getMapper(), "mapper가 null"); + assertInstanceOf(InterfaceMapperDJB.class, manager.getMapper(), + "mapper가 InterfaceMapperDJB가 아님: " + manager.getMapper().getClass().getName()); + } + + @Test + void mapper_GUID_경로_매핑_정상() { + StandardMessage msg = manager.getStandardMessage(); + InterfaceMapperDJB mapper = (InterfaceMapperDJB) manager.getMapper(); + String guid = GUIDGeneratorDJB.getGUID("EAI"); + mapper.setGuid(msg, guid); + assertEquals(guid, mapper.getGuid(msg), "GUID 설정/조회 불일치"); + } + + @Test + void mapper_ResponseType_S_F_변환() { + StandardMessage msg = manager.getStandardMessage(); + InterfaceMapperDJB mapper = (InterfaceMapperDJB) manager.getMapper(); + // NR → S + mapper.setResponseType(msg, com.ext.eai.common.stdmessage.STDMessageKeys.RESPONSE_TYPE_CODE_N); + assertEquals("S", msg.findItemValue("HEAD.procs_rslt_dvcd")); + // ER → F + mapper.setResponseType(msg, com.ext.eai.common.stdmessage.STDMessageKeys.RESPONSE_TYPE_CODE_E); + assertEquals("F", msg.findItemValue("HEAD.procs_rslt_dvcd")); + } + + // ---------------------------------------------------------------- + // 3. StandardMessageCoordinator + // ---------------------------------------------------------------- + + @Test + void coordinator_StandardMessageCoordinatorDJB_인스턴스() { + assertNotNull(manager.getMessageCoordinator(), "messageCoordinator가 null"); + assertInstanceOf(StandardMessageCoordinatorDJB.class, manager.getMessageCoordinator(), + "coordinator가 StandardMessageCoordinatorDJB가 아님: " + + manager.getMessageCoordinator().getClass().getName()); + } + + // ---------------------------------------------------------------- + // 4. Reader 등록 + // ---------------------------------------------------------------- + + @Test + void reader_JSON_등록() { + StandardReader reader = manager.getReader("JSON"); + assertNotNull(reader, "JSON reader 미등록"); + assertEquals("com.eactive.eai.message.parser.JsonReader", + reader.getClass().getName()); + } + + @Test + void reader_XML_등록() { + StandardReader reader = manager.getReader("XML"); + assertNotNull(reader, "XML reader 미등록"); + assertEquals("com.eactive.eai.message.parser.XmlReader", + reader.getClass().getName()); + } + + @Test + void reader_ASC_등록() { + StandardReader reader = manager.getReader("ASC"); + assertNotNull(reader, "ASC(Flat) reader 미등록"); + assertEquals("com.eactive.eai.message.parser.FlatReader", + reader.getClass().getName()); + } + + @Test + void reader_FLAT_등록() { + StandardReader reader = manager.getReader("FLAT"); + assertNotNull(reader, "FLAT reader 미등록"); + assertEquals("com.eactive.eai.message.parser.FlatReader", + reader.getClass().getName()); + } + + // ---------------------------------------------------------------- + // 5. getStandardMessage() 독립 복사본 반환 + // ---------------------------------------------------------------- + + @Test + void getStandardMessage_호출마다_독립_복사본() { + StandardMessage msg1 = manager.getStandardMessage(); + StandardMessage msg2 = manager.getStandardMessage(); + assertNotSame(msg1, msg2, "getStandardMessage()는 매번 새 인스턴스를 반환해야 함"); + + // 한 쪽 수정이 다른 쪽에 영향 없어야 함 + msg1.setData("HEAD.guid", "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"); + assertNotEquals( + msg1.findItemValue("HEAD.guid"), + msg2.findItemValue("HEAD.guid"), + "복사본이 원본에 영향을 줌 — clone 미작동"); + } + + // ---------------------------------------------------------------- + // 6. reload() — init() 재실행 후 상태 일관성 + // ---------------------------------------------------------------- + + @Test + void reload_후_mapper_정상() throws Exception { + manager.reload(); + assertNotNull(manager.getMapper(), "reload 후 mapper null"); + assertInstanceOf(InterfaceMapperDJB.class, manager.getMapper()); + } + + @Test + void reload_후_standardMessage_정상() throws Exception { + manager.reload(); + StandardMessage msg = manager.getStandardMessage(); + assertNotNull(msg); + assertNotNull(msg.findItem("HEAD")); + } +} diff --git a/src/test/resources/standard-message-config-test.properties b/src/test/resources/standard-message-config-test.properties new file mode 100644 index 0000000..568c989 --- /dev/null +++ b/src/test/resources/standard-message-config-test.properties @@ -0,0 +1,13 @@ +layout.file.type=CSV +layout.file.path=src/main/resources/standard-layout-djb.csv +layout.filter.FLAT=com.eactive.eai.message.filter.FlatMessageFilter +mapper.class=com.eactive.eai.custom.message.InterfaceMapperDJB +mapper.definition=standard-message-mapping-config-djb.properties +message.coordinator.class=com.eactive.eai.custom.message.StandardMessageCoordinatorDJB +reader.JSON=com.eactive.eai.message.parser.JsonReader +reader.JSN=com.eactive.eai.message.parser.JsonReader +reader.XML=com.eactive.eai.message.parser.XmlReader +reader.ASC=com.eactive.eai.message.parser.FlatReader +reader.FLAT=com.eactive.eai.message.parser.FlatReader +encode.flat=euc-kr +encode.xml=utf-8