feat: 템플릿 기반 어댑터 에러 메시지 핸들러 추가
AdapterPropManager "AdapterErrorMessageHandler" 그룹의
{adapterGroupId}.template 키로 등록된 템플릿을 읽어
StandardMessage 및 callProp 값으로 치환한 에러 응답을 생성한다.
변수 치환 규칙:
- ${callprop.키} → callProp.getProperty("키")
- ${MSG.MAIN_MSG.경로} → StandardMessage.findItemValue("경로")
- {{#foreach 경로}}...{{/foreach}} → GRID 배열 반복 (쉼표 구분)
JSON/XML 템플릿 모두 지원
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,197 @@
|
||||
package com.eactive.eai.adapter.handler;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import com.eactive.eai.adapter.AdapterPropManager;
|
||||
import com.eactive.eai.common.util.Logger;
|
||||
import com.eactive.eai.message.StandardItem;
|
||||
import com.eactive.eai.message.StandardMessage;
|
||||
|
||||
/**
|
||||
* 템플릿 기반 어댑터 에러 메시지 핸들러.
|
||||
*
|
||||
* [프로퍼티 설정]
|
||||
* AdapterPropManager 의 "AdapterErrorMessageHandler" 프로퍼티 그룹에서
|
||||
* "{adapterGroupId}.template" 키로 등록된 템플릿 문자열을 읽어
|
||||
* StandardMessage 의 필드 값으로 치환한 결과를 반환한다.
|
||||
*
|
||||
* [변수 치환 규칙]
|
||||
* 1. "${callprop.키}" → callProp.getProperty("키") 로 치환
|
||||
* 2. "${경로}" → StandardMessage.findItemValue("경로") 로 치환
|
||||
* callprop 접두어가 있으면 callProp 을 우선 참조하고,
|
||||
* 없으면 StandardMessage 경로로 해석한다.
|
||||
*
|
||||
* [배열 반복 문법] (MSG_LIST 등 GRID 타입)
|
||||
* {{#foreach MSG.MSG_LIST}}
|
||||
* { "code":"${outp_msg_cd}", "msg":"${outp_msg_ctnt}" }
|
||||
* {{/foreach}}
|
||||
* → MSG_LIST 각 행을 반복하며 행 내 필드명(접두어 없이)으로 치환,
|
||||
* 반복 결과를 쉼표(,)로 이어 붙임
|
||||
*
|
||||
* [사용 예 – JSON 템플릿]
|
||||
* {
|
||||
* "adapterName": "${callprop.ADAPTER_NAME}",
|
||||
* "resultCode": "${MSG.MAIN_MSG.outp_msg_cd}",
|
||||
* "resultMsg": "${MSG.MAIN_MSG.outp_msg_ctnt}",
|
||||
* "errors": [
|
||||
* {{#foreach MSG.MSG_LIST}}
|
||||
* {
|
||||
* "code": "${outp_msg_cd}",
|
||||
* "msg": "${outp_msg_ctnt}",
|
||||
* "desc": "${outp_msg_desc}",
|
||||
* "field": "${err_occu_item_nm}"
|
||||
* }
|
||||
* {{/foreach}}
|
||||
* ]
|
||||
* }
|
||||
*
|
||||
* [사용 예 – XML 템플릿]
|
||||
* <error>
|
||||
* <adapter>${callprop.ADAPTER_NAME}</adapter>
|
||||
* <code>${MSG.MAIN_MSG.outp_msg_cd}</code>
|
||||
* <errors>
|
||||
* {{#foreach MSG.MSG_LIST}}
|
||||
* <item><code>${outp_msg_cd}</code><msg>${outp_msg_ctnt}</msg></item>
|
||||
* {{/foreach}}
|
||||
* </errors>
|
||||
* </error>
|
||||
*/
|
||||
public class TemplateAdapterErrorMsgHandler implements AdapterErrorMessageHandler {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER);
|
||||
|
||||
/** AdapterPropManager 에서 템플릿을 조회할 프로퍼티 그룹 이름 */
|
||||
static final String PROP_GROUP = "AdapterErrorMessageHandler";
|
||||
|
||||
/** callProp 참조 변수 접두어: ${callprop.키} */
|
||||
static final String CALLPROP_PREFIX = "callprop.";
|
||||
|
||||
/** ${path} 또는 ${callprop.key} 스칼라 변수 패턴 */
|
||||
private static final Pattern VAR_PATTERN =
|
||||
Pattern.compile("\\$\\{([^}]+)\\}");
|
||||
|
||||
/** {{#foreach path}}...{{/foreach}} 배열 반복 블록 패턴 */
|
||||
private static final Pattern FOREACH_PATTERN =
|
||||
Pattern.compile("\\{\\{#foreach\\s+([^}]+)\\}\\}(.*?)\\{\\{/foreach\\}\\}",
|
||||
Pattern.DOTALL);
|
||||
|
||||
@Override
|
||||
public Object generateNonStandardErrorResponseMessage(
|
||||
String inboundAdapterGroupName,
|
||||
String inboundAdapterName,
|
||||
Properties callProp,
|
||||
Object outboundRequestData,
|
||||
StandardMessage resStandardMessage) throws Exception {
|
||||
|
||||
String templateKey = inboundAdapterGroupName + ".template";
|
||||
String template = AdapterPropManager.getInstance().getProperty(PROP_GROUP, templateKey);
|
||||
|
||||
if (StringUtils.isBlank(template)) {
|
||||
if (logger.isWarn()) {
|
||||
logger.warn("TemplateAdapterErrorMsgHandler] template not found. group=" + PROP_GROUP
|
||||
+ ", key=" + templateKey);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
return render(template, resStandardMessage, callProp);
|
||||
}
|
||||
|
||||
/**
|
||||
* 템플릿에 StandardMessage 및 callProp 값을 치환해 최종 문자열을 반환한다.
|
||||
* package-private: 단위 테스트에서 직접 호출 가능
|
||||
*/
|
||||
String render(String template, StandardMessage msg, Properties callProp) {
|
||||
// 1단계: {{#foreach path}}...{{/foreach}} 배열 반복 처리
|
||||
StringBuffer sb = new StringBuffer();
|
||||
Matcher foreachMatcher = FOREACH_PATTERN.matcher(template);
|
||||
while (foreachMatcher.find()) {
|
||||
String arrayPath = foreachMatcher.group(1).trim();
|
||||
String blockContent = foreachMatcher.group(2);
|
||||
String rendered = renderForeach(arrayPath, blockContent, msg);
|
||||
foreachMatcher.appendReplacement(sb, Matcher.quoteReplacement(rendered));
|
||||
}
|
||||
foreachMatcher.appendTail(sb);
|
||||
|
||||
// 2단계: 나머지 ${path} / ${callprop.키} 스칼라 변수 치환
|
||||
String intermediate = sb.toString();
|
||||
StringBuffer result = new StringBuffer();
|
||||
Matcher varMatcher = VAR_PATTERN.matcher(intermediate);
|
||||
while (varMatcher.find()) {
|
||||
String expr = varMatcher.group(1).trim();
|
||||
String value = resolveScalar(expr, msg, callProp);
|
||||
varMatcher.appendReplacement(result, Matcher.quoteReplacement(value));
|
||||
}
|
||||
varMatcher.appendTail(result);
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 변수 표현식을 해석해 값을 반환한다.
|
||||
* - "callprop.키" → callProp.getProperty("키")
|
||||
* - 그 외 → StandardMessage.findItemValue(expr)
|
||||
*/
|
||||
private String resolveScalar(String expr, StandardMessage msg, Properties callProp) {
|
||||
if (expr.startsWith(CALLPROP_PREFIX)) {
|
||||
String key = expr.substring(CALLPROP_PREFIX.length());
|
||||
if (callProp != null) {
|
||||
return StringUtils.defaultString(callProp.getProperty(key));
|
||||
}
|
||||
return "";
|
||||
}
|
||||
return StringUtils.defaultString(msg.findItemValue(expr));
|
||||
}
|
||||
|
||||
/**
|
||||
* GRID 타입 배열 아이템을 반복하며 blockContent 를 각 행으로 치환,
|
||||
* 결과를 쉼표로 이어 반환한다. (foreach 블록 내부는 callProp 참조 미지원)
|
||||
*/
|
||||
private String renderForeach(String arrayPath, String blockContent, StandardMessage msg) {
|
||||
StandardItem arrayItem = msg.findItem(arrayPath);
|
||||
if (arrayItem == null) {
|
||||
if (logger.isWarn()) logger.warn("TemplateAdapterErrorMsgHandler] foreach path not found: " + arrayPath);
|
||||
return "";
|
||||
}
|
||||
|
||||
List<LinkedHashMap<String, StandardItem>> rows = arrayItem.getList();
|
||||
if (rows == null || rows.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
List<String> renderedItems = new ArrayList<>();
|
||||
for (LinkedHashMap<String, StandardItem> row : rows) {
|
||||
String rowText = renderRow(blockContent, row);
|
||||
if (StringUtils.isNotBlank(rowText)) {
|
||||
renderedItems.add(rowText);
|
||||
}
|
||||
}
|
||||
return String.join(",", renderedItems);
|
||||
}
|
||||
|
||||
/**
|
||||
* 배열 한 행(LinkedHashMap) 기준으로 blockContent 의 ${fieldName} 을 치환한다.
|
||||
* fieldName 은 경로 없이 해당 행의 컬럼명만 사용한다.
|
||||
*/
|
||||
private String renderRow(String blockContent, LinkedHashMap<String, StandardItem> row) {
|
||||
StringBuffer result = new StringBuffer();
|
||||
Matcher varMatcher = VAR_PATTERN.matcher(blockContent);
|
||||
while (varMatcher.find()) {
|
||||
String fieldName = varMatcher.group(1).trim();
|
||||
String value = "";
|
||||
StandardItem item = row.get(fieldName);
|
||||
if (item != null) {
|
||||
value = StringUtils.defaultString(item.getValue());
|
||||
}
|
||||
varMatcher.appendReplacement(result, Matcher.quoteReplacement(value));
|
||||
}
|
||||
varMatcher.appendTail(result);
|
||||
return result.toString().trim();
|
||||
}
|
||||
}
|
||||
+439
@@ -0,0 +1,439 @@
|
||||
package com.eactive.eai.adapter.handler;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.LinkedHashMap;
|
||||
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.adapter.AdapterPropManager;
|
||||
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;
|
||||
|
||||
/**
|
||||
* TemplateAdapterErrorMsgHandler 단위테스트
|
||||
*
|
||||
* - render() : package-private 이므로 같은 패키지에서 직접 호출
|
||||
* - generateNonStandardErrorResponseMessage() : ApplicationContextProvider 에
|
||||
* mock ApplicationContext 를 리플렉션으로 주입하여 AdapterPropManager 격리
|
||||
*/
|
||||
class TemplateAdapterErrorMsgHandlerTest {
|
||||
|
||||
private TemplateAdapterErrorMsgHandler handler;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
handler = new TemplateAdapterErrorMsgHandler();
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// StandardMessage 빌더 헬퍼
|
||||
// ================================================================
|
||||
|
||||
/** FIELD 아이템 (type=2) */
|
||||
private static StandardItem field(String name, String value) {
|
||||
return new StandardItem(name, 3, StandardType.FIELD, 1, 0, 300, 1, "", "", value, name);
|
||||
}
|
||||
|
||||
/** GROUP 아이템 (type=3, size=1 → 활성) */
|
||||
private static StandardItem group(String name) {
|
||||
return new StandardItem(name, 2, StandardType.GROUP, 1, 0, 0, 1, "", "", null, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* DJB 표준전문 MSG 영역을 모사한 StandardMessage 생성.
|
||||
*
|
||||
* 구조:
|
||||
* MSG (GROUP)
|
||||
* MAIN_MSG (GROUP)
|
||||
* outp_msg_cd, outp_msg_ctnt, outp_msg_desc
|
||||
* MSG_LIST (GRID)
|
||||
* row[n]: outp_msg_cd, outp_msg_ctnt, outp_msg_desc, err_occu_item_nm
|
||||
*
|
||||
* @param listRows null 또는 빈 배열이면 MSG_LIST 행 없음
|
||||
*/
|
||||
private StandardMessage buildMsg(String errCode, String errMsg, String errDesc,
|
||||
String[][] listRows) {
|
||||
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));
|
||||
mainMsg.addItem(field("outp_msg_desc", errDesc));
|
||||
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", ""));
|
||||
msgList.addItem(field("outp_msg_desc", ""));
|
||||
msgList.addItem(field("err_occu_item_nm", ""));
|
||||
|
||||
if (listRows != null) {
|
||||
for (String[] row : listRows) {
|
||||
LinkedHashMap<String, StandardItem> rowMap = new LinkedHashMap<>();
|
||||
rowMap.put("outp_msg_cd", field("outp_msg_cd", get(row, 0)));
|
||||
rowMap.put("outp_msg_ctnt", field("outp_msg_ctnt", get(row, 1)));
|
||||
rowMap.put("outp_msg_desc", field("outp_msg_desc", get(row, 2)));
|
||||
rowMap.put("err_occu_item_nm", field("err_occu_item_nm", get(row, 3)));
|
||||
msgList.addArray(rowMap);
|
||||
}
|
||||
}
|
||||
msgGroup.addItem(msgList);
|
||||
msg.addItem(msgGroup);
|
||||
return msg;
|
||||
}
|
||||
|
||||
private static String get(String[] arr, int idx) {
|
||||
return (arr != null && arr.length > idx) ? arr[idx] : "";
|
||||
}
|
||||
|
||||
private static Properties props(String... keyValues) {
|
||||
Properties p = new Properties();
|
||||
for (int i = 0; i < keyValues.length - 1; i += 2) {
|
||||
p.setProperty(keyValues[i], keyValues[i + 1]);
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// 스칼라 변수 치환 ${path}
|
||||
// ================================================================
|
||||
|
||||
@Nested
|
||||
@DisplayName("StandardMessage 스칼라 변수 치환 ${path}")
|
||||
class ScalarSubstitution {
|
||||
|
||||
@Test
|
||||
@DisplayName("단순 JSON 템플릿 치환")
|
||||
void 단순_치환() {
|
||||
StandardMessage msg = buildMsg("E001", "오류발생", "상세설명", null);
|
||||
String template = "{\"code\":\"${MSG.MAIN_MSG.outp_msg_cd}\",\"msg\":\"${MSG.MAIN_MSG.outp_msg_ctnt}\"}";
|
||||
|
||||
String result = handler.render(template, msg, null);
|
||||
|
||||
assertEquals("{\"code\":\"E001\",\"msg\":\"오류발생\"}", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("동일 변수 중복 치환")
|
||||
void 동일_변수_중복_치환() {
|
||||
StandardMessage msg = buildMsg("E001", "오류", "", null);
|
||||
String template = "${MSG.MAIN_MSG.outp_msg_cd}/${MSG.MAIN_MSG.outp_msg_cd}";
|
||||
|
||||
assertEquals("E001/E001", handler.render(template, msg, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("존재하지 않는 경로는 빈 문자열로 치환")
|
||||
void 없는_경로_빈_문자열() {
|
||||
StandardMessage msg = buildMsg("E001", "오류", "", null);
|
||||
|
||||
assertEquals("", handler.render("${NONE.PATH}", msg, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("변수 없는 템플릿은 원본 그대로 반환")
|
||||
void 변수_없는_템플릿_원본() {
|
||||
StandardMessage msg = buildMsg("E001", "오류", "", null);
|
||||
|
||||
assertEquals("{\"static\":\"value\"}", handler.render("{\"static\":\"value\"}", msg, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("outp_msg_desc 필드 치환")
|
||||
void outp_msg_desc_치환() {
|
||||
StandardMessage msg = buildMsg("E001", "오류", "상세설명입니다", null);
|
||||
|
||||
assertEquals("상세설명입니다", handler.render("${MSG.MAIN_MSG.outp_msg_desc}", msg, null));
|
||||
}
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// callProp 변수 치환 ${callprop.키}
|
||||
// ================================================================
|
||||
|
||||
@Nested
|
||||
@DisplayName("callProp 변수 치환 ${callprop.키}")
|
||||
class CallPropSubstitution {
|
||||
|
||||
@Test
|
||||
@DisplayName("callProp 값을 템플릿에서 참조")
|
||||
void callProp_단순_참조() {
|
||||
StandardMessage msg = buildMsg("E001", "오류", "", null);
|
||||
Properties callProp = props("ADAPTER_NAME", "MY_ADAPTER", "IF_ID", "SVC001");
|
||||
String template = "{\"adapter\":\"${callprop.ADAPTER_NAME}\",\"ifId\":\"${callprop.IF_ID}\"}";
|
||||
|
||||
String result = handler.render(template, msg, callProp);
|
||||
|
||||
assertEquals("{\"adapter\":\"MY_ADAPTER\",\"ifId\":\"SVC001\"}", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("callProp 과 StandardMessage 혼합 참조")
|
||||
void callProp_stdmsg_혼합() {
|
||||
StandardMessage msg = buildMsg("E001", "오류발생", "", null);
|
||||
Properties callProp = props("ADAPTER_NAME", "MY_ADAPTER");
|
||||
String template = "{\"adapter\":\"${callprop.ADAPTER_NAME}\",\"code\":\"${MSG.MAIN_MSG.outp_msg_cd}\"}";
|
||||
|
||||
String result = handler.render(template, msg, callProp);
|
||||
|
||||
assertEquals("{\"adapter\":\"MY_ADAPTER\",\"code\":\"E001\"}", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("callProp 에 없는 키는 빈 문자열")
|
||||
void callProp_없는_키_빈문자열() {
|
||||
StandardMessage msg = buildMsg("E001", "오류", "", null);
|
||||
Properties callProp = new Properties();
|
||||
|
||||
assertEquals("", handler.render("${callprop.NONE_KEY}", msg, callProp));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("callProp 이 null 이면 빈 문자열")
|
||||
void callProp_null_빈문자열() {
|
||||
StandardMessage msg = buildMsg("E001", "오류", "", null);
|
||||
|
||||
assertEquals("", handler.render("${callprop.ADAPTER_NAME}", msg, null));
|
||||
}
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// 배열 반복 {{#foreach path}}...{{/foreach}}
|
||||
// ================================================================
|
||||
|
||||
@Nested
|
||||
@DisplayName("배열 반복 블록 {{#foreach}}")
|
||||
class ForeachBlock {
|
||||
|
||||
@Test
|
||||
@DisplayName("MSG_LIST 1건 렌더링")
|
||||
void 단건_렌더링() {
|
||||
StandardMessage msg = buildMsg("E001", "대표오류", "",
|
||||
new String[][]{{"E001", "메시지A", "설명A", "fieldA"}});
|
||||
String template = "[{{#foreach MSG.MSG_LIST}}{\"c\":\"${outp_msg_cd}\",\"m\":\"${outp_msg_ctnt}\"}{{/foreach}}]";
|
||||
|
||||
assertEquals("[{\"c\":\"E001\",\"m\":\"메시지A\"}]", handler.render(template, msg, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("MSG_LIST 2건 → 쉼표로 연결")
|
||||
void 다건_쉼표_연결() {
|
||||
StandardMessage msg = buildMsg("E001", "대표오류", "",
|
||||
new String[][]{
|
||||
{"E001", "오류A", "설명A", "fieldA"},
|
||||
{"E002", "오류B", "설명B", "fieldB"}
|
||||
});
|
||||
String template = "[{{#foreach MSG.MSG_LIST}}{\"c\":\"${outp_msg_cd}\"}{{/foreach}}]";
|
||||
|
||||
assertEquals("[{\"c\":\"E001\"},{\"c\":\"E002\"}]", handler.render(template, msg, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("MSG_LIST 가 비어있으면 foreach 결과는 빈 문자열")
|
||||
void 빈_배열() {
|
||||
StandardMessage msg = buildMsg("E001", "오류", "", new String[][]{});
|
||||
String template = "[{{#foreach MSG.MSG_LIST}}{\"c\":\"${outp_msg_cd}\"}{{/foreach}}]";
|
||||
|
||||
assertEquals("[]", handler.render(template, msg, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("foreach 경로가 없으면 빈 문자열")
|
||||
void 없는_경로() {
|
||||
StandardMessage msg = buildMsg("E001", "오류", "", null);
|
||||
String template = "[{{#foreach NONE.LIST}}{\"c\":\"${outp_msg_cd}\"}{{/foreach}}]";
|
||||
|
||||
assertEquals("[]", handler.render(template, msg, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("err_occu_item_nm 필드 포함 렌더링")
|
||||
void err_occu_item_nm_포함() {
|
||||
StandardMessage msg = buildMsg("E001", "오류", "",
|
||||
new String[][]{{"E001", "오류A", "설명A", "accountNo"}});
|
||||
String template = "{{#foreach MSG.MSG_LIST}}{\"field\":\"${err_occu_item_nm}\"}{{/foreach}}";
|
||||
|
||||
assertEquals("{\"field\":\"accountNo\"}", handler.render(template, msg, null));
|
||||
}
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// 혼합 템플릿 (스칼라 + callProp + 배열)
|
||||
// ================================================================
|
||||
|
||||
@Nested
|
||||
@DisplayName("혼합 템플릿")
|
||||
class MixedTemplate {
|
||||
|
||||
@Test
|
||||
@DisplayName("JSON – MAIN_MSG + callProp + MSG_LIST 2건")
|
||||
void JSON_전체_혼합() {
|
||||
StandardMessage msg = buildMsg("E001", "대표오류", "대표설명",
|
||||
new String[][]{
|
||||
{"E001", "오류A", "설명A", "fieldA"},
|
||||
{"E002", "오류B", "설명B", "fieldB"}
|
||||
});
|
||||
Properties callProp = props("ADAPTER_NAME", "TEST_ADAPTER");
|
||||
|
||||
String template =
|
||||
"{" +
|
||||
"\"adapter\":\"${callprop.ADAPTER_NAME}\"," +
|
||||
"\"resultCode\":\"${MSG.MAIN_MSG.outp_msg_cd}\"," +
|
||||
"\"resultMsg\":\"${MSG.MAIN_MSG.outp_msg_ctnt}\"," +
|
||||
"\"errors\":[{{#foreach MSG.MSG_LIST}}" +
|
||||
"{\"code\":\"${outp_msg_cd}\",\"msg\":\"${outp_msg_ctnt}\",\"field\":\"${err_occu_item_nm}\"}" +
|
||||
"{{/foreach}}]" +
|
||||
"}";
|
||||
|
||||
String result = handler.render(template, msg, callProp);
|
||||
|
||||
assertEquals(
|
||||
"{\"adapter\":\"TEST_ADAPTER\"," +
|
||||
"\"resultCode\":\"E001\",\"resultMsg\":\"대표오류\"," +
|
||||
"\"errors\":[{\"code\":\"E001\",\"msg\":\"오류A\",\"field\":\"fieldA\"}," +
|
||||
"{\"code\":\"E002\",\"msg\":\"오류B\",\"field\":\"fieldB\"}]}",
|
||||
result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("XML – 스칼라 + callProp 치환")
|
||||
void XML_스칼라_callProp() {
|
||||
StandardMessage msg = buildMsg("E001", "오류발생", "", null);
|
||||
Properties callProp = props("IF_ID", "IF_001");
|
||||
String template = "<error><ifId>${callprop.IF_ID}</ifId><code>${MSG.MAIN_MSG.outp_msg_cd}</code></error>";
|
||||
|
||||
assertEquals("<error><ifId>IF_001</ifId><code>E001</code></error>",
|
||||
handler.render(template, msg, callProp));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("XML – foreach 배열")
|
||||
void XML_foreach_배열() {
|
||||
StandardMessage msg = buildMsg("E001", "오류", "",
|
||||
new String[][]{{"E001", "오류A", "", ""}, {"E002", "오류B", "", ""}});
|
||||
String template =
|
||||
"<errors>{{#foreach MSG.MSG_LIST}}" +
|
||||
"<item><code>${outp_msg_cd}</code><msg>${outp_msg_ctnt}</msg></item>" +
|
||||
"{{/foreach}}</errors>";
|
||||
|
||||
assertEquals(
|
||||
"<errors>" +
|
||||
"<item><code>E001</code><msg>오류A</msg></item>," +
|
||||
"<item><code>E002</code><msg>오류B</msg></item>" +
|
||||
"</errors>",
|
||||
handler.render(template, msg, null));
|
||||
}
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// generateNonStandardErrorResponseMessage – AdapterPropManager mock
|
||||
// ================================================================
|
||||
|
||||
@Nested
|
||||
@DisplayName("generateNonStandardErrorResponseMessage")
|
||||
class GenerateMethod {
|
||||
|
||||
private void injectMockPropManager(AdapterPropManager mockPropManager) throws Exception {
|
||||
ApplicationContext mockCtx = Mockito.mock(ApplicationContext.class);
|
||||
Mockito.when(mockCtx.getBean(AdapterPropManager.class)).thenReturn(mockPropManager);
|
||||
Field ctxField = ApplicationContextProvider.class.getDeclaredField("context");
|
||||
ctxField.setAccessible(true);
|
||||
ctxField.set(null, mockCtx);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("템플릿 미설정 시 null 반환")
|
||||
void 템플릿_없으면_null() throws Exception {
|
||||
AdapterPropManager mockProp = Mockito.mock(AdapterPropManager.class);
|
||||
Mockito.when(mockProp.getProperty(anyString(), anyString())).thenReturn(null);
|
||||
injectMockPropManager(mockProp);
|
||||
|
||||
StandardMessage msg = buildMsg("E001", "오류", "", null);
|
||||
Object result = handler.generateNonStandardErrorResponseMessage(
|
||||
"TEST_GROUP", "TEST_ADAPTER", new Properties(), null, msg);
|
||||
|
||||
assertNull(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("빈 템플릿 시 null 반환")
|
||||
void 빈_템플릿_null() throws Exception {
|
||||
AdapterPropManager mockProp = Mockito.mock(AdapterPropManager.class);
|
||||
Mockito.when(mockProp.getProperty(anyString(), anyString())).thenReturn(" ");
|
||||
injectMockPropManager(mockProp);
|
||||
|
||||
StandardMessage msg = buildMsg("E001", "오류", "", null);
|
||||
Object result = handler.generateNonStandardErrorResponseMessage(
|
||||
"TEST_GROUP", "TEST_ADAPTER", new Properties(), null, msg);
|
||||
|
||||
assertNull(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("템플릿 설정 시 StandardMessage 치환 결과 반환")
|
||||
void 템플릿_stdmsg_치환() throws Exception {
|
||||
String template = "{\"code\":\"${MSG.MAIN_MSG.outp_msg_cd}\"}";
|
||||
AdapterPropManager mockProp = Mockito.mock(AdapterPropManager.class);
|
||||
Mockito.when(mockProp.getProperty(
|
||||
eq(TemplateAdapterErrorMsgHandler.PROP_GROUP),
|
||||
eq("MY_GROUP.template"))).thenReturn(template);
|
||||
injectMockPropManager(mockProp);
|
||||
|
||||
StandardMessage msg = buildMsg("E999", "테스트오류", "", null);
|
||||
Object result = handler.generateNonStandardErrorResponseMessage(
|
||||
"MY_GROUP", "MY_ADAPTER", new Properties(), null, msg);
|
||||
|
||||
assertEquals("{\"code\":\"E999\"}", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("callProp 값이 템플릿에 치환되어 반환")
|
||||
void 템플릿_callProp_치환() throws Exception {
|
||||
String template = "{\"adapter\":\"${callprop.ADAPTER_NAME}\",\"code\":\"${MSG.MAIN_MSG.outp_msg_cd}\"}";
|
||||
AdapterPropManager mockProp = Mockito.mock(AdapterPropManager.class);
|
||||
Mockito.when(mockProp.getProperty(
|
||||
eq(TemplateAdapterErrorMsgHandler.PROP_GROUP),
|
||||
eq("MY_GROUP.template"))).thenReturn(template);
|
||||
injectMockPropManager(mockProp);
|
||||
|
||||
StandardMessage msg = buildMsg("E999", "테스트오류", "", null);
|
||||
Properties callProp = props("ADAPTER_NAME", "REAL_ADAPTER");
|
||||
|
||||
Object result = handler.generateNonStandardErrorResponseMessage(
|
||||
"MY_GROUP", "MY_ADAPTER", callProp, null, msg);
|
||||
|
||||
assertEquals("{\"adapter\":\"REAL_ADAPTER\",\"code\":\"E999\"}", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("템플릿 키는 {adapterGroupId}.template 형식으로 조회")
|
||||
void 템플릿_키_형식_검증() throws Exception {
|
||||
AdapterPropManager mockProp = Mockito.mock(AdapterPropManager.class);
|
||||
Mockito.when(mockProp.getProperty(anyString(), anyString())).thenReturn(null);
|
||||
injectMockPropManager(mockProp);
|
||||
|
||||
StandardMessage msg = buildMsg("E001", "오류", "", null);
|
||||
handler.generateNonStandardErrorResponseMessage(
|
||||
"SAMPLE_GROUP", "ANY_ADAPTER", new Properties(), null, msg);
|
||||
|
||||
Mockito.verify(mockProp).getProperty(
|
||||
TemplateAdapterErrorMsgHandler.PROP_GROUP,
|
||||
"SAMPLE_GROUP.template");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user