Merge branch 'feature/template-adapter-error-msg-handler'
This commit is contained in:
+137
-15
@@ -9,7 +9,9 @@ import java.util.regex.Pattern;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import com.eactive.eai.adapter.AdapterPropManager;
|
||||
import com.eactive.eai.common.property.PropManager;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonParser;
|
||||
import com.eactive.eai.common.util.Logger;
|
||||
import com.eactive.eai.message.StandardItem;
|
||||
import com.eactive.eai.message.StandardMessage;
|
||||
@@ -23,10 +25,13 @@ import com.eactive.eai.message.StandardMessage;
|
||||
* StandardMessage 의 필드 값으로 치환한 결과를 반환한다.
|
||||
*
|
||||
* [변수 치환 규칙]
|
||||
* 1. "${callprop.키}" → callProp.getProperty("키") 로 치환
|
||||
* 2. "${경로}" → StandardMessage.findItemValue("경로") 로 치환
|
||||
* callprop 접두어가 있으면 callProp 을 우선 참조하고,
|
||||
* 없으면 StandardMessage 경로로 해석한다.
|
||||
* 1. "${callprop.키}" → callProp.getProperty("키") 로 치환
|
||||
* "${callprop.키:기본값}" → 키 없거나 callProp null 이면 기본값 반환
|
||||
* 2. "${callprop[경로]}" → StandardMessage 경로 값을 키로 callProp 간접 조회
|
||||
* "${callprop[경로]:기본값}" → 키 결정 실패 또는 키 없으면 기본값 반환
|
||||
* 3. "${경로}" → StandardMessage.findItemValue("경로") 로 치환
|
||||
* "${경로:기본값}" → 경로 값이 null/공백이면 기본값 반환
|
||||
* 모든 형태에서 기본값 미지정 시 빈 문자열을 반환한다.
|
||||
*
|
||||
* [배열 반복 문법] (MSG_LIST 등 GRID 타입)
|
||||
* {{#foreach MSG.MSG_LIST}}
|
||||
@@ -62,6 +67,12 @@ import com.eactive.eai.message.StandardMessage;
|
||||
* {{/foreach}}
|
||||
* </errors>
|
||||
* </error>
|
||||
*
|
||||
* [callProp 중첩 Properties 참조]
|
||||
* callProp 의 값이 Properties 인 경우 점(.) 으로 체인 탐색이 가능하다.
|
||||
* callProp.put("OUTBOUND", subProps) // subProps.setProperty("IF_ID", "SVC001")
|
||||
* → 템플릿에서 ${callprop.OUTBOUND.IF_ID} 로 참조
|
||||
* 중간 값이 Properties 가 아닌 경우 전체 keyPath 를 키로 폴백 조회한다.
|
||||
*/
|
||||
public class TemplateAdapterErrorMsgHandler implements AdapterErrorMessageHandler {
|
||||
|
||||
@@ -73,6 +84,9 @@ public class TemplateAdapterErrorMsgHandler implements AdapterErrorMessageHandle
|
||||
/** callProp 참조 변수 접두어: ${callprop.키} */
|
||||
static final String CALLPROP_PREFIX = "callprop.";
|
||||
|
||||
/** callProp 간접 참조 접두어: ${callprop[표준전문경로]} */
|
||||
static final String CALLPROP_INDIRECT_PREFIX = "callprop[";
|
||||
|
||||
/** ${path} 또는 ${callprop.key} 스칼라 변수 패턴 */
|
||||
private static final Pattern VAR_PATTERN =
|
||||
Pattern.compile("\\$\\{([^}]+)\\}");
|
||||
@@ -91,7 +105,7 @@ public class TemplateAdapterErrorMsgHandler implements AdapterErrorMessageHandle
|
||||
StandardMessage resStandardMessage) throws Exception {
|
||||
|
||||
String templateKey = inboundAdapterGroupName + ".template";
|
||||
String template = AdapterPropManager.getInstance().getProperty(PROP_GROUP, templateKey);
|
||||
String template = PropManager.getInstance().getProperty(PROP_GROUP, templateKey);
|
||||
|
||||
if (StringUtils.isBlank(template)) {
|
||||
if (logger.isWarn()) {
|
||||
@@ -135,18 +149,126 @@ public class TemplateAdapterErrorMsgHandler implements AdapterErrorMessageHandle
|
||||
|
||||
/**
|
||||
* 변수 표현식을 해석해 값을 반환한다.
|
||||
* - "callprop.키" → callProp.getProperty("키")
|
||||
* - 그 외 → StandardMessage.findItemValue(expr)
|
||||
* 모든 형태에서 ':기본값' 접미사를 지원한다. 기본값 미지정 시 빈 문자열.
|
||||
* - "callprop[경로][:기본값]" → 표준전문 경로 값을 키로 callProp 간접 조회
|
||||
* - "callprop.키[:기본값]" → callProp 직접/중첩 조회
|
||||
* - "경로[:기본값]" → StandardMessage.findItemValue(경로)
|
||||
*/
|
||||
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 "";
|
||||
if (expr.startsWith(CALLPROP_INDIRECT_PREFIX)) {
|
||||
return resolveIndirectCallProp(expr, msg, callProp);
|
||||
}
|
||||
return StringUtils.defaultString(msg.findItemValue(expr));
|
||||
if (expr.startsWith(CALLPROP_PREFIX)) {
|
||||
String rest = expr.substring(CALLPROP_PREFIX.length());
|
||||
int colonIdx = rest.indexOf(':');
|
||||
String keyPath = colonIdx >= 0 ? rest.substring(0, colonIdx) : rest;
|
||||
String defaultVal = colonIdx >= 0 ? rest.substring(colonIdx + 1) : "";
|
||||
if (callProp == null) return defaultVal;
|
||||
String value = resolveCallProp(callProp, keyPath);
|
||||
return value != null ? value : defaultVal;
|
||||
}
|
||||
int colonIdx = expr.indexOf(':');
|
||||
String path = colonIdx >= 0 ? expr.substring(0, colonIdx) : expr;
|
||||
String defaultVal = colonIdx >= 0 ? expr.substring(colonIdx + 1) : "";
|
||||
String value = resolveMessagePath(msg, path);
|
||||
return StringUtils.isNotEmpty(value) ? value : defaultVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* StandardMessage 경로를 해석한다.
|
||||
* 1. findItemValue(path) 로 직접 조회한다.
|
||||
* 2. null 이면 경로를 뒤에서부터 분리하며, 부모 경로 값이 JSON 문자열인 경우
|
||||
* 나머지 경로(subPath)를 키로 JSON 필드를 추출한다.
|
||||
* 예) DATA.BIZDATA.acctNo → findItemValue("DATA.BIZDATA") → JSON 파싱 → acctNo
|
||||
* DATA.BIZDATA.addr.city → findItemValue("DATA.BIZDATA") → JSON 파싱 → addr.city
|
||||
*/
|
||||
private String resolveMessagePath(StandardMessage msg, String path) {
|
||||
String value = msg.findItemValue(path);
|
||||
if (value != null) return value;
|
||||
|
||||
int dotIdx = path.lastIndexOf('.');
|
||||
while (dotIdx > 0) {
|
||||
String parentPath = path.substring(0, dotIdx);
|
||||
String subPath = path.substring(dotIdx + 1);
|
||||
String parentVal = msg.findItemValue(parentPath);
|
||||
if (StringUtils.isNotBlank(parentVal)) {
|
||||
String extracted = extractFromJson(parentVal, subPath);
|
||||
if (extracted != null) return extracted;
|
||||
}
|
||||
dotIdx = parentPath.lastIndexOf('.');
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* JSON 문자열에서 dot 구분 경로로 필드 값을 추출한다.
|
||||
* 중간 경로가 JSON 오브젝트면 계속 탐색하고, 최종 값이 primitive 면 문자열로,
|
||||
* 오브젝트/배열이면 JSON 문자열 그대로 반환한다. 파싱 실패 시 null 반환.
|
||||
*/
|
||||
private String extractFromJson(String json, String fieldPath) {
|
||||
try {
|
||||
JsonElement el = new JsonParser().parse(json);
|
||||
for (String part : fieldPath.split("\\.", -1)) {
|
||||
if (!el.isJsonObject()) return null;
|
||||
el = el.getAsJsonObject().get(part);
|
||||
if (el == null) return null;
|
||||
}
|
||||
return el.isJsonPrimitive() ? el.getAsString() : el.toString();
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ${callprop[경로]} 또는 ${callprop[경로]:기본값} 처리.
|
||||
* 1. 표준전문 경로로 callProp 키를 동적으로 결정한다.
|
||||
* 2. 결정된 키로 callProp 에서 값을 조회한다.
|
||||
* 3. 키 또는 값을 찾지 못하면 기본값(없으면 빈 문자열)을 반환한다.
|
||||
*/
|
||||
private String resolveIndirectCallProp(String expr, StandardMessage msg, Properties callProp) {
|
||||
int closeIdx = expr.indexOf(']');
|
||||
if (closeIdx < 0) return "";
|
||||
|
||||
String msgPath = expr.substring(CALLPROP_INDIRECT_PREFIX.length(), closeIdx);
|
||||
String defaultVal = (closeIdx + 1 < expr.length() && expr.charAt(closeIdx + 1) == ':')
|
||||
? expr.substring(closeIdx + 2)
|
||||
: "";
|
||||
|
||||
String key = msg.findItemValue(msgPath);
|
||||
if (StringUtils.isBlank(key)) return defaultVal;
|
||||
if (callProp == null) return defaultVal;
|
||||
|
||||
String value = resolveCallProp(callProp, key);
|
||||
return value != null ? value : defaultVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Properties 체인을 점(.) 구분자로 재귀 탐색한다.
|
||||
* - 첫 세그먼트의 값이 Properties 이면 나머지 경로로 재귀
|
||||
* - String 이면 반환, 그 외 Object 이면 toString() 반환
|
||||
* - 첫 세그먼트 값이 Properties 가 아닌 경우 전체 keyPath 로 폴백 조회
|
||||
*/
|
||||
private String resolveCallProp(Properties props, String keyPath) {
|
||||
int dotIdx = keyPath.indexOf('.');
|
||||
if (dotIdx < 0) {
|
||||
Object val = props.get(keyPath);
|
||||
if (val == null) return null;
|
||||
if (val instanceof String) return (String) val;
|
||||
return val.toString();
|
||||
}
|
||||
|
||||
String first = keyPath.substring(0, dotIdx);
|
||||
String rest = keyPath.substring(dotIdx + 1);
|
||||
Object val = props.get(first);
|
||||
if (val instanceof Properties) {
|
||||
return resolveCallProp((Properties) val, rest);
|
||||
}
|
||||
|
||||
// 중간 값이 Properties 가 아닌 경우: 전체 keyPath 를 키로 폴백
|
||||
Object direct = props.get(keyPath);
|
||||
if (direct == null) return null;
|
||||
if (direct instanceof String) return (String) direct;
|
||||
return direct.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+296
-10
@@ -15,7 +15,7 @@ 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.property.PropManager;
|
||||
import com.eactive.eai.common.util.ApplicationContextProvider;
|
||||
import com.eactive.eai.message.StandardItem;
|
||||
import com.eactive.eai.message.StandardMessage;
|
||||
@@ -26,7 +26,7 @@ import com.eactive.eai.message.StandardType;
|
||||
*
|
||||
* - render() : package-private 이므로 같은 패키지에서 직접 호출
|
||||
* - generateNonStandardErrorResponseMessage() : ApplicationContextProvider 에
|
||||
* mock ApplicationContext 를 리플렉션으로 주입하여 AdapterPropManager 격리
|
||||
* mock ApplicationContext 를 리플렉션으로 주입하여 PropManager 격리
|
||||
*/
|
||||
class TemplateAdapterErrorMsgHandlerTest {
|
||||
|
||||
@@ -160,6 +160,30 @@ class TemplateAdapterErrorMsgHandlerTest {
|
||||
|
||||
assertEquals("상세설명입니다", handler.render("${MSG.MAIN_MSG.outp_msg_desc}", msg, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("경로:기본값 – 경로 존재 시 값 반환")
|
||||
void 표준전문_기본값_경로존재() {
|
||||
StandardMessage msg = buildMsg("E001", "오류", "", null);
|
||||
|
||||
assertEquals("E001", handler.render("${MSG.MAIN_MSG.outp_msg_cd:DEFAULT}", msg, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("경로:기본값 – 경로 없으면 기본값 반환")
|
||||
void 표준전문_기본값_경로없음() {
|
||||
StandardMessage msg = buildMsg("E001", "오류", "", null);
|
||||
|
||||
assertEquals("UNKNOWN", handler.render("${NONE.PATH:UNKNOWN}", msg, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("경로:기본값 – 기본값이 빈 문자열")
|
||||
void 표준전문_기본값_빈문자열() {
|
||||
StandardMessage msg = buildMsg("E001", "오류", "", null);
|
||||
|
||||
assertEquals("", handler.render("${NONE.PATH:}", msg, null));
|
||||
}
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
@@ -210,12 +234,274 @@ class TemplateAdapterErrorMsgHandlerTest {
|
||||
|
||||
assertEquals("", handler.render("${callprop.ADAPTER_NAME}", msg, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("callProp 값이 Properties 인 경우 중첩 탐색")
|
||||
void callProp_중첩_Properties() {
|
||||
Properties sub = new Properties();
|
||||
sub.setProperty("IF_ID", "SVC001");
|
||||
sub.setProperty("SVC_NM", "조회서비스");
|
||||
|
||||
Properties callProp = new Properties();
|
||||
callProp.put("OUTBOUND", sub);
|
||||
callProp.setProperty("ADAPTER_NAME", "MY_ADAPTER");
|
||||
|
||||
StandardMessage msg = buildMsg("E001", "오류", "", null);
|
||||
String template = "{\"adapter\":\"${callprop.ADAPTER_NAME}\",\"ifId\":\"${callprop.OUTBOUND.IF_ID}\",\"svcNm\":\"${callprop.OUTBOUND.SVC_NM}\"}";
|
||||
|
||||
String result = handler.render(template, msg, callProp);
|
||||
|
||||
assertEquals("{\"adapter\":\"MY_ADAPTER\",\"ifId\":\"SVC001\",\"svcNm\":\"조회서비스\"}", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("중첩 Properties 3단계 탐색")
|
||||
void callProp_3단계_중첩() {
|
||||
Properties level2 = new Properties();
|
||||
level2.setProperty("CODE", "L2CODE");
|
||||
|
||||
Properties level1 = new Properties();
|
||||
level1.put("LEVEL2", level2);
|
||||
|
||||
Properties callProp = new Properties();
|
||||
callProp.put("LEVEL1", level1);
|
||||
|
||||
StandardMessage msg = buildMsg("E001", "오류", "", null);
|
||||
|
||||
assertEquals("L2CODE", handler.render("${callprop.LEVEL1.LEVEL2.CODE}", msg, callProp));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("중간 값이 Properties 가 아니면 전체 keyPath 로 폴백 조회")
|
||||
void callProp_중간값_비Properties_폴백() {
|
||||
Properties callProp = new Properties();
|
||||
callProp.setProperty("A.B", "FALLBACK_VALUE"); // 점 포함 키
|
||||
callProp.setProperty("A", "NOT_PROPS"); // String 값
|
||||
|
||||
StandardMessage msg = buildMsg("E001", "오류", "", null);
|
||||
|
||||
// A 는 String 이므로 Properties 가 아님 → "A.B" 전체 키로 폴백
|
||||
assertEquals("FALLBACK_VALUE", handler.render("${callprop.A.B}", msg, callProp));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("중첩 Properties 에서 없는 키는 빈 문자열")
|
||||
void callProp_중첩_없는키() {
|
||||
Properties sub = new Properties();
|
||||
sub.setProperty("EXIST", "VALUE");
|
||||
|
||||
Properties callProp = new Properties();
|
||||
callProp.put("SUB", sub);
|
||||
|
||||
StandardMessage msg = buildMsg("E001", "오류", "", null);
|
||||
|
||||
assertEquals("", handler.render("${callprop.SUB.NONE_KEY}", msg, callProp));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("callprop.키:기본값 – 키 존재 시 값 반환")
|
||||
void callProp_직접_기본값_키존재() {
|
||||
StandardMessage msg = buildMsg("E001", "오류", "", null);
|
||||
Properties callProp = props("ADAPTER_NAME", "MY_ADAPTER");
|
||||
|
||||
assertEquals("MY_ADAPTER", handler.render("${callprop.ADAPTER_NAME:DEFAULT}", msg, callProp));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("callprop.키:기본값 – 키 없으면 기본값 반환")
|
||||
void callProp_직접_기본값_키없음() {
|
||||
StandardMessage msg = buildMsg("E001", "오류", "", null);
|
||||
Properties callProp = new Properties();
|
||||
|
||||
assertEquals("DEFAULT_VAL", handler.render("${callprop.NONE_KEY:DEFAULT_VAL}", msg, callProp));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("callprop.키:기본값 – callProp null 이면 기본값 반환")
|
||||
void callProp_직접_기본값_null() {
|
||||
StandardMessage msg = buildMsg("E001", "오류", "", null);
|
||||
|
||||
assertEquals("FALLBACK", handler.render("${callprop.ADAPTER_NAME:FALLBACK}", msg, null));
|
||||
}
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// callProp 간접 참조 ${callprop[표준전문경로]} / ${callprop[경로]:기본값}
|
||||
// ================================================================
|
||||
|
||||
@Nested
|
||||
@DisplayName("callProp 간접 참조 ${callprop[경로]}")
|
||||
class IndirectCallPropSubstitution {
|
||||
|
||||
/** 표준전문에 cp_key 필드가 있는 메시지 */
|
||||
private StandardMessage buildMsgWithKey(String cpKey, String errCode) {
|
||||
StandardMessage msg = buildMsg(errCode, "오류", "", null);
|
||||
// MSG 그룹 아래 cp_key 필드 추가
|
||||
StandardItem msgGroup = msg.findItem("MSG");
|
||||
if (msgGroup != null) {
|
||||
msgGroup.addItem(field("cp_key", cpKey));
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("표준전문 경로 값으로 callProp 키를 결정해 값 조회")
|
||||
void 간접_참조_정상() {
|
||||
StandardMessage msg = buildMsgWithKey("ADAPTER_NAME", "E001");
|
||||
Properties callProp = props("ADAPTER_NAME", "MY_ADAPTER");
|
||||
String template = "${callprop[MSG.cp_key]}";
|
||||
|
||||
assertEquals("MY_ADAPTER", handler.render(template, msg, callProp));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("표준전문 경로 값이 없으면 기본값 반환")
|
||||
void 표준전문_경로_없으면_기본값() {
|
||||
StandardMessage msg = buildMsg("E001", "오류", "", null);
|
||||
Properties callProp = props("ADAPTER_NAME", "MY_ADAPTER");
|
||||
String template = "${callprop[MSG.NONE_KEY]:DEFAULT_ADAPTER}";
|
||||
|
||||
assertEquals("DEFAULT_ADAPTER", handler.render(template, msg, callProp));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("callProp 에 해당 키 없으면 기본값 반환")
|
||||
void callProp_키_없으면_기본값() {
|
||||
StandardMessage msg = buildMsgWithKey("MISSING_KEY", "E001");
|
||||
Properties callProp = props("OTHER_KEY", "OTHER_VALUE");
|
||||
String template = "${callprop[MSG.cp_key]:FALLBACK}";
|
||||
|
||||
assertEquals("FALLBACK", handler.render(template, msg, callProp));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("기본값 미지정 시 빈 문자열")
|
||||
void 기본값_없으면_빈문자열() {
|
||||
StandardMessage msg = buildMsg("E001", "오류", "", null);
|
||||
Properties callProp = props("ADAPTER_NAME", "MY_ADAPTER");
|
||||
String template = "${callprop[MSG.NONE_KEY]}";
|
||||
|
||||
assertEquals("", handler.render(template, msg, callProp));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("callProp 이 null 이면 기본값 반환")
|
||||
void callProp_null_기본값() {
|
||||
StandardMessage msg = buildMsgWithKey("ADAPTER_NAME", "E001");
|
||||
String template = "${callprop[MSG.cp_key]:NO_PROP}";
|
||||
|
||||
assertEquals("NO_PROP", handler.render(template, msg, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("기본값이 빈 문자열인 경우")
|
||||
void 기본값이_빈문자열() {
|
||||
StandardMessage msg = buildMsg("E001", "오류", "", null);
|
||||
Properties callProp = new Properties();
|
||||
String template = "${callprop[MSG.NONE_KEY]:}";
|
||||
|
||||
assertEquals("", handler.render(template, msg, callProp));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("간접 참조와 직접 참조 혼합")
|
||||
void 간접_직접_혼합() {
|
||||
StandardMessage msg = buildMsgWithKey("IF_ID_KEY", "E001");
|
||||
Properties callProp = props("IF_ID_KEY", "SVC_001", "ADAPTER_NAME", "MY_ADAPTER");
|
||||
String template = "{\"adapter\":\"${callprop.ADAPTER_NAME}\",\"ifId\":\"${callprop[MSG.cp_key]}\"}";
|
||||
|
||||
assertEquals("{\"adapter\":\"MY_ADAPTER\",\"ifId\":\"SVC_001\"}", handler.render(template, msg, callProp));
|
||||
}
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// 배열 반복 {{#foreach path}}...{{/foreach}}
|
||||
// ================================================================
|
||||
|
||||
// ================================================================
|
||||
// DATA 영역 변수 치환 ${DATA.xxx}
|
||||
// ================================================================
|
||||
|
||||
@Nested
|
||||
@DisplayName("DATA 영역 변수 치환 ${DATA.xxx}")
|
||||
class DataSectionSubstitution {
|
||||
|
||||
/**
|
||||
* DATA > BIZDATA(FIELD, JSON String value) 구조의 StandardMessage 생성.
|
||||
* BIZDATA 는 파싱 없이 JSON 문자열 그대로 세팅되는 형태.
|
||||
*/
|
||||
private StandardMessage buildMsgWithBizData(String bizDataJson) {
|
||||
StandardMessage msg = buildMsg("E001", "오류", "", null);
|
||||
|
||||
StandardItem dataGroup = group("DATA");
|
||||
dataGroup.addItem(field("BIZDATA", bizDataJson));
|
||||
msg.addItem(dataGroup);
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("DATA.BIZDATA JSON 필드 치환 – 단일 depth")
|
||||
void DATA_BIZDATA_JSON_필드_치환() {
|
||||
StandardMessage msg = buildMsgWithBizData("{\"acctNo\":\"1234567890\",\"custNm\":\"홍길동\"}");
|
||||
String template = "{\"acctNo\":\"${DATA.BIZDATA.acctNo}\",\"custNm\":\"${DATA.BIZDATA.custNm}\"}";
|
||||
|
||||
assertEquals("{\"acctNo\":\"1234567890\",\"custNm\":\"홍길동\"}",
|
||||
handler.render(template, msg, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("DATA.BIZDATA JSON 필드 치환 – 중첩 depth")
|
||||
void DATA_BIZDATA_JSON_중첩_필드_치환() {
|
||||
StandardMessage msg = buildMsgWithBizData("{\"addr\":{\"city\":\"서울\",\"zip\":\"12345\"}}");
|
||||
|
||||
assertEquals("서울", handler.render("${DATA.BIZDATA.addr.city}", msg, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("DATA.BIZDATA 원본 JSON 문자열 치환")
|
||||
void DATA_BIZDATA_원본_치환() {
|
||||
String rawJson = "{\"acctNo\":\"1234567890\"}";
|
||||
StandardMessage msg = buildMsgWithBizData(rawJson);
|
||||
|
||||
assertEquals(rawJson, handler.render("${DATA.BIZDATA}", msg, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("DATA.BIZDATA 와 MSG 영역 혼합 치환")
|
||||
void DATA_MSG_혼합_치환() {
|
||||
StandardMessage msg = buildMsgWithBizData("{\"acctNo\":\"9876543210\"}");
|
||||
String template = "{\"code\":\"${MSG.MAIN_MSG.outp_msg_cd}\",\"acctNo\":\"${DATA.BIZDATA.acctNo}\"}";
|
||||
|
||||
assertEquals("{\"code\":\"E001\",\"acctNo\":\"9876543210\"}",
|
||||
handler.render(template, msg, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("DATA.BIZDATA JSON 에 없는 필드는 빈 문자열")
|
||||
void DATA_BIZDATA_없는_JSON_필드_빈문자열() {
|
||||
StandardMessage msg = buildMsgWithBizData("{\"acctNo\":\"1234567890\"}");
|
||||
|
||||
assertEquals("", handler.render("${DATA.BIZDATA.noneField}", msg, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("DATA.BIZDATA JSON 에 없는 필드에 기본값 지정")
|
||||
void DATA_BIZDATA_없는_JSON_필드_기본값() {
|
||||
StandardMessage msg = buildMsgWithBizData("{\"acctNo\":\"1234567890\"}");
|
||||
|
||||
assertEquals("UNKNOWN", handler.render("${DATA.BIZDATA.noneField:UNKNOWN}", msg, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("DATA 영역 미설정 시 기본값 반환")
|
||||
void DATA_없으면_기본값() {
|
||||
StandardMessage msg = buildMsg("E001", "오류", "", null);
|
||||
|
||||
assertEquals("{}", handler.render("${DATA.BIZDATA:{}}", msg, null));
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("배열 반복 블록 {{#foreach}}")
|
||||
class ForeachBlock {
|
||||
@@ -341,16 +627,16 @@ class TemplateAdapterErrorMsgHandlerTest {
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// generateNonStandardErrorResponseMessage – AdapterPropManager mock
|
||||
// generateNonStandardErrorResponseMessage – PropManager mock
|
||||
// ================================================================
|
||||
|
||||
@Nested
|
||||
@DisplayName("generateNonStandardErrorResponseMessage")
|
||||
class GenerateMethod {
|
||||
|
||||
private void injectMockPropManager(AdapterPropManager mockPropManager) throws Exception {
|
||||
private void injectMockPropManager(PropManager mockPropManager) throws Exception {
|
||||
ApplicationContext mockCtx = Mockito.mock(ApplicationContext.class);
|
||||
Mockito.when(mockCtx.getBean(AdapterPropManager.class)).thenReturn(mockPropManager);
|
||||
Mockito.when(mockCtx.getBean(PropManager.class)).thenReturn(mockPropManager);
|
||||
Field ctxField = ApplicationContextProvider.class.getDeclaredField("context");
|
||||
ctxField.setAccessible(true);
|
||||
ctxField.set(null, mockCtx);
|
||||
@@ -359,7 +645,7 @@ class TemplateAdapterErrorMsgHandlerTest {
|
||||
@Test
|
||||
@DisplayName("템플릿 미설정 시 null 반환")
|
||||
void 템플릿_없으면_null() throws Exception {
|
||||
AdapterPropManager mockProp = Mockito.mock(AdapterPropManager.class);
|
||||
PropManager mockProp = Mockito.mock(PropManager.class);
|
||||
Mockito.when(mockProp.getProperty(anyString(), anyString())).thenReturn(null);
|
||||
injectMockPropManager(mockProp);
|
||||
|
||||
@@ -373,7 +659,7 @@ class TemplateAdapterErrorMsgHandlerTest {
|
||||
@Test
|
||||
@DisplayName("빈 템플릿 시 null 반환")
|
||||
void 빈_템플릿_null() throws Exception {
|
||||
AdapterPropManager mockProp = Mockito.mock(AdapterPropManager.class);
|
||||
PropManager mockProp = Mockito.mock(PropManager.class);
|
||||
Mockito.when(mockProp.getProperty(anyString(), anyString())).thenReturn(" ");
|
||||
injectMockPropManager(mockProp);
|
||||
|
||||
@@ -388,7 +674,7 @@ class TemplateAdapterErrorMsgHandlerTest {
|
||||
@DisplayName("템플릿 설정 시 StandardMessage 치환 결과 반환")
|
||||
void 템플릿_stdmsg_치환() throws Exception {
|
||||
String template = "{\"code\":\"${MSG.MAIN_MSG.outp_msg_cd}\"}";
|
||||
AdapterPropManager mockProp = Mockito.mock(AdapterPropManager.class);
|
||||
PropManager mockProp = Mockito.mock(PropManager.class);
|
||||
Mockito.when(mockProp.getProperty(
|
||||
eq(TemplateAdapterErrorMsgHandler.PROP_GROUP),
|
||||
eq("MY_GROUP.template"))).thenReturn(template);
|
||||
@@ -405,7 +691,7 @@ class TemplateAdapterErrorMsgHandlerTest {
|
||||
@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);
|
||||
PropManager mockProp = Mockito.mock(PropManager.class);
|
||||
Mockito.when(mockProp.getProperty(
|
||||
eq(TemplateAdapterErrorMsgHandler.PROP_GROUP),
|
||||
eq("MY_GROUP.template"))).thenReturn(template);
|
||||
@@ -423,7 +709,7 @@ class TemplateAdapterErrorMsgHandlerTest {
|
||||
@Test
|
||||
@DisplayName("템플릿 키는 {adapterGroupId}.template 형식으로 조회")
|
||||
void 템플릿_키_형식_검증() throws Exception {
|
||||
AdapterPropManager mockProp = Mockito.mock(AdapterPropManager.class);
|
||||
PropManager mockProp = Mockito.mock(PropManager.class);
|
||||
Mockito.when(mockProp.getProperty(anyString(), anyString())).thenReturn(null);
|
||||
injectMockPropManager(mockProp);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user