오류코드 메시지를 프로퍼티 그룹에 등록하여 매핑 치환하는 AdapterErrorMsgHandler 개발

- AdapterErrorMessageHandler{CODE_CONVERT} 에 등록
- TemplateCodeConvertAdapterErrorMsgHandler json만 지원하는 handler 개발
This commit is contained in:
curry772
2026-06-15 14:45:18 +09:00
parent 756523c5c3
commit bf03a97fa3
3 changed files with 676 additions and 1 deletions
@@ -89,7 +89,7 @@ public class TemplateAdapterErrorMsgHandler implements AdapterErrorMessageHandle
private static final Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
/** AdapterPropManager 에서 템플릿을 조회할 프로퍼티 그룹 이름 */
/** PropManager 에서 템플릿을 조회할 프로퍼티 그룹 이름 */
static final String PROP_GROUP = "AdapterErrorMessageHandler";
/** callProp 참조 변수 접두어: ${callprop.키} */
@@ -0,0 +1,126 @@
package com.eactive.eai.adapter.handler;
import java.util.Properties;
import org.apache.commons.lang3.StringUtils;
import com.eactive.eai.common.message.EAIMessage;
import com.eactive.eai.common.property.PropManager;
import com.eactive.eai.common.util.Logger;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
public class TemplateCodeConvertAdapterErrorMsgHandler extends TemplateAdapterErrorMsgHandler {
private static final Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
/** PropManager 에서 코드 변환 설정을 조회할 프로퍼티 그룹 이름 */
static final String PROP_GROUP = "AdapterErrorMessageHandler{CODE_CONVERT}";
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
@Override
public Object generateNonStandardErrorResponseMessage(
String inboundAdapterGroupName,
String inboundAdapterName,
Properties callProp,
Object outboundRequestData,
EAIMessage resEaiMsg) throws Exception {
Object responseMsessage = super.generateNonStandardErrorResponseMessage(
inboundAdapterGroupName,
inboundAdapterName,
callProp,
outboundRequestData,
resEaiMsg);
logger.debug("generateNonStandardErrorResponseMessage - {}", responseMsessage);
if (!(responseMsessage instanceof String)) {
return responseMsessage;
}
String jsonStr = (String) responseMsessage;
if (StringUtils.isBlank(jsonStr)) {
return responseMsessage;
}
Properties props = PropManager.getInstance().getProperties(PROP_GROUP);
if (props == null) {
return responseMsessage;
}
String fieldsKey = inboundAdapterGroupName + ".convert.fields";
String fieldsValue = props.getProperty(fieldsKey);
if (StringUtils.isBlank(fieldsValue)) {
return responseMsessage;
}
JsonNode rootNode = OBJECT_MAPPER.readTree(jsonStr);
boolean modified = false;
for (String rawField : fieldsValue.split(",")) {
String fieldPath = rawField.trim();
if (StringUtils.isBlank(fieldPath)) continue;
String currentValue = getJsonValue(rootNode, fieldPath);
if (currentValue == null) continue;
// key: inboundAdapterGroupName.{fieldPath}.{현재값} → 폴백: ...{fieldPath}.default
String mappingKey = inboundAdapterGroupName + "." + fieldPath + "." + currentValue;
String newValue = props.getProperty(mappingKey);
if (newValue == null) {
String defaultKey = inboundAdapterGroupName + "." + fieldPath + ".default";
newValue = props.getProperty(defaultKey);
}
if (newValue == null) continue;
setJsonValue(rootNode, fieldPath, newValue);
modified = true;
if (logger.isDebug()) {
logger.debug("code convert: field={}, {} -> {}", fieldPath, currentValue, newValue);
}
}
if (modified) {
responseMsessage = OBJECT_MAPPER.writeValueAsString(rootNode);
}
return responseMsessage;
}
/**
* dot 구분 경로로 JsonNode 에서 값을 꺼낸다.
* 최종 노드가 값 노드(primitive)가 아니면 null 반환.
*/
private String getJsonValue(JsonNode node, String fieldPath) {
JsonNode current = node;
for (String part : fieldPath.split("\\.", -1)) {
if (current == null || !current.isObject()) return null;
current = current.get(part);
}
if (current == null || current.isNull() || !current.isValueNode()) return null;
return current.asText();
}
/**
* dot 구분 경로로 ObjectNode 의 최종 필드 값을 newValue 로 교체한다.
* 중간 경로가 ObjectNode 가 아니면 무시한다.
*/
private void setJsonValue(JsonNode rootNode, String fieldPath, String newValue) {
String[] parts = fieldPath.split("\\.", -1);
JsonNode current = rootNode;
for (int i = 0; i < parts.length - 1; i++) {
if (current == null || !current.isObject()) return;
current = current.get(parts[i]);
}
if (current instanceof ObjectNode) {
((ObjectNode) current).put(parts[parts.length - 1], newValue);
}
}
}
@@ -0,0 +1,549 @@
package com.eactive.eai.adapter.handler;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import java.lang.reflect.Field;
import java.util.Properties;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.springframework.context.ApplicationContext;
import com.eactive.eai.common.message.EAIMessage;
import com.eactive.eai.common.property.PropManager;
import com.eactive.eai.common.util.ApplicationContextProvider;
import com.eactive.eai.message.StandardItem;
import com.eactive.eai.message.StandardMessage;
import com.eactive.eai.message.StandardType;
/**
* TemplateCodeConvertAdapterErrorMsgHandler 단위테스트
*
* 검증 대상: generateNonStandardErrorResponseMessage
* - 부모 클래스(TemplateAdapterErrorMsgHandler)가 반환한 JSON 문자열에서
* PropManager(AdapterErrorMessageHandler{CODE_CONVERT} 그룹)에 등록된
* 매핑 정보로 지정 필드 값을 변환한다.
*/
class TemplateCodeConvertAdapterErrorMsgHandlerTest {
private TemplateCodeConvertAdapterErrorMsgHandler handler;
@BeforeEach
void setUp() {
handler = new TemplateCodeConvertAdapterErrorMsgHandler();
}
// ================================================================
// 공통 헬퍼
// ================================================================
private static StandardItem field(String name, String value) {
return new StandardItem(name, 3, StandardType.FIELD, 1, 0, 300, 1, "", "", value, name);
}
private static StandardItem group(String name) {
return new StandardItem(name, 2, StandardType.GROUP, 1, 0, 0, 1, "", "", null, name);
}
private StandardMessage buildMsg(String errCode, String errMsg) {
StandardMessage msg = new StandardMessage();
StandardItem msgGroup = group("MSG");
StandardItem mainMsg = group("MAIN_MSG");
mainMsg.addItem(field("outp_msg_cd", errCode));
mainMsg.addItem(field("outp_msg_ctnt", errMsg));
msgGroup.addItem(mainMsg);
StandardItem msgList = new StandardItem(
"MSG_LIST", 2, StandardType.GRID, 0, 0, 0, 1, "", "", null, "MSG_LIST");
msgList.addItem(field("outp_msg_cd", ""));
msgList.addItem(field("outp_msg_ctnt", ""));
msgGroup.addItem(msgList);
msg.addItem(msgGroup);
return msg;
}
private EAIMessage toEaiMessage(StandardMessage msg) {
EAIMessage eaiMessage = new EAIMessage();
eaiMessage.setStandardMessage(msg);
return eaiMessage;
}
/**
* ApplicationContextProvider 에 mock PropManager 를 주입한다.
* 부모 클래스의 getProperty(group, key) 와
* 자식 클래스의 getProperties(group) 을 동일한 mock 으로 처리한다.
*/
private void injectMockPropManager(PropManager mockPropManager) throws Exception {
ApplicationContext mockCtx = Mockito.mock(ApplicationContext.class);
Mockito.when(mockCtx.getBean(PropManager.class)).thenReturn(mockPropManager);
Field ctxField = ApplicationContextProvider.class.getDeclaredField("context");
ctxField.setAccessible(true);
ctxField.set(null, mockCtx);
}
/**
* 부모 템플릿 결과가 될 JSON 과 코드 변환 설정을 함께 세팅하는 헬퍼.
*
* @param adapterGroupName 어댑터 그룹명
* @param template 부모 핸들러에서 사용할 JSON 템플릿 문자열
* @param convertProps PROP_GROUP(CODE_CONVERT) 에서 반환될 Properties (null 가능)
*/
private PropManager setupMock(String adapterGroupName, String template,
Properties convertProps) throws Exception {
PropManager mockProp = Mockito.mock(PropManager.class);
// 부모 클래스: getProperty("AdapterErrorMessageHandler", "GROUP.template")
Mockito.when(mockProp.getProperty(
eq(TemplateAdapterErrorMsgHandler.PROP_GROUP),
eq(adapterGroupName + ".template"))).thenReturn(template);
// 자식 클래스: getProperties("AdapterErrorMessageHandler{CODE_CONVERT}")
Mockito.when(mockProp.getProperties(
eq(TemplateCodeConvertAdapterErrorMsgHandler.PROP_GROUP))).thenReturn(convertProps);
injectMockPropManager(mockProp);
return mockProp;
}
/** convert.fields 와 매핑 항목들로 Properties 를 생성하는 헬퍼 */
private static Properties convertProps(String adapterGroupName,
String fields,
String... mappings) {
Properties p = new Properties();
p.setProperty(adapterGroupName + ".convert.fields", fields);
for (int i = 0; i < mappings.length - 1; i += 2) {
p.setProperty(adapterGroupName + "." + mappings[i], mappings[i + 1]);
}
return p;
}
// ================================================================
// 사전 조건 및 조기 반환
// ================================================================
@Nested
@DisplayName("사전 조건 조기 반환")
class EarlyReturn {
@Test
@DisplayName("부모 핸들러가 null 반환 시 null 그대로 반환")
void 부모_null_반환시_null() throws Exception {
PropManager mockProp = Mockito.mock(PropManager.class);
// 템플릿 없음 → 부모 핸들러가 null 반환
Mockito.when(mockProp.getProperty(anyString(), anyString())).thenReturn(null);
injectMockPropManager(mockProp);
Object result = handler.generateNonStandardErrorResponseMessage(
"MY_GROUP", "MY_ADAPTER", new Properties(), null,
toEaiMessage(buildMsg("40000", "오류")));
assertNull(result);
// getProperties 는 호출되지 않아야 함
Mockito.verify(mockProp, Mockito.never())
.getProperties(TemplateCodeConvertAdapterErrorMsgHandler.PROP_GROUP);
}
@Test
@DisplayName("부모 핸들러가 빈 문자열 반환 시 그대로 반환 (변환 미수행)")
void 부모_빈문자열_그대로_반환() throws Exception {
setupMock("MY_GROUP", " ", null); // blank → 부모도 null 반환
Object result = handler.generateNonStandardErrorResponseMessage(
"MY_GROUP", "MY_ADAPTER", new Properties(), null,
toEaiMessage(buildMsg("40000", "오류")));
assertNull(result);
}
@Test
@DisplayName("CODE_CONVERT 그룹의 Properties 가 null 이면 템플릿 결과 그대로 반환")
void convert_props_null_시_원본_반환() throws Exception {
String template = "{\"resCode\":\"${MSG.MAIN_MSG.outp_msg_cd}\"}";
setupMock("MY_GROUP", template, null); // getProperties → null
Object result = handler.generateNonStandardErrorResponseMessage(
"MY_GROUP", "MY_ADAPTER", new Properties(), null,
toEaiMessage(buildMsg("40000", "오류")));
assertEquals("{\"resCode\":\"40000\"}", result);
}
@Test
@DisplayName("convert.fields 키가 없으면 템플릿 결과 그대로 반환")
void convert_fields_없으면_원본_반환() throws Exception {
String template = "{\"resCode\":\"${MSG.MAIN_MSG.outp_msg_cd}\"}";
// convert.fields 를 설정하지 않은 빈 Properties
setupMock("MY_GROUP", template, new Properties());
Object result = handler.generateNonStandardErrorResponseMessage(
"MY_GROUP", "MY_ADAPTER", new Properties(), null,
toEaiMessage(buildMsg("40000", "오류")));
assertEquals("{\"resCode\":\"40000\"}", result);
}
@Test
@DisplayName("convert.fields 가 공백이면 템플릿 결과 그대로 반환")
void convert_fields_공백이면_원본_반환() throws Exception {
String template = "{\"resCode\":\"${MSG.MAIN_MSG.outp_msg_cd}\"}";
Properties cp = new Properties();
cp.setProperty("MY_GROUP.convert.fields", " ");
setupMock("MY_GROUP", template, cp);
Object result = handler.generateNonStandardErrorResponseMessage(
"MY_GROUP", "MY_ADAPTER", new Properties(), null,
toEaiMessage(buildMsg("40000", "오류")));
// 필드가 공백이므로 split 후 trim → blank → skip → 변환 없음
assertEquals("{\"resCode\":\"40000\"}", result);
}
}
// ================================================================
// 단순 필드 코드 변환
// ================================================================
@Nested
@DisplayName("단순 필드 코드 변환")
class SimpleFieldConvert {
@Test
@DisplayName("단일 필드 값 변환")
void 단일_필드_변환() throws Exception {
String template = "{\"resCode\":\"${MSG.MAIN_MSG.outp_msg_cd}\",\"resMessage\":\"${MSG.MAIN_MSG.outp_msg_ctnt}\"}";
Properties cp = convertProps("GRP",
"resCode",
"resCode.40000", "TB_ERR_999");
setupMock("GRP", template, cp);
Object result = handler.generateNonStandardErrorResponseMessage(
"GRP", "ADP", new Properties(), null,
toEaiMessage(buildMsg("40000", "Bad Request")));
assertEquals("{\"resCode\":\"TB_ERR_999\",\"resMessage\":\"Bad Request\"}", result);
}
@Test
@DisplayName("여러 필드 동시 변환")
void 여러_필드_변환() throws Exception {
String template = "{\"resCode\":\"${MSG.MAIN_MSG.outp_msg_cd}\",\"resMessage\":\"${MSG.MAIN_MSG.outp_msg_ctnt}\"}";
Properties cp = convertProps("GRP",
"resCode,resMessage",
"resCode.40000", "TB_ERR_999",
"resMessage.40000", "요청파라미터에 문제가 있습니다.(필수값 입력오류)");
setupMock("GRP", template, cp);
Object result = handler.generateNonStandardErrorResponseMessage(
"GRP", "ADP", new Properties(), null,
toEaiMessage(buildMsg("40000", "40000")));
assertEquals(
"{\"resCode\":\"TB_ERR_999\",\"resMessage\":\"요청파라미터에 문제가 있습니다.(필수값 입력오류)\"}",
result);
}
@Test
@DisplayName("매핑에 없는 값은 원본 그대로 유지")
void 매핑_없는_값_원본_유지() throws Exception {
String template = "{\"resCode\":\"${MSG.MAIN_MSG.outp_msg_cd}\"}";
Properties cp = convertProps("GRP",
"resCode",
"resCode.40000", "TB_ERR_999"); // 99999 에 대한 매핑 없음
setupMock("GRP", template, cp);
Object result = handler.generateNonStandardErrorResponseMessage(
"GRP", "ADP", new Properties(), null,
toEaiMessage(buildMsg("99999", "기타오류")));
assertEquals("{\"resCode\":\"99999\"}", result);
}
@Test
@DisplayName("convert.fields 쉼표 구분 중 공백 포함해도 정상 처리")
void fields_공백_포함_정상처리() throws Exception {
String template = "{\"resCode\":\"${MSG.MAIN_MSG.outp_msg_cd}\",\"resMessage\":\"${MSG.MAIN_MSG.outp_msg_ctnt}\"}";
Properties cp = convertProps("GRP",
" resCode , resMessage ", // 공백 포함
"resCode.40000", "TB_ERR_999",
"resMessage.40000", "필수값 오류");
setupMock("GRP", template, cp);
Object result = handler.generateNonStandardErrorResponseMessage(
"GRP", "ADP", new Properties(), null,
toEaiMessage(buildMsg("40000", "40000")));
assertEquals("{\"resCode\":\"TB_ERR_999\",\"resMessage\":\"필수값 오류\"}", result);
}
@Test
@DisplayName("매핑에 없는 값은 default 키로 폴백 변환")
void 매핑_없는_값_default_폴백() throws Exception {
String template = "{\"resCode\":\"${MSG.MAIN_MSG.outp_msg_cd}\"}";
Properties cp = convertProps("GRP",
"resCode",
"resCode.40000", "TB_ERR_999",
"resCode.default", "TB_ERR_DEFAULT"); // 폴백
setupMock("GRP", template, cp);
Object result = handler.generateNonStandardErrorResponseMessage(
"GRP", "ADP", new Properties(), null,
toEaiMessage(buildMsg("99999", ""))); // 99999 매핑 없음
assertEquals("{\"resCode\":\"TB_ERR_DEFAULT\"}", result);
}
@Test
@DisplayName("매핑이 있으면 default 보다 우선 적용")
void 매핑_존재시_default_미사용() throws Exception {
String template = "{\"resCode\":\"${MSG.MAIN_MSG.outp_msg_cd}\"}";
Properties cp = convertProps("GRP",
"resCode",
"resCode.40000", "TB_ERR_999",
"resCode.default", "TB_ERR_DEFAULT");
setupMock("GRP", template, cp);
Object result = handler.generateNonStandardErrorResponseMessage(
"GRP", "ADP", new Properties(), null,
toEaiMessage(buildMsg("40000", ""))); // 40000 매핑 존재
assertEquals("{\"resCode\":\"TB_ERR_999\"}", result);
}
@Test
@DisplayName("매핑도 default 도 없으면 원본 유지")
void 매핑_default_모두없으면_원본_유지() throws Exception {
String template = "{\"resCode\":\"${MSG.MAIN_MSG.outp_msg_cd}\"}";
Properties cp = convertProps("GRP",
"resCode",
"resCode.40000", "TB_ERR_999" // 99999 매핑 없음, default 도 없음
);
setupMock("GRP", template, cp);
Object result = handler.generateNonStandardErrorResponseMessage(
"GRP", "ADP", new Properties(), null,
toEaiMessage(buildMsg("99999", "")));
assertEquals("{\"resCode\":\"99999\"}", result);
}
@Test
@DisplayName("여러 필드에서 일부는 매핑, 일부는 default 로 변환")
void 여러_필드_매핑과_default_혼합() throws Exception {
String template = "{\"resCode\":\"${MSG.MAIN_MSG.outp_msg_cd}\",\"resMessage\":\"${MSG.MAIN_MSG.outp_msg_ctnt}\"}";
Properties cp = new Properties();
cp.setProperty("GRP.convert.fields", "resCode,resMessage");
cp.setProperty("GRP.resCode.40000", "TB_ERR_999"); // 정확 매핑
cp.setProperty("GRP.resMessage.default", "기타 오류입니다."); // resMessage 는 default 만
setupMock("GRP", template, cp);
Object result = handler.generateNonStandardErrorResponseMessage(
"GRP", "ADP", new Properties(), null,
toEaiMessage(buildMsg("40000", "Bad Request")));
assertEquals("{\"resCode\":\"TB_ERR_999\",\"resMessage\":\"기타 오류입니다.\"}", result);
}
@Test
@DisplayName("여러 에러코드에 대한 각각 다른 매핑 적용")
void 다른_에러코드_다른_매핑() throws Exception {
String template = "{\"resCode\":\"${MSG.MAIN_MSG.outp_msg_cd}\"}";
Properties cp = new Properties();
cp.setProperty("GRP.convert.fields", "resCode");
cp.setProperty("GRP.resCode.40000", "TB_ERR_999");
cp.setProperty("GRP.resCode.50000", "TB_ERR_500");
setupMock("GRP", template, cp);
// 40000 → TB_ERR_999
Object result1 = handler.generateNonStandardErrorResponseMessage(
"GRP", "ADP", new Properties(), null,
toEaiMessage(buildMsg("40000", "")));
assertEquals("{\"resCode\":\"TB_ERR_999\"}", result1);
// 50000 → TB_ERR_500
Object result2 = handler.generateNonStandardErrorResponseMessage(
"GRP", "ADP", new Properties(), null,
toEaiMessage(buildMsg("50000", "")));
assertEquals("{\"resCode\":\"TB_ERR_500\"}", result2);
}
}
// ================================================================
// 중첩 경로(dot path) 필드 코드 변환
// ================================================================
@Nested
@DisplayName("중첩 경로(dot path) 필드 코드 변환")
class NestedPathConvert {
@Test
@DisplayName("1단계 중첩 경로 필드 변환 result.code")
void 한단계_중첩_경로_변환() throws Exception {
// 템플릿이 중첩 JSON 을 생성하는 경우 → 변환 전 JSON 을 직접 구성
// 부모 render 결과를 미리 알고 있으므로, 간단한 템플릿 + 고정값 활용
String template = "{\"result\":{\"code\":\"${MSG.MAIN_MSG.outp_msg_cd}\"}}";
Properties cp = convertProps("GRP",
"result.code",
"result.code.40000", "TB_ERR_999");
setupMock("GRP", template, cp);
Object result = handler.generateNonStandardErrorResponseMessage(
"GRP", "ADP", new Properties(), null,
toEaiMessage(buildMsg("40000", "")));
assertEquals("{\"result\":{\"code\":\"TB_ERR_999\"}}", result);
}
@Test
@DisplayName("2단계 중첩 경로 필드 변환 – a.b.code")
void 두단계_중첩_경로_변환() throws Exception {
// 부모가 반환할 JSON: 고정 문자열 + stdmsg 치환 없이 직접 지정
// 어댑터 그룹명 키 + template 이 fixed JSON 을 반환하도록 설정
String fixedJson = "{\"a\":{\"b\":{\"code\":\"ERR_001\"}}}";
PropManager mockProp = Mockito.mock(PropManager.class);
Mockito.when(mockProp.getProperty(
eq(TemplateAdapterErrorMsgHandler.PROP_GROUP),
eq("GRP.template"))).thenReturn(fixedJson); // 변수 없는 고정 JSON
Properties cp = convertProps("GRP",
"a.b.code",
"a.b.code.ERR_001", "MAPPED_CODE");
Mockito.when(mockProp.getProperties(
eq(TemplateCodeConvertAdapterErrorMsgHandler.PROP_GROUP))).thenReturn(cp);
injectMockPropManager(mockProp);
Object result = handler.generateNonStandardErrorResponseMessage(
"GRP", "ADP", new Properties(), null,
toEaiMessage(buildMsg("", "")));
assertEquals("{\"a\":{\"b\":{\"code\":\"MAPPED_CODE\"}}}", result);
}
@Test
@DisplayName("중첩 경로가 존재하지 않으면 해당 필드 변환 건너뜀")
void 없는_중첩_경로_건너뜀() throws Exception {
String template = "{\"resCode\":\"${MSG.MAIN_MSG.outp_msg_cd}\"}";
Properties cp = convertProps("GRP",
"nonExist.code", // JSON 에 없는 경로
"nonExist.code.40000", "MAPPED");
setupMock("GRP", template, cp);
Object result = handler.generateNonStandardErrorResponseMessage(
"GRP", "ADP", new Properties(), null,
toEaiMessage(buildMsg("40000", "")));
// 변환 대상 필드가 없으므로 원본 그대로
assertEquals("{\"resCode\":\"40000\"}", result);
}
}
// ================================================================
// 복합 시나리오
// ================================================================
@Nested
@DisplayName("복합 시나리오")
class Combined {
@Test
@DisplayName("일부 필드만 매핑 존재 – 나머지는 원본 유지")
void 일부_필드_매핑_나머지_원본() throws Exception {
String template = "{\"resCode\":\"${MSG.MAIN_MSG.outp_msg_cd}\",\"resMessage\":\"${MSG.MAIN_MSG.outp_msg_ctnt}\"}";
Properties cp = convertProps("GRP",
"resCode,resMessage",
"resCode.40000", "TB_ERR_999"
// resMessage 매핑 없음
);
setupMock("GRP", template, cp);
Object result = handler.generateNonStandardErrorResponseMessage(
"GRP", "ADP", new Properties(), null,
toEaiMessage(buildMsg("40000", "Bad Request")));
// resCode 는 변환, resMessage 는 원본 유지
assertEquals("{\"resCode\":\"TB_ERR_999\",\"resMessage\":\"Bad Request\"}", result);
}
@Test
@DisplayName("프로퍼티 그룹명 검증 – PROP_GROUP 상수 값")
void PROP_GROUP_상수_검증() {
assertEquals("AdapterErrorMessageHandler{CODE_CONVERT}",
TemplateCodeConvertAdapterErrorMsgHandler.PROP_GROUP);
}
@Test
@DisplayName("부모 PROP_GROUP 과 자식 PROP_GROUP 이 다른 그룹을 사용")
void 부모_자식_PROP_GROUP_다름() {
// 부모: "AdapterErrorMessageHandler"
// 자식: "AdapterErrorMessageHandler{CODE_CONVERT}"
assertNotEquals(
TemplateAdapterErrorMsgHandler.PROP_GROUP,
TemplateCodeConvertAdapterErrorMsgHandler.PROP_GROUP);
}
@Test
@DisplayName("변환 후에도 JSON 에 변환 대상 외 필드는 그대로 유지")
void 변환_후_비대상_필드_유지() throws Exception {
String template = "{\"resCode\":\"${MSG.MAIN_MSG.outp_msg_cd}\",\"extra\":\"value\",\"resMessage\":\"${MSG.MAIN_MSG.outp_msg_ctnt}\"}";
Properties cp = convertProps("GRP",
"resCode",
"resCode.40000", "TB_ERR_999");
setupMock("GRP", template, cp);
Object result = handler.generateNonStandardErrorResponseMessage(
"GRP", "ADP", new Properties(), null,
toEaiMessage(buildMsg("40000", "오류")));
assertEquals("{\"resCode\":\"TB_ERR_999\",\"extra\":\"value\",\"resMessage\":\"오류\"}", result);
}
@Test
@DisplayName("convert.fields 에 등록된 필드가 JSON 루트에 없어도 예외 없이 처리")
void JSON에_없는_필드_등록시_예외_없음() throws Exception {
String template = "{\"resCode\":\"${MSG.MAIN_MSG.outp_msg_cd}\"}";
Properties cp = convertProps("GRP",
"resCode,nonExistField",
"resCode.40000", "TB_ERR_999",
"nonExistField.40000", "MAPPED");
setupMock("GRP", template, cp);
Object result = handler.generateNonStandardErrorResponseMessage(
"GRP", "ADP", new Properties(), null,
toEaiMessage(buildMsg("40000", "")));
// nonExistField 는 getJsonValue 가 null 반환 → 건너뜀, 예외 없음
assertEquals("{\"resCode\":\"TB_ERR_999\"}", result);
}
@Test
@DisplayName("inboundAdapterGroupName 이 달라지면 다른 convert.fields 키를 사용")
void 다른_어댑터그룹_다른_키_사용() throws Exception {
String template = "{\"resCode\":\"${MSG.MAIN_MSG.outp_msg_cd}\"}";
Properties cp = new Properties();
cp.setProperty("GROUP_A.convert.fields", "resCode");
cp.setProperty("GROUP_A.resCode.40000", "A_MAPPED");
cp.setProperty("GROUP_B.convert.fields", "resCode");
cp.setProperty("GROUP_B.resCode.40000", "B_MAPPED");
PropManager mockProp = Mockito.mock(PropManager.class);
Mockito.when(mockProp.getProperty(
eq(TemplateAdapterErrorMsgHandler.PROP_GROUP),
anyString())).thenReturn(template);
Mockito.when(mockProp.getProperties(
eq(TemplateCodeConvertAdapterErrorMsgHandler.PROP_GROUP))).thenReturn(cp);
injectMockPropManager(mockProp);
EAIMessage msg = toEaiMessage(buildMsg("40000", ""));
Object resultA = handler.generateNonStandardErrorResponseMessage(
"GROUP_A", "ADP", new Properties(), null, msg);
Object resultB = handler.generateNonStandardErrorResponseMessage(
"GROUP_B", "ADP", new Properties(), null, msg);
assertEquals("{\"resCode\":\"A_MAPPED\"}", resultA);
assertEquals("{\"resCode\":\"B_MAPPED\"}", resultB);
}
}
}