ec6b51f2a8
outp_msg_cd(NCMM00001) / outp_msg_ctnt(정상처리되었습니다.) 가 표준전문 레이아웃의 기본값으로 붙어 있어서, "아무도 채우지 않은 상태" 와 "정상이라고 판정한 상태" 가 값만으로 구분되지 않았다. 2026-09 사고에서 MSG 부 파싱이 통째로 스킵됐는데도 오류가 "정상처리되었습니다." 로 보고된 것이 이 때문이다. 레이아웃 기본값을 없애고, StandardMessageCoordinatorDJB.coordinateBeforeResponse 에서 정상 응답일 때만 채운다. 이 메서드는 모든 동기 응답이 거치는 마지막 관문이라 (RequestProcessor 에서 호출) 경로를 빠짐없이 덮는다. 두 조건을 함께 본다. - 정상 응답(procs_rslt_dvcd=S) 일 때만. 오류 응답에 정상 메시지를 채우면 사고 재현이다. - 값이 비어 있을 때만(setIfBlank). 대외계가 표준응답으로 보낸 MSG 가 우선이며 덮어쓰면 JsonReader 의 MSG 부 보존 수정이 무의미해진다. 테스트: 기존 3건이 coordinateAfterRecvNonStdSyncResponse 직후에 단정하고 있었으나 값은 이제 마지막 관문에서 채워지므로, 실제 흐름대로 coordinateBeforeResponse 까지 거치도록 고치고 단정도 "공백 아님" 에서 상수 일치로 강화했다. 설계 조건을 지키는 회귀 테스트 2건을 추가했다. - 오류응답에는_정상메시지가_채워지지_않는다 - 대외계가_보낸_MSG_값은_덮어쓰지_않는다 주의: 이 CSV 는 layout.file.type=CSV 인 테스트 설정에서만 쓰인다. 운영은 DB 레이아웃을 읽으므로 DB 의 두 항목 기본값도 함께 비워야 실제 효과가 있다. 비우기 전까지는 setIfBlank 가 아무것도 하지 않아 기존과 동일하게 동작한다. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UnybKuxcuPafGhVGh4wqkZ
608 lines
26 KiB
Java
608 lines
26 KiB
Java
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) 일관성
|
|
* 8. 상대 헤더가 표준을 어긴 응답 파싱 (2026-09 사고 회귀 방지)
|
|
*/
|
|
@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\"}"
|
|
+ "}]"
|
|
+ "}";
|
|
|
|
/**
|
|
* 상대 헤더가 표준을 어긴 오류응답 JSON (2026-09 실제 사고 전문 형태)
|
|
*
|
|
* 표준은 응답 시 dman_rspn_dvcd=R 이어야 하는데, 상대가 요청값 S 를 그대로 에코백하면서
|
|
* procs_rslt_dvcd 만 F(오류)로 보냈다. MSG 블록의 레이아웃 조건이 !S 라 예전에는
|
|
* MSG 이하가 통째로 버려졌고, MAIN_MSG 가 레이아웃 기본값으로 남아 오류가
|
|
* "정상처리되었습니다." 로 보고되고 거래로그에서도 MSG 부가 누락됐다.
|
|
*
|
|
* 같은 전문에 들어있던 다른 비표준 요소도 함께 담아 둔다.
|
|
* - msg_list_rowcnt 가 따옴표 없는 선행 0 숫자(00001)
|
|
* - outp_msg_desc 안에 이스케이프되지 않은 개행/탭
|
|
*/
|
|
private static final String RSP_JSON_BAD_HEADER =
|
|
"{"
|
|
+ "\"HEAD\":{"
|
|
+ "\"nxgn_stnd_idfr\":\"JERA\","
|
|
+ "\"guid\":\"" + TEST_GUID + "\","
|
|
+ "\"guid_prgs_no\":\"2\","
|
|
+ "\"stnd_mesg_ver\":\"R10\","
|
|
+ "\"ortr_guid\":\"" + TEST_GUID + "\","
|
|
+ "\"dman_rspn_dvcd\":\"S\"," // 표준 위반: 응답인데 R 이 아님
|
|
+ "\"if_id\":\"" + TEST_IF_ID + "\","
|
|
+ "\"procs_rslt_dvcd\":\"F\","
|
|
+ "\"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\","
|
|
+ "\"rprs_msg_cd\":\"900400\""
|
|
+ "},"
|
|
+ "\"MSG\":{"
|
|
+ "\"msg_dvcd\":\"EM\","
|
|
+ "\"msg_scop_len\":1437,"
|
|
+ "\"MAIN_MSG\":{"
|
|
+ "\"outp_atrb_cd\":\"0\","
|
|
+ "\"outp_msg_cd\":\"900400\","
|
|
+ "\"outp_msg_ctnt\":\"\","
|
|
+ "\"outp_msg_desc\":\"[ErrorCode:S9OWP0011].WTC.Adapter.\n\t호출 오류\","
|
|
+ "\"mngm_msg_cd\":\"\","
|
|
+ "\"msg_list_rowcnt\":00001"
|
|
+ "}"
|
|
+ "}"
|
|
+ "}";
|
|
|
|
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<String>() {
|
|
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();
|
|
|
|
// DATA는 GRID(반복부)라 값은 행(row 0)에 들어간다. 인덱스 없는 "DATA.BIZ_DATA"는
|
|
// 직렬화에 쓰이지 않는 템플릿 항목을 가리키므로 빈 값이 나온다.
|
|
// 운영 경로도 인덱스 형태를 쓴다 — standard-message-mapping-config-djb.properties:27
|
|
// BIZ_DATA=DATA[0].BIZ_DATA
|
|
String bizData = msg.findItemValue("DATA[0].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);
|
|
// DATA는 GRID(반복부)이므로 직렬화하면 JSON 배열이다. 표준 레이아웃상 1건 고정.
|
|
// BIZ_DATA는 JSON 문자열 또는 JSON 오브젝트로 들어갈 수 있음
|
|
assertTrue(root.path("DATA").isArray(), "직렬화 JSON의 DATA가 배열이 아님");
|
|
assertNotNull(root.path("DATA").path(0).get("BIZ_DATA"),
|
|
"직렬화 JSON에 DATA[0].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);
|
|
// outp_msg_cd/ctnt 는 레이아웃 기본값이 없으므로, 실제 흐름대로 응답 직전 조정까지
|
|
// 거쳐야 채워진다 (StandardMessageCoordinatorDJB.coordinateBeforeResponse)
|
|
coordinator.coordinateBeforeResponse(msg, null, null, "utf-8");
|
|
|
|
String json = msg.getDataString(MessageType.JSON, "utf-8");
|
|
JsonNode root = jacksonMapper.readTree(json);
|
|
String ctnt = root.path("MSG").path("MAIN_MSG").path("outp_msg_ctnt").asText();
|
|
assertEquals(StandardMessageCoordinatorDJB.NORMAL_OUTP_MSG_CTNT, ctnt,
|
|
"정상응답 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 불일치");
|
|
}
|
|
|
|
// ================================================================
|
|
// 8. 상대 헤더가 표준을 어긴 응답 파싱 (2026-09 사고 회귀 방지)
|
|
// ================================================================
|
|
|
|
@Test
|
|
void 상대헤더오류_dman_rspn_dvcd가_S여도_MSG부가_파싱됨() throws Exception {
|
|
StandardMessage msg = parseRspBadHeader();
|
|
|
|
assertEquals("F", msg.findItemValue("HEAD.procs_rslt_dvcd"),
|
|
"HEAD가 파싱되지 않음 — 전제 조건 실패");
|
|
assertEquals("EM", msg.findItemValue("MSG.msg_dvcd"),
|
|
"MSG.msg_dvcd 미반영 — MSG 블록이 버려졌다");
|
|
assertEquals("900400", msg.findItemValue("MSG.MAIN_MSG.outp_msg_cd"),
|
|
"outp_msg_cd가 레이아웃 기본값(NCMM00001) 그대로 — MSG부가 버려졌다");
|
|
assertNotEquals("정상처리되었습니다.", msg.findItemValue("MSG.MAIN_MSG.outp_msg_ctnt"),
|
|
"오류응답인데 레이아웃 기본값 '정상처리되었습니다.'가 그대로 남음");
|
|
assertNotNull(msg.findItemValue("MSG.MAIN_MSG.outp_msg_desc"),
|
|
"outp_msg_desc가 null — 개행/탭이 섞인 값이 파싱되지 않았다");
|
|
}
|
|
|
|
@Test
|
|
void 상대헤더오류_재직렬화시_MSG부_보존() throws Exception {
|
|
StandardMessage msg = parseRspBadHeader();
|
|
|
|
// 거래로그(EAILogDAO)는 수신 원문이 아니라 StandardMessage 재직렬화본을 저장한다.
|
|
// MSG가 hidden 이면 로그에서도 MSG부 이후가 통째로 누락된다.
|
|
String json = msg.getDataString(MessageType.JSON, "utf-8");
|
|
JsonNode root = jacksonMapper.readTree(json);
|
|
|
|
assertTrue(root.has("MSG"), "재직렬화 결과에 MSG 블록 없음 — 거래로그에 MSG부 누락");
|
|
assertEquals("900400", root.path("MSG").path("MAIN_MSG").path("outp_msg_cd").asText(),
|
|
"재직렬화 결과의 outp_msg_cd 불일치");
|
|
}
|
|
|
|
// ================================================================
|
|
// 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;
|
|
}
|
|
|
|
private StandardMessage parseRspBadHeader() throws Exception {
|
|
StandardMessage msg = manager.getStandardMessage();
|
|
jsonReader.parse(msg, RSP_JSON_BAD_HEADER);
|
|
return msg;
|
|
}
|
|
}
|