TemplateAdapterErrorMsgHandler 구현 작업 내역
-인바운드/아웃바운드/내부 오류 발생 시 어댑터별로 프로퍼티에 등록된 템플릿을 사용하여 커스텀 에러 응답 메시지를 생성하는 TemplateAdapterErrorMsgHandler 구현 및 연동 작업. - ExceptionHandler 변경 인바운드 어댑터에 ERR_MSG_HANDLER 프로퍼티가 설정된 경우 우선 실행 - ApiController에서 사용할 수 있는 generateNonStandardInboundErrorResponseMessage 추가, Controller에서도 Adapter가 식별되면 설정된 핸들러에 의해 에러메시지 행성 가능
This commit is contained in:
@@ -2,9 +2,16 @@ package com.eactive.eai.adapter.handler;
|
|||||||
|
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
|
|
||||||
import com.eactive.eai.message.StandardMessage;
|
import com.eactive.eai.common.message.EAIMessage;
|
||||||
|
|
||||||
public interface AdapterErrorMessageHandler {
|
public interface AdapterErrorMessageHandler {
|
||||||
public Object generateNonStandardErrorResponseMessage(String inboudnAdapterGroupName, String inboudnAdapterName, Properties callProp, Object outboundRequestData, StandardMessage resStandardMessage) throws Exception;
|
public Object generateNonStandardErrorResponseMessage(String inboudnAdapterGroupName, String inboudnAdapterName,
|
||||||
|
Properties callProp, Object outboundRequestData, EAIMessage resEaiMsg) throws Exception;
|
||||||
|
|
||||||
|
public Object generateNonStandardInternalErrorResponseMessage(String inboudnAdapterGroupName,
|
||||||
|
String inboudnAdapterName, Properties callProp, Object inboundRequestData, EAIMessage resEaiMsg)
|
||||||
|
throws Exception;
|
||||||
|
|
||||||
|
public Object generateNonStandardInboundErrorResponseMessage(String adapterGroupName, String adapterName,
|
||||||
|
Properties callProp, Object inboundRequestData, Object inboundResponseData, Throwable e) throws Exception;
|
||||||
}
|
}
|
||||||
|
|
||||||
+14
-2
@@ -2,13 +2,25 @@ package com.eactive.eai.adapter.handler;
|
|||||||
|
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
|
|
||||||
import com.eactive.eai.message.StandardMessage;
|
import com.eactive.eai.common.message.EAIMessage;
|
||||||
|
|
||||||
public class DefaultAdapterErrorMessageHandler implements AdapterErrorMessageHandler {
|
public class DefaultAdapterErrorMessageHandler implements AdapterErrorMessageHandler {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object generateNonStandardErrorResponseMessage(String inboudnAdapterGroupName, String inboudnAdapterName,
|
public Object generateNonStandardErrorResponseMessage(String inboudnAdapterGroupName, String inboudnAdapterName,
|
||||||
Properties callProp, Object outboundRequestData, StandardMessage resStandardMessage) throws Exception {
|
Properties callProp, Object outboundRequestData, EAIMessage resEaiMsg) throws Exception {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object generateNonStandardInternalErrorResponseMessage(String inboudnAdapterGroupName, String inboudnAdapterName,
|
||||||
|
Properties callProp, Object inboundRequestData, EAIMessage resEaiMsg) throws Exception {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object generateNonStandardInboundErrorResponseMessage(String adapterGroupName, String adapterName,
|
||||||
|
Properties callProp, Object inboundRequestData, Object inboundResponseData, Throwable e) throws Exception {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+233
-38
@@ -1,5 +1,7 @@
|
|||||||
package com.eactive.eai.adapter.handler;
|
package com.eactive.eai.adapter.handler;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -8,13 +10,22 @@ import java.util.regex.Matcher;
|
|||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
|
||||||
|
import com.eactive.eai.adapter.AdapterGroupVO;
|
||||||
|
import com.eactive.eai.adapter.AdapterManager;
|
||||||
|
import com.eactive.eai.adapter.AdapterPropManager;
|
||||||
|
import com.eactive.eai.adapter.AdapterVO;
|
||||||
|
import com.eactive.eai.adapter.http.HttpStatusException;
|
||||||
|
import com.eactive.eai.adapter.http.dynamic.filter.JwtAuthException;
|
||||||
|
import com.eactive.eai.common.message.EAIMessage;
|
||||||
import com.eactive.eai.common.property.PropManager;
|
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.common.util.Logger;
|
||||||
|
import com.eactive.eai.common.util.MessageUtil;
|
||||||
import com.eactive.eai.message.StandardItem;
|
import com.eactive.eai.message.StandardItem;
|
||||||
import com.eactive.eai.message.StandardMessage;
|
import com.eactive.eai.message.StandardMessage;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonParser;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 템플릿 기반 어댑터 에러 메시지 핸들러.
|
* 템플릿 기반 어댑터 에러 메시지 핸들러.
|
||||||
@@ -87,14 +98,22 @@ public class TemplateAdapterErrorMsgHandler implements AdapterErrorMessageHandle
|
|||||||
/** callProp 간접 참조 접두어: ${callprop[표준전문경로]} */
|
/** callProp 간접 참조 접두어: ${callprop[표준전문경로]} */
|
||||||
static final String CALLPROP_INDIRECT_PREFIX = "callprop[";
|
static final String CALLPROP_INDIRECT_PREFIX = "callprop[";
|
||||||
|
|
||||||
/** ${path} 또는 ${callprop.key} 스칼라 변수 패턴 */
|
/** 예외 객체 필드 참조 접두어: ${exception.필드명} */
|
||||||
|
static final String EXCEPTION_PREFIX = "exception.";
|
||||||
|
|
||||||
|
/** ${path} 또는 ${callprop.key} 스칼라 변수 패턴 (renderRow 내부에서 사용) */
|
||||||
private static final Pattern VAR_PATTERN =
|
private static final Pattern VAR_PATTERN =
|
||||||
Pattern.compile("\\$\\{([^}]+)\\}");
|
Pattern.compile("\\$\\{([^}]+)\\}");
|
||||||
|
|
||||||
/** {{#foreach path}}...{{/foreach}} 배열 반복 블록 패턴 */
|
/**
|
||||||
private static final Pattern FOREACH_PATTERN =
|
* foreach 블록과 스칼라 변수를 순서대로 단일 패스로 처리하는 통합 패턴.
|
||||||
Pattern.compile("\\{\\{#foreach\\s+([^}]+)\\}\\}(.*?)\\{\\{/foreach\\}\\}",
|
* group(1) != null → foreach 블록: group(1)=경로, group(2)=블록 본문
|
||||||
Pattern.DOTALL);
|
* group(3) != null → 스칼라 변수: group(3)=표현식
|
||||||
|
*/
|
||||||
|
private static final Pattern COMBINED_PATTERN =
|
||||||
|
Pattern.compile(
|
||||||
|
"\\{\\{#foreach\\s+([^}]+)\\}\\}(.*?)\\{\\{/foreach\\}\\}|\\$\\{([^}]+)\\}",
|
||||||
|
Pattern.DOTALL);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object generateNonStandardErrorResponseMessage(
|
public Object generateNonStandardErrorResponseMessage(
|
||||||
@@ -102,9 +121,10 @@ public class TemplateAdapterErrorMsgHandler implements AdapterErrorMessageHandle
|
|||||||
String inboundAdapterName,
|
String inboundAdapterName,
|
||||||
Properties callProp,
|
Properties callProp,
|
||||||
Object outboundRequestData,
|
Object outboundRequestData,
|
||||||
StandardMessage resStandardMessage) throws Exception {
|
EAIMessage resEaiMsg) throws Exception {
|
||||||
|
|
||||||
String templateKey = inboundAdapterGroupName + ".template";
|
StandardMessage resStandardMessage = resEaiMsg.getStandardMessage();
|
||||||
|
String templateKey = inboundAdapterGroupName + ".template";
|
||||||
String template = PropManager.getInstance().getProperty(PROP_GROUP, templateKey);
|
String template = PropManager.getInstance().getProperty(PROP_GROUP, templateKey);
|
||||||
|
|
||||||
if (StringUtils.isBlank(template)) {
|
if (StringUtils.isBlank(template)) {
|
||||||
@@ -118,55 +138,73 @@ public class TemplateAdapterErrorMsgHandler implements AdapterErrorMessageHandle
|
|||||||
return render(template, resStandardMessage, callProp);
|
return render(template, resStandardMessage, callProp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 3-인자 위임: exception 없이 호출하는 기존 경로 유지 */
|
||||||
|
String render(String template, StandardMessage msg, Properties callProp) {
|
||||||
|
return render(template, msg, callProp, null);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 템플릿에 StandardMessage 및 callProp 값을 치환해 최종 문자열을 반환한다.
|
* 템플릿에 StandardMessage, callProp, exception 값을 치환해 최종 문자열을 반환한다.
|
||||||
|
* foreach 블록과 스칼라 변수를 좌→우 순서로 단일 패스 처리한다.
|
||||||
* package-private: 단위 테스트에서 직접 호출 가능
|
* package-private: 단위 테스트에서 직접 호출 가능
|
||||||
*/
|
*/
|
||||||
String render(String template, StandardMessage msg, Properties callProp) {
|
String render(String template, StandardMessage msg, Properties callProp, Throwable exception) {
|
||||||
// 1단계: {{#foreach path}}...{{/foreach}} 배열 반복 처리
|
// 관리 UI 등에서 HTML 인코딩된 엔티티를 복원 (예: # → #)
|
||||||
StringBuffer sb = new StringBuffer();
|
template = template.replace("#", "#")
|
||||||
Matcher foreachMatcher = FOREACH_PATTERN.matcher(template);
|
.replace("&", "&")
|
||||||
while (foreachMatcher.find()) {
|
.replace("<", "<")
|
||||||
String arrayPath = foreachMatcher.group(1).trim();
|
.replace(">", ">")
|
||||||
String blockContent = foreachMatcher.group(2);
|
.replace(""", "\"");
|
||||||
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();
|
StringBuffer result = new StringBuffer();
|
||||||
Matcher varMatcher = VAR_PATTERN.matcher(intermediate);
|
Matcher matcher = COMBINED_PATTERN.matcher(template);
|
||||||
while (varMatcher.find()) {
|
while (matcher.find()) {
|
||||||
String expr = varMatcher.group(1).trim();
|
String replacement;
|
||||||
String value = resolveScalar(expr, msg, callProp);
|
if (matcher.group(1) != null) {
|
||||||
varMatcher.appendReplacement(result, Matcher.quoteReplacement(value));
|
// {{#foreach path}}...{{/foreach}} 블록
|
||||||
|
String arrayPath = matcher.group(1).trim();
|
||||||
|
String blockContent = matcher.group(2);
|
||||||
|
replacement = (msg != null) ? renderForeach(arrayPath, blockContent, msg) : "";
|
||||||
|
} else {
|
||||||
|
// ${path} / ${callprop.키} / ${exception.필드} 스칼라 변수
|
||||||
|
String expr = matcher.group(3).trim();
|
||||||
|
replacement = resolveScalar(expr, msg, callProp, exception);
|
||||||
|
}
|
||||||
|
matcher.appendReplacement(result, Matcher.quoteReplacement(replacement));
|
||||||
}
|
}
|
||||||
varMatcher.appendTail(result);
|
matcher.appendTail(result);
|
||||||
return result.toString();
|
return result.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 변수 표현식을 해석해 값을 반환한다.
|
* 변수 표현식을 해석해 값을 반환한다.
|
||||||
* 모든 형태에서 ':기본값' 접미사를 지원한다. 기본값 미지정 시 빈 문자열.
|
* 모든 형태에서 ':기본값' 접미사를 지원한다. 기본값 미지정 시 빈 문자열.
|
||||||
* - "callprop[경로][:기본값]" → 표준전문 경로 값을 키로 callProp 간접 조회
|
* - "callprop[경로][:기본값]" → 표준전문 경로 값을 키로 callProp 간접 조회
|
||||||
* - "callprop.키[:기본값]" → callProp 직접/중첩 조회
|
* - "callprop.키[:기본값]" → callProp 직접/중첩 조회
|
||||||
* - "경로[:기본값]" → StandardMessage.findItemValue(경로)
|
* - "exception.필드[:기본값]" → 리플렉션으로 예외 객체 getter/필드 조회
|
||||||
|
* - "경로[:기본값]" → StandardMessage.findItemValue(경로)
|
||||||
*/
|
*/
|
||||||
private String resolveScalar(String expr, StandardMessage msg, Properties callProp) {
|
private String resolveScalar(String expr, StandardMessage msg, Properties callProp, Throwable exception) {
|
||||||
if (expr.startsWith(CALLPROP_INDIRECT_PREFIX)) {
|
if (expr.startsWith(CALLPROP_INDIRECT_PREFIX)) {
|
||||||
return resolveIndirectCallProp(expr, msg, callProp);
|
return resolveIndirectCallProp(expr, msg, callProp);
|
||||||
}
|
}
|
||||||
if (expr.startsWith(CALLPROP_PREFIX)) {
|
if (expr.startsWith(CALLPROP_PREFIX)) {
|
||||||
String rest = expr.substring(CALLPROP_PREFIX.length());
|
String rest = expr.substring(CALLPROP_PREFIX.length());
|
||||||
int colonIdx = rest.indexOf(':');
|
int colonIdx = rest.indexOf(':');
|
||||||
String keyPath = colonIdx >= 0 ? rest.substring(0, colonIdx) : rest;
|
String keyPath = colonIdx >= 0 ? rest.substring(0, colonIdx) : rest;
|
||||||
String defaultVal = colonIdx >= 0 ? rest.substring(colonIdx + 1) : "";
|
String defaultVal = colonIdx >= 0 ? rest.substring(colonIdx + 1) : "";
|
||||||
if (callProp == null) return defaultVal;
|
if (callProp == null) return defaultVal;
|
||||||
String value = resolveCallProp(callProp, keyPath);
|
String value = resolveCallProp(callProp, keyPath);
|
||||||
return value != null ? value : defaultVal;
|
return value != null ? value : defaultVal;
|
||||||
}
|
}
|
||||||
|
if (expr.startsWith(EXCEPTION_PREFIX)) {
|
||||||
|
String rest = expr.substring(EXCEPTION_PREFIX.length());
|
||||||
|
int colonIdx = rest.indexOf(':');
|
||||||
|
String fieldName = colonIdx >= 0 ? rest.substring(0, colonIdx) : rest;
|
||||||
|
String defaultVal = colonIdx >= 0 ? rest.substring(colonIdx + 1) : "";
|
||||||
|
if (exception == null) return defaultVal;
|
||||||
|
String value = resolveExceptionProperty(exception, fieldName);
|
||||||
|
return value != null ? value : defaultVal;
|
||||||
|
}
|
||||||
int colonIdx = expr.indexOf(':');
|
int colonIdx = expr.indexOf(':');
|
||||||
String path = colonIdx >= 0 ? expr.substring(0, colonIdx) : expr;
|
String path = colonIdx >= 0 ? expr.substring(0, colonIdx) : expr;
|
||||||
String defaultVal = colonIdx >= 0 ? expr.substring(colonIdx + 1) : "";
|
String defaultVal = colonIdx >= 0 ? expr.substring(colonIdx + 1) : "";
|
||||||
@@ -176,13 +214,22 @@ public class TemplateAdapterErrorMsgHandler implements AdapterErrorMessageHandle
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* StandardMessage 경로를 해석한다.
|
* StandardMessage 경로를 해석한다.
|
||||||
* 1. findItemValue(path) 로 직접 조회한다.
|
* 1. 경로에 '[n]' 구문이 있으면 배열 인덱스 접근으로 처리한다.
|
||||||
* 2. null 이면 경로를 뒤에서부터 분리하며, 부모 경로 값이 JSON 문자열인 경우
|
* 예) MSG.MSG_LIST[0].outp_msg_cd → MSG_LIST 첫 번째 행의 outp_msg_cd 값
|
||||||
|
* 2. findItemValue(path) 로 직접 조회한다.
|
||||||
|
* 3. null 이면 경로를 뒤에서부터 분리하며, 부모 경로 값이 JSON 문자열인 경우
|
||||||
* 나머지 경로(subPath)를 키로 JSON 필드를 추출한다.
|
* 나머지 경로(subPath)를 키로 JSON 필드를 추출한다.
|
||||||
* 예) DATA.BIZDATA.acctNo → findItemValue("DATA.BIZDATA") → JSON 파싱 → acctNo
|
* 예) DATA.BIZDATA.acctNo → findItemValue("DATA.BIZDATA") → JSON 파싱 → acctNo
|
||||||
* DATA.BIZDATA.addr.city → findItemValue("DATA.BIZDATA") → JSON 파싱 → addr.city
|
* DATA.BIZDATA.addr.city → findItemValue("DATA.BIZDATA") → JSON 파싱 → addr.city
|
||||||
*/
|
*/
|
||||||
private String resolveMessagePath(StandardMessage msg, String path) {
|
private String resolveMessagePath(StandardMessage msg, String path) {
|
||||||
|
if (msg == null) return null;
|
||||||
|
|
||||||
|
int bracketStart = path.indexOf('[');
|
||||||
|
if (bracketStart >= 0) {
|
||||||
|
return resolveArrayIndex(msg, path, bracketStart);
|
||||||
|
}
|
||||||
|
|
||||||
String value = msg.findItemValue(path);
|
String value = msg.findItemValue(path);
|
||||||
if (value != null) return value;
|
if (value != null) return value;
|
||||||
|
|
||||||
@@ -200,6 +247,44 @@ public class TemplateAdapterErrorMsgHandler implements AdapterErrorMessageHandle
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 배열 인덱스 접근 구문 처리: {arrayPath}[{n}].{fieldName}
|
||||||
|
* - arrayPath : GRID 타입 아이템 경로 (예: MSG.MSG_LIST)
|
||||||
|
* - n : 0-based 행 인덱스
|
||||||
|
* - fieldName : 행 내 컬럼명 (예: outp_msg_cd)
|
||||||
|
* 인덱스가 범위를 벗어나거나 필드가 없으면 null 반환.
|
||||||
|
*/
|
||||||
|
private String resolveArrayIndex(StandardMessage msg, String path, int bracketStart) {
|
||||||
|
if (msg == null) return null;
|
||||||
|
|
||||||
|
int bracketEnd = path.indexOf(']', bracketStart);
|
||||||
|
if (bracketEnd < 0) return null;
|
||||||
|
|
||||||
|
String arrayPath = path.substring(0, bracketStart);
|
||||||
|
String indexStr = path.substring(bracketStart + 1, bracketEnd);
|
||||||
|
int index;
|
||||||
|
try {
|
||||||
|
index = Integer.parseInt(indexStr.trim());
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String fieldName = null;
|
||||||
|
if (bracketEnd + 2 <= path.length() - 1 && path.charAt(bracketEnd + 1) == '.') {
|
||||||
|
fieldName = path.substring(bracketEnd + 2);
|
||||||
|
}
|
||||||
|
if (StringUtils.isBlank(fieldName)) return null;
|
||||||
|
|
||||||
|
StandardItem arrayItem = msg.findItem(arrayPath);
|
||||||
|
if (arrayItem == null) return null;
|
||||||
|
|
||||||
|
List<LinkedHashMap<String, StandardItem>> rows = arrayItem.getList();
|
||||||
|
if (rows == null || index < 0 || index >= rows.size()) return null;
|
||||||
|
|
||||||
|
StandardItem item = rows.get(index).get(fieldName);
|
||||||
|
return item != null ? StringUtils.defaultString(item.getValue()) : null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* JSON 문자열에서 dot 구분 경로로 필드 값을 추출한다.
|
* JSON 문자열에서 dot 구분 경로로 필드 값을 추출한다.
|
||||||
* 중간 경로가 JSON 오브젝트면 계속 탐색하고, 최종 값이 primitive 면 문자열로,
|
* 중간 경로가 JSON 오브젝트면 계속 탐색하고, 최종 값이 primitive 면 문자열로,
|
||||||
@@ -276,7 +361,7 @@ public class TemplateAdapterErrorMsgHandler implements AdapterErrorMessageHandle
|
|||||||
* 결과를 쉼표로 이어 반환한다. (foreach 블록 내부는 callProp 참조 미지원)
|
* 결과를 쉼표로 이어 반환한다. (foreach 블록 내부는 callProp 참조 미지원)
|
||||||
*/
|
*/
|
||||||
private String renderForeach(String arrayPath, String blockContent, StandardMessage msg) {
|
private String renderForeach(String arrayPath, String blockContent, StandardMessage msg) {
|
||||||
StandardItem arrayItem = msg.findItem(arrayPath);
|
StandardItem arrayItem = msg.findItem(arrayPath);
|
||||||
if (arrayItem == null) {
|
if (arrayItem == null) {
|
||||||
if (logger.isWarn()) logger.warn("TemplateAdapterErrorMsgHandler] foreach path not found: " + arrayPath);
|
if (logger.isWarn()) logger.warn("TemplateAdapterErrorMsgHandler] foreach path not found: " + arrayPath);
|
||||||
return "";
|
return "";
|
||||||
@@ -316,4 +401,114 @@ public class TemplateAdapterErrorMsgHandler implements AdapterErrorMessageHandle
|
|||||||
varMatcher.appendTail(result);
|
varMatcher.appendTail(result);
|
||||||
return result.toString().trim();
|
return result.toString().trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object generateNonStandardInternalErrorResponseMessage(String inboudnAdapterGroupName, String inboudnAdapterName,
|
||||||
|
Properties callProp, Object inboundRequestData, EAIMessage resEaiMsg) throws Exception {
|
||||||
|
String templateKey = inboudnAdapterGroupName + ".sys.template";
|
||||||
|
String template = PropManager.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, resEaiMsg.getStandardMessage(), callProp);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object generateNonStandardInboundErrorResponseMessage(String adapterGroupName, String adapterName,
|
||||||
|
Properties callProp, Object inboundRequestData, Object inboundResponseData, Throwable e) throws Exception {
|
||||||
|
|
||||||
|
AdapterGroupVO adapterGroupVO = AdapterManager.getInstance().getAdapterGroupVO(adapterGroupName);
|
||||||
|
AdapterVO adapterVO = AdapterManager.getInstance().getAdapterVO(adapterGroupName, adapterName);
|
||||||
|
Properties httpProp = AdapterPropManager.getInstance().getProperties(adapterVO.getPropGroupName());
|
||||||
|
String adptMsgType = adapterVO.getAdapterGroupVO().getMessageType();
|
||||||
|
String encode = StringUtils.defaultIfBlank(adapterGroupVO.getMessageEncode(), "UTF-8");
|
||||||
|
|
||||||
|
if (e instanceof HttpStatusException) {
|
||||||
|
HttpStatusException e1 = (HttpStatusException) e;
|
||||||
|
int httpCode = e1.getStatus();
|
||||||
|
|
||||||
|
return genInboundErrorResponse(adapterGroupName, callProp, httpProp, adptMsgType, encode, e1, httpCode);
|
||||||
|
} else if (e instanceof JwtAuthException) {
|
||||||
|
return genInboundErrorResponse(adapterGroupName, callProp, httpProp, adptMsgType, encode, e,
|
||||||
|
HttpStatus.UNAUTHORIZED.value());
|
||||||
|
} else {
|
||||||
|
return genInboundErrorResponse(adapterGroupName, callProp, httpProp, adptMsgType, encode, e,
|
||||||
|
HttpStatus.INTERNAL_SERVER_ERROR.value());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object genInboundErrorResponse(String adapterGroupName, Properties callProp, Properties httpProp,
|
||||||
|
String adptMsgType, String encode, Throwable e1, int httpCode) {
|
||||||
|
String templateKey = adapterGroupName + ".in." + httpCode + ".template";
|
||||||
|
String template = PropManager.getInstance().getProperty(PROP_GROUP, templateKey);
|
||||||
|
if (StringUtils.isBlank(template)) {
|
||||||
|
templateKey = adapterGroupName + ".in.template";
|
||||||
|
template = PropManager.getInstance().getProperty(PROP_GROUP, templateKey);
|
||||||
|
if (StringUtils.isBlank(template)) {
|
||||||
|
String errorResponseFormat = httpProp.getProperty("ERROR_RESPONSE_FORMAT");
|
||||||
|
return MessageUtil.makeErrorMessageByMessageType(adptMsgType, encode,
|
||||||
|
MessageUtil.ERROR_CODE_AP_ERROR, e1.getMessage(), errorResponseFormat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return render(template, null, callProp, e1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 리플렉션으로 예외 객체의 필드 값을 동적으로 조회한다.
|
||||||
|
* 탐색 순서: getXxx() getter → isXxx() getter → 필드 직접 접근
|
||||||
|
* 상위 클래스(Throwable 포함)까지 계층적으로 탐색한다.
|
||||||
|
*/
|
||||||
|
private String resolveExceptionProperty(Throwable e, String fieldName) {
|
||||||
|
if (StringUtils.isBlank(fieldName)) return null;
|
||||||
|
String cap = Character.toUpperCase(fieldName.charAt(0)) + fieldName.substring(1);
|
||||||
|
for (String prefix : new String[]{"get", "is"}) {
|
||||||
|
Method m = findMethod(e.getClass(), prefix + cap);
|
||||||
|
if (m != null) {
|
||||||
|
try {
|
||||||
|
Object val = m.invoke(e);
|
||||||
|
return val != null ? val.toString() : null;
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Field f = findField(e.getClass(), fieldName);
|
||||||
|
if (f != null) {
|
||||||
|
try {
|
||||||
|
f.setAccessible(true);
|
||||||
|
Object val = f.get(e);
|
||||||
|
return val != null ? val.toString() : null;
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Method findMethod(Class<?> clazz, String name) {
|
||||||
|
while (clazz != null) {
|
||||||
|
try {
|
||||||
|
Method m = clazz.getDeclaredMethod(name);
|
||||||
|
m.setAccessible(true);
|
||||||
|
return m;
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
clazz = clazz.getSuperclass();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Field findField(Class<?> clazz, String name) {
|
||||||
|
while (clazz != null) {
|
||||||
|
try {
|
||||||
|
return clazz.getDeclaredField(name);
|
||||||
|
} catch (NoSuchFieldException e) {
|
||||||
|
clazz = clazz.getSuperclass();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -570,8 +570,8 @@ public abstract class HttpClient5AdapterServiceSupport implements HttpClientAdap
|
|||||||
public abstract Object execute(Properties prop, Object message, Properties tempProp) throws Exception;
|
public abstract Object execute(Properties prop, Object message, Properties tempProp) throws Exception;
|
||||||
|
|
||||||
protected boolean isTas(Properties tempProp) {
|
protected boolean isTas(Properties tempProp) {
|
||||||
String simYn = tempProp.getProperty("SIM_YN");
|
String tranType = tempProp.getProperty("tranType");
|
||||||
return EAIServerManager.getInstance().isTASEnabledEAIServer()
|
return EAIServerManager.getInstance().isTASEnabledEAIServer()
|
||||||
&& StringUtils.equals(simYn, EAIMessageKeys.TRANTYPE_TAS);
|
&& StringUtils.equals(tranType, EAIMessageKeys.TRANTYPE_TAS);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+24
-11
@@ -267,10 +267,14 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
|
|||||||
Map<String, String> inboundPathVariables = (Map<String, String>) tempProp.get(HttpAdapterServiceKey.INBOUND_PATH_VARIABLES);
|
Map<String, String> inboundPathVariables = (Map<String, String>) tempProp.get(HttpAdapterServiceKey.INBOUND_PATH_VARIABLES);
|
||||||
// interface 에 설정된게 우선한다.
|
// interface 에 설정된게 우선한다.
|
||||||
String uri = null;
|
String uri = null;
|
||||||
if (dataObject == null) {
|
if(this.isTas(tempProp)) {
|
||||||
uri = changeUrl(messageType, vo.getUrl(), restOptionData, sendData, inboundPathVariables);
|
uri = vo.getUrl();
|
||||||
} else {
|
} else {
|
||||||
uri = changeUrl(messageType, vo.getUrl(), restOptionData, dataObject, inboundPathVariables);
|
if (dataObject == null) {
|
||||||
|
uri = changeUrl(messageType, vo.getUrl(), restOptionData, sendData, inboundPathVariables);
|
||||||
|
} else {
|
||||||
|
uri = changeUrl(messageType, vo.getUrl(), restOptionData, dataObject, inboundPathVariables);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.debug("HttpClientAdapterServiceRest] dataObject2 = [" + dataObject + "]");
|
logger.debug("HttpClientAdapterServiceRest] dataObject2 = [" + dataObject + "]");
|
||||||
@@ -435,11 +439,12 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
|
|||||||
|
|
||||||
int status = -1;
|
int status = -1;
|
||||||
|
|
||||||
|
Header[] responseHeaders = null;
|
||||||
|
String responseString = null;
|
||||||
try {
|
try {
|
||||||
byte[] responseMessage = null;
|
byte[] responseMessage = null;
|
||||||
// byte[] responseMessageOri = null;
|
// byte[] responseMessageOri = null;
|
||||||
String responseContentType = null;
|
String responseContentType = null;
|
||||||
Header[] responseHeaders;
|
|
||||||
|
|
||||||
if (logger.isDebug() && "N".equals(vo.getTestCallYn())) {
|
if (logger.isDebug() && "N".equals(vo.getTestCallYn())) {
|
||||||
if (dataObject instanceof JSONObject) {
|
if (dataObject instanceof JSONObject) {
|
||||||
@@ -550,7 +555,7 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
|
|||||||
*/
|
*/
|
||||||
boolean needReissue = false;
|
boolean needReissue = false;
|
||||||
|
|
||||||
String responseString = new String(responseMessage, vo.getEncode());
|
responseString = new String(responseMessage, vo.getEncode());
|
||||||
|
|
||||||
if (isStatusInIgnore(status,errIgnore )) {
|
if (isStatusInIgnore(status,errIgnore )) {
|
||||||
// 이경우 정상으로 처리함
|
// 이경우 정상으로 처리함
|
||||||
@@ -697,12 +702,7 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
|
|||||||
}
|
}
|
||||||
|
|
||||||
Properties responseHeaderProp = assignRelayDataToInbound(tempProp, responseHeaders);
|
Properties responseHeaderProp = assignRelayDataToInbound(tempProp, responseHeaders);
|
||||||
Map<String, Object> map = new HashMap<String, Object>();
|
assignOutboundPropertyMap(tempProp, status, responseString, responseHeaderProp);
|
||||||
tempProp.put(HttpAdapterServiceKey.OUTBOUND_PROPERTY_MAP, map);
|
|
||||||
map.put(HttpAdapterServiceKey.OUTBOUND_RESPONSE_HEADERS, responseHeaderProp);
|
|
||||||
map.put(HttpAdapterServiceKey.OUTBOUND_RESPONSE_MESSAGE, responseString);
|
|
||||||
map.put(HttpAdapterServiceKey.OUTBOUND_RESPONSE_STATUS, status);
|
|
||||||
|
|
||||||
|
|
||||||
return assignResponseHeaders(method, responseMessage, headerGroupName, vo.getEncode(), responseContentType,
|
return assignResponseHeaders(method, responseMessage, headerGroupName, vo.getEncode(), responseContentType,
|
||||||
relayResponseHeaderKeys, status, responseHeaderProp);
|
relayResponseHeaderKeys, status, responseHeaderProp);
|
||||||
@@ -732,6 +732,10 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
|
|||||||
}
|
}
|
||||||
logger.error("HttpClientAdapterServiceRest] Exception (" + vo.getAdapterGroupName() + ") : " + e.toString(),
|
logger.error("HttpClientAdapterServiceRest] Exception (" + vo.getAdapterGroupName() + ") : " + e.toString(),
|
||||||
e);
|
e);
|
||||||
|
|
||||||
|
Properties responseHeaderProp = assignRelayDataToInbound(tempProp, responseHeaders);
|
||||||
|
assignOutboundPropertyMap(tempProp, status, responseString, responseHeaderProp);
|
||||||
|
|
||||||
throw e;
|
throw e;
|
||||||
} finally {
|
} finally {
|
||||||
if (status != HttpStatus.SC_OK) {
|
if (status != HttpStatus.SC_OK) {
|
||||||
@@ -745,6 +749,15 @@ public class HttpClient5AdapterServiceRest extends HttpClient5AdapterServiceSupp
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void assignOutboundPropertyMap(Properties tempProp, int status, String responseString,
|
||||||
|
Properties responseHeaderProp) {
|
||||||
|
Map<String, Object> map = new HashMap<String, Object>();
|
||||||
|
tempProp.put(HttpAdapterServiceKey.OUTBOUND_PROPERTY_MAP, map);
|
||||||
|
map.put(HttpAdapterServiceKey.OUTBOUND_RESPONSE_HEADERS, responseHeaderProp);
|
||||||
|
map.put(HttpAdapterServiceKey.OUTBOUND_RESPONSE_MESSAGE, responseString);
|
||||||
|
map.put(HttpAdapterServiceKey.OUTBOUND_RESPONSE_STATUS, status);
|
||||||
|
}
|
||||||
|
|
||||||
private Properties assignRelayDataToInbound(Properties prop, Header[] responseHeaders) {
|
private Properties assignRelayDataToInbound(Properties prop, Header[] responseHeaders) {
|
||||||
Properties headerProp = new Properties();
|
Properties headerProp = new Properties();
|
||||||
if (responseHeaders == null || responseHeaders.length == 0) {
|
if (responseHeaders == null || responseHeaders.length == 0) {
|
||||||
|
|||||||
@@ -7,6 +7,10 @@ import org.json.simple.JSONValue;
|
|||||||
|
|
||||||
import com.eactive.eai.adapter.AdapterGroupVO;
|
import com.eactive.eai.adapter.AdapterGroupVO;
|
||||||
import com.eactive.eai.adapter.AdapterManager;
|
import com.eactive.eai.adapter.AdapterManager;
|
||||||
|
import com.eactive.eai.adapter.AdapterPropManager;
|
||||||
|
import com.eactive.eai.adapter.AdapterVO;
|
||||||
|
import com.eactive.eai.adapter.handler.AdapterErrorMessageHandler;
|
||||||
|
import com.eactive.eai.adapter.handler.AdapterErrorMessageHandlerFactory;
|
||||||
import com.eactive.eai.common.EAIKeys;
|
import com.eactive.eai.common.EAIKeys;
|
||||||
import com.eactive.eai.common.logger.EAILogSender;
|
import com.eactive.eai.common.logger.EAILogSender;
|
||||||
import com.eactive.eai.common.message.EAIMessage;
|
import com.eactive.eai.common.message.EAIMessage;
|
||||||
@@ -749,6 +753,53 @@ public class ExceptionHandler {
|
|||||||
String serviceType, String psvItfTp, String charset) {
|
String serviceType, String psvItfTp, String charset) {
|
||||||
String error = null;
|
String error = null;
|
||||||
EAIMessage resultEAIMessage = null;
|
EAIMessage resultEAIMessage = null;
|
||||||
|
|
||||||
|
// AdapterErrorMessageHandler(TemplateAdapterErrorMsgHandler) 설정이 있으면 우선 호출
|
||||||
|
try {
|
||||||
|
AdapterGroupVO inboundAdapterGroupVO = AdapterManager.getInstance().getAdapterGroupVO(sngSysItfTp);
|
||||||
|
if (inboundAdapterGroupVO != null) {
|
||||||
|
AdapterVO inboundAdapterVO = inboundAdapterGroupVO.nextAdapterVO();
|
||||||
|
if (inboundAdapterVO != null) {
|
||||||
|
String errorHandlerClass = AdapterPropManager.getInstance().getProperty(
|
||||||
|
inboundAdapterVO.getPropGroupName(), "ERR_MSG_HANDLER");
|
||||||
|
if (StringUtils.isNotBlank(errorHandlerClass)) {
|
||||||
|
if (logger.isInfo())
|
||||||
|
logger.info("ExceptionHandler] errorSyncSend ERR_MSG_HANDLER=" + errorHandlerClass);
|
||||||
|
AdapterErrorMessageHandler handler = AdapterErrorMessageHandlerFactory.createHandler(errorHandlerClass);
|
||||||
|
if (handler != null) {
|
||||||
|
// 표준전문에 에러코드/에러메시지 세팅
|
||||||
|
try {
|
||||||
|
StandardMessageManager standardManager = StandardMessageManager.getInstance();
|
||||||
|
standardManager.getMessageCoordinator().coordinateSetStandardMessageError(
|
||||||
|
eaiMessage.getStandardMessage(), eaiMessage.getMapper(), errCode, errMsg);
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (logger.isWarn())
|
||||||
|
logger.warn("ExceptionHandler] errorSyncSend coordinateSetStandardMessageError 처리 실패: " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
Object responseObj = handler.generateNonStandardInternalErrorResponseMessage(
|
||||||
|
sngSysItfTp, inboundAdapterVO.getName(), null, tmpMsg, eaiMessage);
|
||||||
|
if (responseObj != null) {
|
||||||
|
resultEAIMessage = eaiMessage;
|
||||||
|
try {
|
||||||
|
resultEAIMessage.getStandardMessage().setBizData(responseObj, charset);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("setBizData error", e);
|
||||||
|
}
|
||||||
|
resultEAIMessage.getMapper().setResponseType(
|
||||||
|
resultEAIMessage.getStandardMessage(), STDMessageKeys.RESPONSE_TYPE_CODE_E);
|
||||||
|
resultEAIMessage.setRspErrCd(EAIMessageKeys.EAI_SUCCESS_CODE, false);
|
||||||
|
return resultEAIMessage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (logger.isWarn())
|
||||||
|
logger.warn("ExceptionHandler] errorSyncSend AdapterErrorMessageHandler 처리 실패: " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (transClassify) {
|
if (transClassify) {
|
||||||
if (logger.isInfo()) {
|
if (logger.isInfo()) {
|
||||||
|
|||||||
@@ -671,6 +671,8 @@ public abstract class DefaultProcess extends Process {
|
|||||||
StandardMessage resStandardMessage = this.resEaiMsg.getStandardMessage();
|
StandardMessage resStandardMessage = this.resEaiMsg.getStandardMessage();
|
||||||
InterfaceMapper mapper = this.resEaiMsg.getMapper();
|
InterfaceMapper mapper = this.resEaiMsg.getMapper();
|
||||||
String responseType = mapper.getResponseType(resStandardMessage);
|
String responseType = mapper.getResponseType(resStandardMessage);
|
||||||
|
|
||||||
|
// apigw내 오류 없이, 표준헤더내 오류설정된 경우 응답 처리
|
||||||
if(!STDMessageKeys.RESPONSE_TYPE_CODE_N.equals(responseType)){
|
if(!STDMessageKeys.RESPONSE_TYPE_CODE_N.equals(responseType)){
|
||||||
String inboudnAdapterGroupName = this.callProp.getProperty(HttpClientAdapterServiceKey.ADAPTER_GROUP_NAME);
|
String inboudnAdapterGroupName = this.callProp.getProperty(HttpClientAdapterServiceKey.ADAPTER_GROUP_NAME);
|
||||||
String inboudnAdapterName = this.callProp.getProperty(HttpClientAdapterServiceKey.ADAPTER_NAME);
|
String inboudnAdapterName = this.callProp.getProperty(HttpClientAdapterServiceKey.ADAPTER_NAME);
|
||||||
@@ -693,7 +695,7 @@ public abstract class DefaultProcess extends Process {
|
|||||||
|
|
||||||
AdapterErrorMessageHandler adapterErrorMessageHandler = AdapterErrorMessageHandlerFactory.createHandler(errorResponseHandlerClass);
|
AdapterErrorMessageHandler adapterErrorMessageHandler = AdapterErrorMessageHandlerFactory.createHandler(errorResponseHandlerClass);
|
||||||
|
|
||||||
Object responseObj = adapterErrorMessageHandler.generateNonStandardErrorResponseMessage(inboudnAdapterGroupName, inboudnAdapterName, this.callProp, this.tgtTranObject, resStandardMessage);
|
Object responseObj = adapterErrorMessageHandler.generateNonStandardErrorResponseMessage(inboudnAdapterGroupName, inboudnAdapterName, this.callProp, this.tgtTranObject, this.resEaiMsg);
|
||||||
if(responseObj != null) {
|
if(responseObj != null) {
|
||||||
resStandardMessage.setBizData(responseObj, inboundAdapterGroupVO.getMessageEncode());
|
resStandardMessage.setBizData(responseObj, inboundAdapterGroupVO.getMessageEncode());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -522,6 +522,7 @@ public class RESTProcess extends HTTPProcess {
|
|||||||
|
|
||||||
String url = StringUtils.removeEnd(simAddr, "/") + "/mockapi/" + this.reqEaiMsg.getEAISvcCd();
|
String url = StringUtils.removeEnd(simAddr, "/") + "/mockapi/" + this.reqEaiMsg.getEAISvcCd();
|
||||||
this.outboundProp.put(HttpClientAdapterServiceKey.URL, url);
|
this.outboundProp.put(HttpClientAdapterServiceKey.URL, url);
|
||||||
|
this.tempProp.put("tranType", reqEaiMsg.getTranType());
|
||||||
this.tempProp.remove(HttpAdapterServiceKey.INBOUND_REWRITE_PATH);
|
this.tempProp.remove(HttpAdapterServiceKey.INBOUND_REWRITE_PATH);
|
||||||
logger.debug("simUrl=" + url);
|
logger.debug("simUrl=" + url);
|
||||||
}
|
}
|
||||||
|
|||||||
+699
-38
@@ -1,6 +1,8 @@
|
|||||||
package com.eactive.eai.adapter.handler;
|
package com.eactive.eai.adapter.handler;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
import static org.mockito.ArgumentMatchers.anyString;
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
import static org.mockito.ArgumentMatchers.eq;
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
|
||||||
@@ -12,9 +14,17 @@ import org.junit.jupiter.api.BeforeEach;
|
|||||||
import org.junit.jupiter.api.DisplayName;
|
import org.junit.jupiter.api.DisplayName;
|
||||||
import org.junit.jupiter.api.Nested;
|
import org.junit.jupiter.api.Nested;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.mockito.InOrder;
|
||||||
import org.mockito.Mockito;
|
import org.mockito.Mockito;
|
||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
|
|
||||||
|
import com.eactive.eai.adapter.AdapterGroupVO;
|
||||||
|
import com.eactive.eai.adapter.AdapterManager;
|
||||||
|
import com.eactive.eai.adapter.AdapterPropManager;
|
||||||
|
import com.eactive.eai.adapter.AdapterVO;
|
||||||
|
import com.eactive.eai.adapter.http.HttpStatusException;
|
||||||
|
import com.eactive.eai.adapter.http.dynamic.filter.JwtAuthException;
|
||||||
|
import com.eactive.eai.common.message.EAIMessage;
|
||||||
import com.eactive.eai.common.property.PropManager;
|
import com.eactive.eai.common.property.PropManager;
|
||||||
import com.eactive.eai.common.util.ApplicationContextProvider;
|
import com.eactive.eai.common.util.ApplicationContextProvider;
|
||||||
import com.eactive.eai.message.StandardItem;
|
import com.eactive.eai.message.StandardItem;
|
||||||
@@ -25,7 +35,7 @@ import com.eactive.eai.message.StandardType;
|
|||||||
* TemplateAdapterErrorMsgHandler 단위테스트
|
* TemplateAdapterErrorMsgHandler 단위테스트
|
||||||
*
|
*
|
||||||
* - render() : package-private 이므로 같은 패키지에서 직접 호출
|
* - render() : package-private 이므로 같은 패키지에서 직접 호출
|
||||||
* - generateNonStandardErrorResponseMessage() : ApplicationContextProvider 에
|
* - generateNonStandard*() : ApplicationContextProvider 에
|
||||||
* mock ApplicationContext 를 리플렉션으로 주입하여 PropManager 격리
|
* mock ApplicationContext 를 리플렉션으로 주입하여 PropManager 격리
|
||||||
*/
|
*/
|
||||||
class TemplateAdapterErrorMsgHandlerTest {
|
class TemplateAdapterErrorMsgHandlerTest {
|
||||||
@@ -38,7 +48,7 @@ class TemplateAdapterErrorMsgHandlerTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ================================================================
|
// ================================================================
|
||||||
// StandardMessage 빌더 헬퍼
|
// 공통 헬퍼
|
||||||
// ================================================================
|
// ================================================================
|
||||||
|
|
||||||
/** FIELD 아이템 (type=2) */
|
/** FIELD 아이템 (type=2) */
|
||||||
@@ -109,6 +119,22 @@ class TemplateAdapterErrorMsgHandlerTest {
|
|||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** ApplicationContextProvider 에 mock PropManager 를 주입한다. */
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** EAIMessage 에 StandardMessage 를 주입해 반환한다. */
|
||||||
|
private EAIMessage toEaiMessage(StandardMessage msg) {
|
||||||
|
EAIMessage eaiMessage = new EAIMessage();
|
||||||
|
eaiMessage.setStandardMessage(msg);
|
||||||
|
return eaiMessage;
|
||||||
|
}
|
||||||
|
|
||||||
// ================================================================
|
// ================================================================
|
||||||
// 스칼라 변수 치환 ${path}
|
// 스칼라 변수 치환 ${path}
|
||||||
// ================================================================
|
// ================================================================
|
||||||
@@ -275,12 +301,11 @@ class TemplateAdapterErrorMsgHandlerTest {
|
|||||||
@DisplayName("중간 값이 Properties 가 아니면 전체 keyPath 로 폴백 조회")
|
@DisplayName("중간 값이 Properties 가 아니면 전체 keyPath 로 폴백 조회")
|
||||||
void callProp_중간값_비Properties_폴백() {
|
void callProp_중간값_비Properties_폴백() {
|
||||||
Properties callProp = new Properties();
|
Properties callProp = new Properties();
|
||||||
callProp.setProperty("A.B", "FALLBACK_VALUE"); // 점 포함 키
|
callProp.setProperty("A.B", "FALLBACK_VALUE");
|
||||||
callProp.setProperty("A", "NOT_PROPS"); // String 값
|
callProp.setProperty("A", "NOT_PROPS");
|
||||||
|
|
||||||
StandardMessage msg = buildMsg("E001", "오류", "", null);
|
StandardMessage msg = buildMsg("E001", "오류", "", null);
|
||||||
|
|
||||||
// A 는 String 이므로 Properties 가 아님 → "A.B" 전체 키로 폴백
|
|
||||||
assertEquals("FALLBACK_VALUE", handler.render("${callprop.A.B}", msg, callProp));
|
assertEquals("FALLBACK_VALUE", handler.render("${callprop.A.B}", msg, callProp));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -333,10 +358,8 @@ class TemplateAdapterErrorMsgHandlerTest {
|
|||||||
@DisplayName("callProp 간접 참조 ${callprop[경로]}")
|
@DisplayName("callProp 간접 참조 ${callprop[경로]}")
|
||||||
class IndirectCallPropSubstitution {
|
class IndirectCallPropSubstitution {
|
||||||
|
|
||||||
/** 표준전문에 cp_key 필드가 있는 메시지 */
|
|
||||||
private StandardMessage buildMsgWithKey(String cpKey, String errCode) {
|
private StandardMessage buildMsgWithKey(String cpKey, String errCode) {
|
||||||
StandardMessage msg = buildMsg(errCode, "오류", "", null);
|
StandardMessage msg = buildMsg(errCode, "오류", "", null);
|
||||||
// MSG 그룹 아래 cp_key 필드 추가
|
|
||||||
StandardItem msgGroup = msg.findItem("MSG");
|
StandardItem msgGroup = msg.findItem("MSG");
|
||||||
if (msgGroup != null) {
|
if (msgGroup != null) {
|
||||||
msgGroup.addItem(field("cp_key", cpKey));
|
msgGroup.addItem(field("cp_key", cpKey));
|
||||||
@@ -414,10 +437,6 @@ class TemplateAdapterErrorMsgHandlerTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ================================================================
|
|
||||||
// 배열 반복 {{#foreach path}}...{{/foreach}}
|
|
||||||
// ================================================================
|
|
||||||
|
|
||||||
// ================================================================
|
// ================================================================
|
||||||
// DATA 영역 변수 치환 ${DATA.xxx}
|
// DATA 영역 변수 치환 ${DATA.xxx}
|
||||||
// ================================================================
|
// ================================================================
|
||||||
@@ -426,17 +445,11 @@ class TemplateAdapterErrorMsgHandlerTest {
|
|||||||
@DisplayName("DATA 영역 변수 치환 ${DATA.xxx}")
|
@DisplayName("DATA 영역 변수 치환 ${DATA.xxx}")
|
||||||
class DataSectionSubstitution {
|
class DataSectionSubstitution {
|
||||||
|
|
||||||
/**
|
|
||||||
* DATA > BIZDATA(FIELD, JSON String value) 구조의 StandardMessage 생성.
|
|
||||||
* BIZDATA 는 파싱 없이 JSON 문자열 그대로 세팅되는 형태.
|
|
||||||
*/
|
|
||||||
private StandardMessage buildMsgWithBizData(String bizDataJson) {
|
private StandardMessage buildMsgWithBizData(String bizDataJson) {
|
||||||
StandardMessage msg = buildMsg("E001", "오류", "", null);
|
StandardMessage msg = buildMsg("E001", "오류", "", null);
|
||||||
|
|
||||||
StandardItem dataGroup = group("DATA");
|
StandardItem dataGroup = group("DATA");
|
||||||
dataGroup.addItem(field("BIZDATA", bizDataJson));
|
dataGroup.addItem(field("BIZDATA", bizDataJson));
|
||||||
msg.addItem(dataGroup);
|
msg.addItem(dataGroup);
|
||||||
|
|
||||||
return msg;
|
return msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -502,6 +515,10 @@ class TemplateAdapterErrorMsgHandlerTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ================================================================
|
||||||
|
// 배열 반복 {{#foreach path}}...{{/foreach}}
|
||||||
|
// ================================================================
|
||||||
|
|
||||||
@Nested
|
@Nested
|
||||||
@DisplayName("배열 반복 블록 {{#foreach}}")
|
@DisplayName("배열 반복 블록 {{#foreach}}")
|
||||||
class ForeachBlock {
|
class ForeachBlock {
|
||||||
@@ -538,6 +555,17 @@ class TemplateAdapterErrorMsgHandlerTest {
|
|||||||
assertEquals("[]", handler.render(template, msg, null));
|
assertEquals("[]", handler.render(template, msg, null));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("# 이 HTML 인코딩된 foreach 도 정상 처리")
|
||||||
|
void HTML_인코딩된_foreach() {
|
||||||
|
StandardMessage msg = buildMsg("E001", "대표오류", "",
|
||||||
|
new String[][]{{"E001", "오류A", "", ""}});
|
||||||
|
// 관리 UI 가 # 을 # 로 저장한 경우
|
||||||
|
String template = "[{{#foreach MSG.MSG_LIST}}{\"c\":\"${outp_msg_cd}\"}{{/foreach}}]";
|
||||||
|
|
||||||
|
assertEquals("[{\"c\":\"E001\"}]", handler.render(template, msg, null));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@DisplayName("foreach 경로가 없으면 빈 문자열")
|
@DisplayName("foreach 경로가 없으면 빈 문자열")
|
||||||
void 없는_경로() {
|
void 없는_경로() {
|
||||||
@@ -556,6 +584,266 @@ class TemplateAdapterErrorMsgHandlerTest {
|
|||||||
|
|
||||||
assertEquals("{\"field\":\"accountNo\"}", handler.render(template, msg, null));
|
assertEquals("{\"field\":\"accountNo\"}", handler.render(template, msg, null));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("스칼라 → foreach → 스칼라 순서 처리 – 단일 패스 검증 (사용자 템플릿 형식)")
|
||||||
|
void 스칼라_foreach_스칼라_단일패스() {
|
||||||
|
StandardMessage msg = buildMsg("E001", "대표오류", "",
|
||||||
|
new String[][]{{"E001", "오류A", "설명A", "fieldA"}});
|
||||||
|
// 스칼라 앞, foreach 중간, 스칼라 뒤 순서로 배치
|
||||||
|
String template =
|
||||||
|
"{\"mainCode\":\"${MSG.MAIN_MSG.outp_msg_cd}\"," +
|
||||||
|
"{{#foreach MSG.MSG_LIST}}{\"code\":\"${outp_msg_cd}\"}{{/foreach}}," +
|
||||||
|
"\"mainMsg\":\"${MSG.MAIN_MSG.outp_msg_ctnt}\"}";
|
||||||
|
|
||||||
|
String result = handler.render(template, msg, null);
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
"{\"mainCode\":\"E001\"," +
|
||||||
|
"{\"code\":\"E001\"}," +
|
||||||
|
"\"mainMsg\":\"대표오류\"}",
|
||||||
|
result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("foreach 앞뒤 스칼라 + 2건 리스트 – 단일 패스 검증")
|
||||||
|
void 스칼라_foreach_스칼라_다건() {
|
||||||
|
StandardMessage msg = buildMsg("E001", "대표오류", "",
|
||||||
|
new String[][]{
|
||||||
|
{"E001", "오류A", "", ""},
|
||||||
|
{"E002", "오류B", "", ""}
|
||||||
|
});
|
||||||
|
String template =
|
||||||
|
"{\"code\":\"${MSG.MAIN_MSG.outp_msg_cd}\"," +
|
||||||
|
"\"errors\":[{{#foreach MSG.MSG_LIST}}{\"c\":\"${outp_msg_cd}\"}{{/foreach}}]," +
|
||||||
|
"\"msg\":\"${MSG.MAIN_MSG.outp_msg_ctnt}\"}";
|
||||||
|
|
||||||
|
String result = handler.render(template, msg, null);
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
"{\"code\":\"E001\"," +
|
||||||
|
"\"errors\":[{\"c\":\"E001\"},{\"c\":\"E002\"}]," +
|
||||||
|
"\"msg\":\"대표오류\"}",
|
||||||
|
result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ================================================================
|
||||||
|
// 예외 객체 필드 치환 ${exception.필드명}
|
||||||
|
// ================================================================
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
@DisplayName("예외 객체 필드 치환 ${exception.필드명}")
|
||||||
|
class ExceptionSubstitution {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("exception.message → getMessage() 반환")
|
||||||
|
void exception_message() {
|
||||||
|
StandardMessage msg = buildMsg("E001", "오류", "", null);
|
||||||
|
RuntimeException e = new RuntimeException("처리 중 오류 발생");
|
||||||
|
|
||||||
|
String result = handler.render("${exception.message}", msg, null, e);
|
||||||
|
|
||||||
|
assertEquals("처리 중 오류 발생", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("HttpStatusException.status → getStatus() 반환")
|
||||||
|
void HttpStatusException_status() {
|
||||||
|
HttpStatusException e = new HttpStatusException("Bad Request", 400);
|
||||||
|
|
||||||
|
assertEquals("400", handler.render("${exception.status}", null, null, e));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("HttpStatusException.code → getCode() 반환")
|
||||||
|
void HttpStatusException_code() {
|
||||||
|
HttpStatusException e = new HttpStatusException("오류", "ERR_CODE", 400);
|
||||||
|
|
||||||
|
assertEquals("ERR_CODE", handler.render("${exception.code}", null, null, e));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("JwtAuthException.code → getCode() 반환")
|
||||||
|
void JwtAuthException_code() {
|
||||||
|
JwtAuthException e = new JwtAuthException("JWT_EXPIRED", "토큰 만료");
|
||||||
|
|
||||||
|
assertEquals("JWT_EXPIRED", handler.render("${exception.code}", null, null, e));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("JwtAuthException.message → getMessage() 반환")
|
||||||
|
void JwtAuthException_message() {
|
||||||
|
JwtAuthException e = new JwtAuthException("JWT_EXPIRED", "토큰이 만료되었습니다");
|
||||||
|
|
||||||
|
assertEquals("토큰이 만료되었습니다", handler.render("${exception.message}", null, null, e));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("존재하지 않는 필드는 빈 문자열")
|
||||||
|
void 없는_필드_빈문자열() {
|
||||||
|
RuntimeException e = new RuntimeException("오류");
|
||||||
|
|
||||||
|
assertEquals("", handler.render("${exception.noneField}", null, null, e));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("존재하지 않는 필드에 기본값 지정")
|
||||||
|
void 없는_필드_기본값() {
|
||||||
|
RuntimeException e = new RuntimeException("오류");
|
||||||
|
|
||||||
|
assertEquals("UNKNOWN", handler.render("${exception.noneField:UNKNOWN}", null, null, e));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("exception 이 null 이면 기본값 반환")
|
||||||
|
void exception_null_기본값() {
|
||||||
|
assertEquals("DEFAULT", handler.render("${exception.message:DEFAULT}", null, null, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("exception + callProp + StandardMessage 혼합 치환")
|
||||||
|
void exception_callProp_stdmsg_혼합() {
|
||||||
|
StandardMessage msg = buildMsg("E001", "대표오류", "", null);
|
||||||
|
Properties callProp = props("IF_ID", "SVC001");
|
||||||
|
HttpStatusException e = new HttpStatusException("Bad Request", 400);
|
||||||
|
String template =
|
||||||
|
"{\"ifId\":\"${callprop.IF_ID}\"," +
|
||||||
|
"\"code\":\"${MSG.MAIN_MSG.outp_msg_cd}\"," +
|
||||||
|
"\"httpStatus\":\"${exception.status}\"," +
|
||||||
|
"\"errMsg\":\"${exception.message}\"}";
|
||||||
|
|
||||||
|
String result = handler.render(template, msg, callProp, e);
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
"{\"ifId\":\"SVC001\"," +
|
||||||
|
"\"code\":\"E001\"," +
|
||||||
|
"\"httpStatus\":\"400\"," +
|
||||||
|
"\"errMsg\":\"Bad Request\"}",
|
||||||
|
result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("부모 클래스(Throwable) 필드도 리플렉션으로 접근")
|
||||||
|
void 부모클래스_필드_접근() {
|
||||||
|
// Throwable 의 detailMessage 필드 (getMessage() 로 접근됨)
|
||||||
|
HttpStatusException e = new HttpStatusException("서비스 불가", 503);
|
||||||
|
|
||||||
|
// getStatus 는 HttpStatusException 직접 정의 메서드
|
||||||
|
assertEquals("503", handler.render("${exception.status}", null, null, e));
|
||||||
|
// getMessage 는 Throwable 상속 메서드
|
||||||
|
assertEquals("서비스 불가", handler.render("${exception.message}", null, null, e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ================================================================
|
||||||
|
// 배열 인덱스 직접 접근 ${path[n].field}
|
||||||
|
// ================================================================
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
@DisplayName("배열 인덱스 직접 접근 ${path[n].field}")
|
||||||
|
class ArrayIndexSubstitution {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("첫 번째 행(인덱스 0) 필드 접근")
|
||||||
|
void 첫번째_행_필드_접근() {
|
||||||
|
StandardMessage msg = buildMsg("E001", "대표오류", "",
|
||||||
|
new String[][]{
|
||||||
|
{"E001", "오류A", "설명A", "fieldA"},
|
||||||
|
{"E002", "오류B", "설명B", "fieldB"}
|
||||||
|
});
|
||||||
|
|
||||||
|
assertEquals("E001", handler.render("${MSG.MSG_LIST[0].outp_msg_cd}", msg, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("두 번째 행(인덱스 1) 필드 접근")
|
||||||
|
void 두번째_행_필드_접근() {
|
||||||
|
StandardMessage msg = buildMsg("E001", "대표오류", "",
|
||||||
|
new String[][]{
|
||||||
|
{"E001", "오류A", "설명A", "fieldA"},
|
||||||
|
{"E002", "오류B", "설명B", "fieldB"}
|
||||||
|
});
|
||||||
|
|
||||||
|
assertEquals("E002", handler.render("${MSG.MSG_LIST[1].outp_msg_cd}", msg, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("여러 인덱스를 한 템플릿에서 동시 참조")
|
||||||
|
void 여러_인덱스_동시_참조() {
|
||||||
|
StandardMessage msg = buildMsg("E001", "대표오류", "",
|
||||||
|
new String[][]{
|
||||||
|
{"E001", "오류A", "", ""},
|
||||||
|
{"E002", "오류B", "", ""}
|
||||||
|
});
|
||||||
|
String template = "{\"first\":\"${MSG.MSG_LIST[0].outp_msg_cd}\",\"second\":\"${MSG.MSG_LIST[1].outp_msg_cd}\"}";
|
||||||
|
|
||||||
|
assertEquals("{\"first\":\"E001\",\"second\":\"E002\"}", handler.render(template, msg, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("인덱스가 범위를 벗어나면 기본값 반환")
|
||||||
|
void 인덱스_범위_초과_기본값() {
|
||||||
|
StandardMessage msg = buildMsg("E001", "오류", "",
|
||||||
|
new String[][]{{"E001", "오류A", "", ""}});
|
||||||
|
|
||||||
|
assertEquals("NONE", handler.render("${MSG.MSG_LIST[5].outp_msg_cd:NONE}", msg, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("인덱스가 범위를 벗어나면 빈 문자열 (기본값 미지정)")
|
||||||
|
void 인덱스_범위_초과_빈문자열() {
|
||||||
|
StandardMessage msg = buildMsg("E001", "오류", "",
|
||||||
|
new String[][]{{"E001", "오류A", "", ""}});
|
||||||
|
|
||||||
|
assertEquals("", handler.render("${MSG.MSG_LIST[9].outp_msg_cd}", msg, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("MSG_LIST 가 비어있을 때 인덱스 접근은 빈 문자열")
|
||||||
|
void 빈_배열_인덱스_접근() {
|
||||||
|
StandardMessage msg = buildMsg("E001", "오류", "", new String[][]{});
|
||||||
|
|
||||||
|
assertEquals("", handler.render("${MSG.MSG_LIST[0].outp_msg_cd}", msg, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("배열 경로가 없으면 기본값 반환")
|
||||||
|
void 없는_배열_경로() {
|
||||||
|
StandardMessage msg = buildMsg("E001", "오류", "", null);
|
||||||
|
|
||||||
|
assertEquals("DEFAULT", handler.render("${NONE.LIST[0].outp_msg_cd:DEFAULT}", msg, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("err_occu_item_nm 필드 인덱스 접근")
|
||||||
|
void err_occu_item_nm_인덱스_접근() {
|
||||||
|
StandardMessage msg = buildMsg("E001", "오류", "",
|
||||||
|
new String[][]{
|
||||||
|
{"E001", "오류A", "", "accountNo"},
|
||||||
|
{"E002", "오류B", "", "amount"}
|
||||||
|
});
|
||||||
|
|
||||||
|
assertEquals("accountNo", handler.render("${MSG.MSG_LIST[0].err_occu_item_nm}", msg, null));
|
||||||
|
assertEquals("amount", handler.render("${MSG.MSG_LIST[1].err_occu_item_nm}", msg, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("인덱스 접근과 MAIN_MSG 스칼라 혼합")
|
||||||
|
void 인덱스_접근_스칼라_혼합() {
|
||||||
|
StandardMessage msg = buildMsg("E001", "대표오류", "",
|
||||||
|
new String[][]{{"E001", "오류A", "", "fieldA"}});
|
||||||
|
String template =
|
||||||
|
"{\"mainCode\":\"${MSG.MAIN_MSG.outp_msg_cd}\"," +
|
||||||
|
"\"firstCode\":\"${MSG.MSG_LIST[0].outp_msg_cd}\"," +
|
||||||
|
"\"firstField\":\"${MSG.MSG_LIST[0].err_occu_item_nm}\"}";
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
"{\"mainCode\":\"E001\"," +
|
||||||
|
"\"firstCode\":\"E001\"," +
|
||||||
|
"\"firstField\":\"fieldA\"}",
|
||||||
|
handler.render(template, msg, null));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ================================================================
|
// ================================================================
|
||||||
@@ -627,21 +915,14 @@ class TemplateAdapterErrorMsgHandlerTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ================================================================
|
// ================================================================
|
||||||
// generateNonStandardErrorResponseMessage – PropManager mock
|
// generateNonStandardErrorResponseMessage
|
||||||
|
// 템플릿 키: {adapterGroupId}.template
|
||||||
// ================================================================
|
// ================================================================
|
||||||
|
|
||||||
@Nested
|
@Nested
|
||||||
@DisplayName("generateNonStandardErrorResponseMessage")
|
@DisplayName("generateNonStandardErrorResponseMessage")
|
||||||
class GenerateMethod {
|
class GenerateMethod {
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@DisplayName("템플릿 미설정 시 null 반환")
|
@DisplayName("템플릿 미설정 시 null 반환")
|
||||||
void 템플릿_없으면_null() throws Exception {
|
void 템플릿_없으면_null() throws Exception {
|
||||||
@@ -649,9 +930,9 @@ class TemplateAdapterErrorMsgHandlerTest {
|
|||||||
Mockito.when(mockProp.getProperty(anyString(), anyString())).thenReturn(null);
|
Mockito.when(mockProp.getProperty(anyString(), anyString())).thenReturn(null);
|
||||||
injectMockPropManager(mockProp);
|
injectMockPropManager(mockProp);
|
||||||
|
|
||||||
StandardMessage msg = buildMsg("E001", "오류", "", null);
|
EAIMessage resEaiMsg = toEaiMessage(buildMsg("E001", "오류", "", null));
|
||||||
Object result = handler.generateNonStandardErrorResponseMessage(
|
Object result = handler.generateNonStandardErrorResponseMessage(
|
||||||
"TEST_GROUP", "TEST_ADAPTER", new Properties(), null, msg);
|
"TEST_GROUP", "TEST_ADAPTER", new Properties(), null, resEaiMsg);
|
||||||
|
|
||||||
assertNull(result);
|
assertNull(result);
|
||||||
}
|
}
|
||||||
@@ -663,9 +944,9 @@ class TemplateAdapterErrorMsgHandlerTest {
|
|||||||
Mockito.when(mockProp.getProperty(anyString(), anyString())).thenReturn(" ");
|
Mockito.when(mockProp.getProperty(anyString(), anyString())).thenReturn(" ");
|
||||||
injectMockPropManager(mockProp);
|
injectMockPropManager(mockProp);
|
||||||
|
|
||||||
StandardMessage msg = buildMsg("E001", "오류", "", null);
|
EAIMessage resEaiMsg = toEaiMessage(buildMsg("E001", "오류", "", null));
|
||||||
Object result = handler.generateNonStandardErrorResponseMessage(
|
Object result = handler.generateNonStandardErrorResponseMessage(
|
||||||
"TEST_GROUP", "TEST_ADAPTER", new Properties(), null, msg);
|
"TEST_GROUP", "TEST_ADAPTER", new Properties(), null, resEaiMsg);
|
||||||
|
|
||||||
assertNull(result);
|
assertNull(result);
|
||||||
}
|
}
|
||||||
@@ -680,9 +961,9 @@ class TemplateAdapterErrorMsgHandlerTest {
|
|||||||
eq("MY_GROUP.template"))).thenReturn(template);
|
eq("MY_GROUP.template"))).thenReturn(template);
|
||||||
injectMockPropManager(mockProp);
|
injectMockPropManager(mockProp);
|
||||||
|
|
||||||
StandardMessage msg = buildMsg("E999", "테스트오류", "", null);
|
EAIMessage resEaiMsg = toEaiMessage(buildMsg("E999", "테스트오류", "", null));
|
||||||
Object result = handler.generateNonStandardErrorResponseMessage(
|
Object result = handler.generateNonStandardErrorResponseMessage(
|
||||||
"MY_GROUP", "MY_ADAPTER", new Properties(), null, msg);
|
"MY_GROUP", "MY_ADAPTER", new Properties(), null, resEaiMsg);
|
||||||
|
|
||||||
assertEquals("{\"code\":\"E999\"}", result);
|
assertEquals("{\"code\":\"E999\"}", result);
|
||||||
}
|
}
|
||||||
@@ -697,11 +978,10 @@ class TemplateAdapterErrorMsgHandlerTest {
|
|||||||
eq("MY_GROUP.template"))).thenReturn(template);
|
eq("MY_GROUP.template"))).thenReturn(template);
|
||||||
injectMockPropManager(mockProp);
|
injectMockPropManager(mockProp);
|
||||||
|
|
||||||
StandardMessage msg = buildMsg("E999", "테스트오류", "", null);
|
EAIMessage resEaiMsg = toEaiMessage(buildMsg("E999", "테스트오류", "", null));
|
||||||
Properties callProp = props("ADAPTER_NAME", "REAL_ADAPTER");
|
Properties callProp = props("ADAPTER_NAME", "REAL_ADAPTER");
|
||||||
|
|
||||||
Object result = handler.generateNonStandardErrorResponseMessage(
|
Object result = handler.generateNonStandardErrorResponseMessage(
|
||||||
"MY_GROUP", "MY_ADAPTER", callProp, null, msg);
|
"MY_GROUP", "MY_ADAPTER", callProp, null, resEaiMsg);
|
||||||
|
|
||||||
assertEquals("{\"adapter\":\"REAL_ADAPTER\",\"code\":\"E999\"}", result);
|
assertEquals("{\"adapter\":\"REAL_ADAPTER\",\"code\":\"E999\"}", result);
|
||||||
}
|
}
|
||||||
@@ -713,13 +993,394 @@ class TemplateAdapterErrorMsgHandlerTest {
|
|||||||
Mockito.when(mockProp.getProperty(anyString(), anyString())).thenReturn(null);
|
Mockito.when(mockProp.getProperty(anyString(), anyString())).thenReturn(null);
|
||||||
injectMockPropManager(mockProp);
|
injectMockPropManager(mockProp);
|
||||||
|
|
||||||
StandardMessage msg = buildMsg("E001", "오류", "", null);
|
EAIMessage resEaiMsg = toEaiMessage(buildMsg("E001", "오류", "", null));
|
||||||
handler.generateNonStandardErrorResponseMessage(
|
handler.generateNonStandardErrorResponseMessage(
|
||||||
"SAMPLE_GROUP", "ANY_ADAPTER", new Properties(), null, msg);
|
"SAMPLE_GROUP", "ANY_ADAPTER", new Properties(), null, resEaiMsg);
|
||||||
|
|
||||||
Mockito.verify(mockProp).getProperty(
|
Mockito.verify(mockProp).getProperty(
|
||||||
TemplateAdapterErrorMsgHandler.PROP_GROUP,
|
TemplateAdapterErrorMsgHandler.PROP_GROUP,
|
||||||
"SAMPLE_GROUP.template");
|
"SAMPLE_GROUP.template");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ================================================================
|
||||||
|
// generateNonStandardInternalErrorResponseMessage (ExceptionHandler 내부 오류용)
|
||||||
|
// 템플릿 키: {adapterGroupId}.sys.template
|
||||||
|
// ================================================================
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
@DisplayName("generateNonStandardInternalErrorResponseMessage")
|
||||||
|
class GenerateInternalMethod {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("템플릿 미설정 시 null 반환")
|
||||||
|
void 템플릿_없으면_null() throws Exception {
|
||||||
|
PropManager mockProp = Mockito.mock(PropManager.class);
|
||||||
|
Mockito.when(mockProp.getProperty(anyString(), anyString())).thenReturn(null);
|
||||||
|
injectMockPropManager(mockProp);
|
||||||
|
|
||||||
|
EAIMessage eaiMessage = toEaiMessage(buildMsg("E001", "오류", "", null));
|
||||||
|
Object result = handler.generateNonStandardInternalErrorResponseMessage(
|
||||||
|
"TEST_GROUP", "TEST_ADAPTER", null, null, eaiMessage);
|
||||||
|
|
||||||
|
assertNull(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("빈 템플릿 시 null 반환")
|
||||||
|
void 빈_템플릿_null() throws Exception {
|
||||||
|
PropManager mockProp = Mockito.mock(PropManager.class);
|
||||||
|
Mockito.when(mockProp.getProperty(anyString(), anyString())).thenReturn(" ");
|
||||||
|
injectMockPropManager(mockProp);
|
||||||
|
|
||||||
|
EAIMessage eaiMessage = toEaiMessage(buildMsg("E001", "오류", "", null));
|
||||||
|
Object result = handler.generateNonStandardInternalErrorResponseMessage(
|
||||||
|
"TEST_GROUP", "TEST_ADAPTER", null, null, eaiMessage);
|
||||||
|
|
||||||
|
assertNull(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("템플릿 설정 시 EAIMessage 의 StandardMessage 로 치환")
|
||||||
|
void 템플릿_stdmsg_치환() throws Exception {
|
||||||
|
String template = "{\"code\":\"${MSG.MAIN_MSG.outp_msg_cd}\",\"msg\":\"${MSG.MAIN_MSG.outp_msg_ctnt}\"}";
|
||||||
|
PropManager mockProp = Mockito.mock(PropManager.class);
|
||||||
|
Mockito.when(mockProp.getProperty(
|
||||||
|
eq(TemplateAdapterErrorMsgHandler.PROP_GROUP),
|
||||||
|
eq("MY_GROUP.sys.template"))).thenReturn(template);
|
||||||
|
injectMockPropManager(mockProp);
|
||||||
|
|
||||||
|
EAIMessage eaiMessage = toEaiMessage(buildMsg("E999", "내부오류", "", null));
|
||||||
|
Object result = handler.generateNonStandardInternalErrorResponseMessage(
|
||||||
|
"MY_GROUP", "MY_ADAPTER", null, null, eaiMessage);
|
||||||
|
|
||||||
|
assertEquals("{\"code\":\"E999\",\"msg\":\"내부오류\"}", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("callProp 이 null 이어도 스칼라 기본값 처리 (ExceptionHandler 에서 null 전달)")
|
||||||
|
void callProp_null_기본값_처리() throws Exception {
|
||||||
|
String template = "{\"code\":\"${MSG.MAIN_MSG.outp_msg_cd}\",\"adapter\":\"${callprop.ADAPTER_NAME:UNKNOWN}\"}";
|
||||||
|
PropManager mockProp = Mockito.mock(PropManager.class);
|
||||||
|
Mockito.when(mockProp.getProperty(
|
||||||
|
eq(TemplateAdapterErrorMsgHandler.PROP_GROUP),
|
||||||
|
eq("MY_GROUP.sys.template"))).thenReturn(template);
|
||||||
|
injectMockPropManager(mockProp);
|
||||||
|
|
||||||
|
EAIMessage eaiMessage = toEaiMessage(buildMsg("E500", "시스템오류", "", null));
|
||||||
|
Object result = handler.generateNonStandardInternalErrorResponseMessage(
|
||||||
|
"MY_GROUP", "MY_ADAPTER", null, null, eaiMessage);
|
||||||
|
|
||||||
|
// callProp null → 기본값 UNKNOWN 반환
|
||||||
|
assertEquals("{\"code\":\"E500\",\"adapter\":\"UNKNOWN\"}", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("MSG_LIST foreach 포함 템플릿 치환")
|
||||||
|
void foreach_MSG_LIST_포함() throws Exception {
|
||||||
|
String template =
|
||||||
|
"{\"code\":\"${MSG.MAIN_MSG.outp_msg_cd}\"," +
|
||||||
|
"\"errors\":[{{#foreach MSG.MSG_LIST}}" +
|
||||||
|
"{\"c\":\"${outp_msg_cd}\",\"m\":\"${outp_msg_ctnt}\"}" +
|
||||||
|
"{{/foreach}}]}";
|
||||||
|
PropManager mockProp = Mockito.mock(PropManager.class);
|
||||||
|
Mockito.when(mockProp.getProperty(
|
||||||
|
eq(TemplateAdapterErrorMsgHandler.PROP_GROUP),
|
||||||
|
eq("GW_GROUP.sys.template"))).thenReturn(template);
|
||||||
|
injectMockPropManager(mockProp);
|
||||||
|
|
||||||
|
EAIMessage eaiMessage = toEaiMessage(buildMsg("E001", "대표오류", "",
|
||||||
|
new String[][]{{"E001", "오류A", "", ""}, {"E002", "오류B", "", ""}}));
|
||||||
|
Object result = handler.generateNonStandardInternalErrorResponseMessage(
|
||||||
|
"GW_GROUP", "GW_ADAPTER", null, null, eaiMessage);
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
"{\"code\":\"E001\"," +
|
||||||
|
"\"errors\":[{\"c\":\"E001\",\"m\":\"오류A\"}," +
|
||||||
|
"{\"c\":\"E002\",\"m\":\"오류B\"}]}",
|
||||||
|
result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("템플릿 키는 {adapterGroupId}.sys.template 형식으로 조회")
|
||||||
|
void 템플릿_키_형식_검증() throws Exception {
|
||||||
|
PropManager mockProp = Mockito.mock(PropManager.class);
|
||||||
|
Mockito.when(mockProp.getProperty(anyString(), anyString())).thenReturn(null);
|
||||||
|
injectMockPropManager(mockProp);
|
||||||
|
|
||||||
|
EAIMessage eaiMessage = toEaiMessage(buildMsg("E001", "오류", "", null));
|
||||||
|
handler.generateNonStandardInternalErrorResponseMessage(
|
||||||
|
"SAMPLE_GROUP", "ANY_ADAPTER", null, null, eaiMessage);
|
||||||
|
|
||||||
|
Mockito.verify(mockProp).getProperty(
|
||||||
|
TemplateAdapterErrorMsgHandler.PROP_GROUP,
|
||||||
|
"SAMPLE_GROUP.sys.template");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("스칼라 → foreach → 스칼라 순서 – 사용자 실제 템플릿 형식")
|
||||||
|
void 사용자_실제_템플릿_형식() throws Exception {
|
||||||
|
// ExceptionHandler 에서 사용되는 실제 템플릿 패턴: 스칼라, foreach, 스칼라 순서
|
||||||
|
String template =
|
||||||
|
"{\"aeh-sys-resultCode1\":\"${MSG.MAIN_MSG.outp_msg_cd}\"," +
|
||||||
|
"{{#foreach MSG.MSG_LIST}}" +
|
||||||
|
"\"aeh-sys-resultCode1\":\"${outp_msg_cd}\"" +
|
||||||
|
"{{/foreach}}," +
|
||||||
|
"\"aeh-sys-resultMsg1\":\"${MSG.MAIN_MSG.outp_msg_ctnt}\"}";
|
||||||
|
PropManager mockProp = Mockito.mock(PropManager.class);
|
||||||
|
Mockito.when(mockProp.getProperty(
|
||||||
|
eq(TemplateAdapterErrorMsgHandler.PROP_GROUP),
|
||||||
|
eq("GW_GROUP.sys.template"))).thenReturn(template);
|
||||||
|
injectMockPropManager(mockProp);
|
||||||
|
|
||||||
|
EAIMessage eaiMessage = toEaiMessage(buildMsg("E001", "오류메시지", "",
|
||||||
|
new String[][]{{"E001", "오류A", "", ""}}));
|
||||||
|
Object result = handler.generateNonStandardInternalErrorResponseMessage(
|
||||||
|
"GW_GROUP", "GW_ADAPTER", null, null, eaiMessage);
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
"{\"aeh-sys-resultCode1\":\"E001\"," +
|
||||||
|
"\"aeh-sys-resultCode1\":\"E001\"," +
|
||||||
|
"\"aeh-sys-resultMsg1\":\"오류메시지\"}",
|
||||||
|
result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ================================================================
|
||||||
|
// generateNonStandardInboundErrorResponseMessage (인바운드 오류 응답)
|
||||||
|
// 템플릿 키: {adapterGroupId}.in.{httpCode}.template → {adapterGroupId}.in.template 순서로 조회
|
||||||
|
// ================================================================
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
@DisplayName("generateNonStandardInboundErrorResponseMessage")
|
||||||
|
class GenerateInboundMethod {
|
||||||
|
|
||||||
|
private AdapterManager mockAdapterManager;
|
||||||
|
private AdapterPropManager mockAdapterPropManager;
|
||||||
|
private PropManager mockPropManager;
|
||||||
|
private AdapterGroupVO mockAdapterGroupVO;
|
||||||
|
private AdapterVO mockAdapterVO;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUpInbound() throws Exception {
|
||||||
|
mockAdapterManager = Mockito.mock(AdapterManager.class);
|
||||||
|
mockAdapterPropManager = Mockito.mock(AdapterPropManager.class);
|
||||||
|
mockPropManager = Mockito.mock(PropManager.class);
|
||||||
|
mockAdapterGroupVO = Mockito.mock(AdapterGroupVO.class);
|
||||||
|
mockAdapterVO = Mockito.mock(AdapterVO.class);
|
||||||
|
|
||||||
|
Mockito.when(mockAdapterGroupVO.getMessageType()).thenReturn("JSON");
|
||||||
|
Mockito.when(mockAdapterGroupVO.getMessageEncode()).thenReturn("UTF-8");
|
||||||
|
Mockito.when(mockAdapterVO.getPropGroupName()).thenReturn("TEST_PROP");
|
||||||
|
Mockito.when(mockAdapterVO.getAdapterGroupVO()).thenReturn(mockAdapterGroupVO);
|
||||||
|
Mockito.when(mockAdapterManager.getAdapterGroupVO("TEST_GROUP")).thenReturn(mockAdapterGroupVO);
|
||||||
|
Mockito.when(mockAdapterManager.getAdapterVO("TEST_GROUP", "TEST_ADAPTER")).thenReturn(mockAdapterVO);
|
||||||
|
|
||||||
|
Properties httpProp = new Properties();
|
||||||
|
httpProp.setProperty("ERROR_RESPONSE_FORMAT", "");
|
||||||
|
Mockito.when(mockAdapterPropManager.getProperties("TEST_PROP")).thenReturn(httpProp);
|
||||||
|
Mockito.when(mockPropManager.getProperty(anyString(), anyString())).thenReturn(null);
|
||||||
|
// 3-인자 getProperty(group, key, defaultValue) → defaultValue 반환
|
||||||
|
// (MessageUtil.makeJsonErrorMessage 에서 ERROR_MESSAGE_DEFAULT_FORMAT 폴백 시 사용)
|
||||||
|
Mockito.when(mockPropManager.getProperty(anyString(), anyString(), anyString()))
|
||||||
|
.thenAnswer(invocation -> invocation.getArgument(2));
|
||||||
|
|
||||||
|
ApplicationContext mockCtx = Mockito.mock(ApplicationContext.class);
|
||||||
|
Mockito.when(mockCtx.getBean(AdapterManager.class)).thenReturn(mockAdapterManager);
|
||||||
|
Mockito.when(mockCtx.getBean(AdapterPropManager.class)).thenReturn(mockAdapterPropManager);
|
||||||
|
Mockito.when(mockCtx.getBean(PropManager.class)).thenReturn(mockPropManager);
|
||||||
|
Field ctxField = ApplicationContextProvider.class.getDeclaredField("context");
|
||||||
|
ctxField.setAccessible(true);
|
||||||
|
ctxField.set(null, mockCtx);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("HttpStatusException → httpCode 전용 템플릿 우선 사용")
|
||||||
|
void HttpStatusException_전용_템플릿() throws Exception {
|
||||||
|
Mockito.when(mockPropManager.getProperty(
|
||||||
|
eq(TemplateAdapterErrorMsgHandler.PROP_GROUP),
|
||||||
|
eq("TEST_GROUP.in.400.template")))
|
||||||
|
.thenReturn("{\"status\":\"400\",\"msg\":\"Bad Request\"}");
|
||||||
|
|
||||||
|
Object result = handler.generateNonStandardInboundErrorResponseMessage(
|
||||||
|
"TEST_GROUP", "TEST_ADAPTER", null, null, null,
|
||||||
|
new HttpStatusException("Bad Request", 400));
|
||||||
|
|
||||||
|
assertEquals("{\"status\":\"400\",\"msg\":\"Bad Request\"}", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("HttpStatusException → 전용 템플릿 없으면 공통 in.template 사용")
|
||||||
|
void HttpStatusException_공통_템플릿_폴백() throws Exception {
|
||||||
|
Mockito.when(mockPropManager.getProperty(
|
||||||
|
eq(TemplateAdapterErrorMsgHandler.PROP_GROUP),
|
||||||
|
eq("TEST_GROUP.in.404.template"))).thenReturn(null);
|
||||||
|
Mockito.when(mockPropManager.getProperty(
|
||||||
|
eq(TemplateAdapterErrorMsgHandler.PROP_GROUP),
|
||||||
|
eq("TEST_GROUP.in.template")))
|
||||||
|
.thenReturn("{\"error\":\"not found\"}");
|
||||||
|
|
||||||
|
Object result = handler.generateNonStandardInboundErrorResponseMessage(
|
||||||
|
"TEST_GROUP", "TEST_ADAPTER", null, null, null,
|
||||||
|
new HttpStatusException("Not Found", 404));
|
||||||
|
|
||||||
|
assertEquals("{\"error\":\"not found\"}", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("JwtAuthException → 401 전용 템플릿 사용")
|
||||||
|
void JwtAuthException_401_전용_템플릿() throws Exception {
|
||||||
|
Mockito.when(mockPropManager.getProperty(
|
||||||
|
eq(TemplateAdapterErrorMsgHandler.PROP_GROUP),
|
||||||
|
eq("TEST_GROUP.in.401.template")))
|
||||||
|
.thenReturn("{\"error\":\"unauthorized\"}");
|
||||||
|
|
||||||
|
Object result = handler.generateNonStandardInboundErrorResponseMessage(
|
||||||
|
"TEST_GROUP", "TEST_ADAPTER", null, null, null,
|
||||||
|
new JwtAuthException("AUTH_ERR", "토큰 인증 실패"));
|
||||||
|
|
||||||
|
assertEquals("{\"error\":\"unauthorized\"}", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("JwtAuthException → 401 전용 없으면 공통 in.template 사용")
|
||||||
|
void JwtAuthException_공통_템플릿_폴백() throws Exception {
|
||||||
|
Mockito.when(mockPropManager.getProperty(
|
||||||
|
eq(TemplateAdapterErrorMsgHandler.PROP_GROUP),
|
||||||
|
eq("TEST_GROUP.in.401.template"))).thenReturn(null);
|
||||||
|
Mockito.when(mockPropManager.getProperty(
|
||||||
|
eq(TemplateAdapterErrorMsgHandler.PROP_GROUP),
|
||||||
|
eq("TEST_GROUP.in.template")))
|
||||||
|
.thenReturn("{\"error\":\"auth failed\"}");
|
||||||
|
|
||||||
|
Object result = handler.generateNonStandardInboundErrorResponseMessage(
|
||||||
|
"TEST_GROUP", "TEST_ADAPTER", null, null, null,
|
||||||
|
new JwtAuthException("AUTH_ERR", "토큰 만료"));
|
||||||
|
|
||||||
|
assertEquals("{\"error\":\"auth failed\"}", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("기타 예외 → 500 전용 템플릿 사용")
|
||||||
|
void 기타예외_500_전용_템플릿() throws Exception {
|
||||||
|
Mockito.when(mockPropManager.getProperty(
|
||||||
|
eq(TemplateAdapterErrorMsgHandler.PROP_GROUP),
|
||||||
|
eq("TEST_GROUP.in.500.template")))
|
||||||
|
.thenReturn("{\"error\":\"internal server error\"}");
|
||||||
|
|
||||||
|
Object result = handler.generateNonStandardInboundErrorResponseMessage(
|
||||||
|
"TEST_GROUP", "TEST_ADAPTER", null, null, null,
|
||||||
|
new RuntimeException("처리 중 오류"));
|
||||||
|
|
||||||
|
assertEquals("{\"error\":\"internal server error\"}", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("기타 예외 → 500 전용 없으면 공통 in.template 사용")
|
||||||
|
void 기타예외_공통_템플릿_폴백() throws Exception {
|
||||||
|
Mockito.when(mockPropManager.getProperty(
|
||||||
|
eq(TemplateAdapterErrorMsgHandler.PROP_GROUP),
|
||||||
|
eq("TEST_GROUP.in.500.template"))).thenReturn(null);
|
||||||
|
Mockito.when(mockPropManager.getProperty(
|
||||||
|
eq(TemplateAdapterErrorMsgHandler.PROP_GROUP),
|
||||||
|
eq("TEST_GROUP.in.template")))
|
||||||
|
.thenReturn("{\"error\":\"server error\"}");
|
||||||
|
|
||||||
|
Object result = handler.generateNonStandardInboundErrorResponseMessage(
|
||||||
|
"TEST_GROUP", "TEST_ADAPTER", null, null, null,
|
||||||
|
new RuntimeException("처리 실패"));
|
||||||
|
|
||||||
|
assertEquals("{\"error\":\"server error\"}", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("callProp 값이 inbound 템플릿에 치환")
|
||||||
|
void callProp_inbound_템플릿_치환() throws Exception {
|
||||||
|
Mockito.when(mockPropManager.getProperty(
|
||||||
|
eq(TemplateAdapterErrorMsgHandler.PROP_GROUP),
|
||||||
|
eq("TEST_GROUP.in.500.template")))
|
||||||
|
.thenReturn("{\"adapter\":\"${callprop.ADAPTER_NAME}\",\"ifId\":\"${callprop.IF_ID}\"}");
|
||||||
|
|
||||||
|
Properties callProp = props("ADAPTER_NAME", "MY_ADAPTER", "IF_ID", "SVC001");
|
||||||
|
|
||||||
|
Object result = handler.generateNonStandardInboundErrorResponseMessage(
|
||||||
|
"TEST_GROUP", "TEST_ADAPTER", callProp, null, null,
|
||||||
|
new RuntimeException("오류"));
|
||||||
|
|
||||||
|
assertEquals("{\"adapter\":\"MY_ADAPTER\",\"ifId\":\"SVC001\"}", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("inbound 템플릿은 msg=null 이므로 StandardMessage 경로는 빈 문자열")
|
||||||
|
void inbound_템플릿_stdmsg_경로_빈문자열() throws Exception {
|
||||||
|
Mockito.when(mockPropManager.getProperty(
|
||||||
|
eq(TemplateAdapterErrorMsgHandler.PROP_GROUP),
|
||||||
|
eq("TEST_GROUP.in.500.template")))
|
||||||
|
.thenReturn("{\"code\":\"${MSG.MAIN_MSG.outp_msg_cd:NONE}\",\"fixed\":\"value\"}");
|
||||||
|
|
||||||
|
Object result = handler.generateNonStandardInboundErrorResponseMessage(
|
||||||
|
"TEST_GROUP", "TEST_ADAPTER", null, null, null,
|
||||||
|
new RuntimeException("오류"));
|
||||||
|
|
||||||
|
// render(template, null, callProp) → StandardMessage 경로는 기본값으로 처리
|
||||||
|
assertEquals("{\"code\":\"NONE\",\"fixed\":\"value\"}", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("inbound 템플릿에서 ${exception.xxx} 로 예외 필드 참조")
|
||||||
|
void inbound_템플릿_exception_치환() throws Exception {
|
||||||
|
Mockito.when(mockPropManager.getProperty(
|
||||||
|
eq(TemplateAdapterErrorMsgHandler.PROP_GROUP),
|
||||||
|
eq("TEST_GROUP.in.400.template")))
|
||||||
|
.thenReturn("{\"status\":\"${exception.status}\",\"msg\":\"${exception.message}\",\"code\":\"${exception.code}\"}");
|
||||||
|
|
||||||
|
Object result = handler.generateNonStandardInboundErrorResponseMessage(
|
||||||
|
"TEST_GROUP", "TEST_ADAPTER", null, null, null,
|
||||||
|
new HttpStatusException("Bad Request", "ERR_400", 400));
|
||||||
|
|
||||||
|
assertEquals("{\"status\":\"400\",\"msg\":\"Bad Request\",\"code\":\"ERR_400\"}", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("JwtAuthException 템플릿에서 ${exception.code} 참조")
|
||||||
|
void inbound_jwt_exception_code_치환() throws Exception {
|
||||||
|
Mockito.when(mockPropManager.getProperty(
|
||||||
|
eq(TemplateAdapterErrorMsgHandler.PROP_GROUP),
|
||||||
|
eq("TEST_GROUP.in.401.template")))
|
||||||
|
.thenReturn("{\"code\":\"${exception.code}\",\"msg\":\"${exception.message}\"}");
|
||||||
|
|
||||||
|
Object result = handler.generateNonStandardInboundErrorResponseMessage(
|
||||||
|
"TEST_GROUP", "TEST_ADAPTER", null, null, null,
|
||||||
|
new JwtAuthException("JWT_EXPIRED", "토큰이 만료되었습니다"));
|
||||||
|
|
||||||
|
assertEquals("{\"code\":\"JWT_EXPIRED\",\"msg\":\"토큰이 만료되었습니다\"}", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("템플릿 조회 순서: 전용(httpCode) → 공통(in) 순서로 조회")
|
||||||
|
void 템플릿_조회_순서_검증() throws Exception {
|
||||||
|
// 두 템플릿 모두 null → MessageUtil 폴백 (조회 순서만 검증)
|
||||||
|
handler.generateNonStandardInboundErrorResponseMessage(
|
||||||
|
"TEST_GROUP", "TEST_ADAPTER", null, null, null,
|
||||||
|
new HttpStatusException("Service Unavailable", 503));
|
||||||
|
|
||||||
|
InOrder inOrder = Mockito.inOrder(mockPropManager);
|
||||||
|
inOrder.verify(mockPropManager).getProperty(
|
||||||
|
TemplateAdapterErrorMsgHandler.PROP_GROUP, "TEST_GROUP.in.503.template");
|
||||||
|
inOrder.verify(mockPropManager).getProperty(
|
||||||
|
TemplateAdapterErrorMsgHandler.PROP_GROUP, "TEST_GROUP.in.template");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("두 템플릿 모두 없으면 MessageUtil 폴백 결과가 non-null")
|
||||||
|
void 템플릿_없으면_MessageUtil_폴백() throws Exception {
|
||||||
|
// 모든 getProperty → null (기본 설정)
|
||||||
|
Object result = handler.generateNonStandardInboundErrorResponseMessage(
|
||||||
|
"TEST_GROUP", "TEST_ADAPTER", null, null, null,
|
||||||
|
new RuntimeException("오류"));
|
||||||
|
|
||||||
|
// MessageUtil.makeErrorMessageByMessageType 가 호출되어 non-null 반환
|
||||||
|
assertNotNull(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user