feat: 템플릿 기반 어댑터 에러 메시지 핸들러 추가

AdapterPropManager "AdapterErrorMessageHandler" 그룹의
{adapterGroupId}.template 키로 등록된 템플릿을 읽어
StandardMessage 및 callProp 값으로 치환한 에러 응답을 생성한다.

변수 치환 규칙:
- ${callprop.키}         → callProp.getProperty("키")
- ${MSG.MAIN_MSG.경로}  → StandardMessage.findItemValue("경로")
- {{#foreach 경로}}...{{/foreach}} → GRID 배열 반복 (쉼표 구분)

JSON/XML 템플릿 모두 지원

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