feat: DJBank 차세대 표준전문(R10) 초기 구현

- standard-layout-djb.csv: HEAD(평면구조)/EZDATA/MSG/DATA/전문종료부(@JJ) 레이아웃 정의
- standard-message-mapping-config-djb.properties: 논리키 → 필드경로 매핑 (GUID 40자, guid_prgs_no 별도 필드 등)
- standard-message-config.properties: DJB 클래스로 전환 (CSV 방식)
- GUIDGeneratorDJB: 40자 GUID 생성 (일자+시간+밀리초+시스템코드+노드+세션+Random)
- InterfaceMapperDJB: GUID 40자, guid_prgs_no 독립 필드, 응답코드 S/F 변환
- StandardMessageCoordinatorDJB: 전문 생성/응답/에러 각 단계 후처리
- 단위테스트 45개 전체 통과 (GUIDGenerator/InterfaceMapper/Layout 검증)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
curry772
2026-04-20 11:46:18 +09:00
parent 51724ab0f5
commit 18b3e781b1
9 changed files with 1252 additions and 4 deletions
@@ -0,0 +1,82 @@
package com.eactive.eai.custom.message;
import static org.junit.jupiter.api.Assertions.*;
import java.util.HashSet;
import java.util.Set;
import org.junit.jupiter.api.Test;
class GUIDGeneratorDJBTest {
@Test
void guid_길이는_40자여야_한다() {
String guid = GUIDGeneratorDJB.getGUID("EAI");
assertEquals(40, guid.length(), "GUID 길이는 40자: " + guid);
}
@Test
void guid_시스템코드_3자로_정규화된다() {
// 짧은 코드
String guid1 = GUIDGeneratorDJB.getGUID("EA");
assertEquals(40, guid1.length());
// 긴 코드 (잘림)
String guid2 = GUIDGeneratorDJB.getGUID("EAIFEP");
assertEquals(40, guid2.length());
// null
String guid3 = GUIDGeneratorDJB.getGUID(null);
assertEquals(40, guid3.length());
}
@Test
void guid_구성_형식_검증() {
String guid = GUIDGeneratorDJB.getGUID("EAI");
// 일자(8) + 시간(6) + 밀리초(3) + 시스템코드(3) + 노드번호(2) + 세션ID(12) + Random(6)
String date = guid.substring(0, 8);
String time = guid.substring(8, 14);
String millis = guid.substring(14, 17);
String sysCode = guid.substring(17, 20);
String nodeNo = guid.substring(20, 22);
String sessId = guid.substring(22, 34);
String random = guid.substring(34, 40);
// 일자: 숫자 8자리
assertDoesNotThrow(() -> Long.parseLong(date), "일자 부분이 숫자가 아님: " + date);
assertTrue(date.matches("\\d{8}"), "일자 형식 오류: " + date);
// 시간: 숫자 6자리
assertTrue(time.matches("\\d{6}"), "시간 형식 오류: " + time);
// 밀리초: 숫자 3자리
assertTrue(millis.matches("\\d{3}"), "밀리초 형식 오류: " + millis);
// 시스템코드: 3자리 영문/숫자
assertEquals(3, sysCode.length(), "시스템코드 길이 오류: " + sysCode);
// 노드번호: 2자리
assertEquals(2, nodeNo.length(), "노드번호 길이 오류: " + nodeNo);
// 세션ID: 12자리
assertEquals(12, sessId.length(), "세션ID 길이 오류: " + sessId);
// Random: 숫자 6자리
assertTrue(random.matches("\\d{6}"), "Random 형식 오류: " + random);
}
@Test
void guid_연속_생성시_유니크해야_한다() {
Set<String> guids = new HashSet<>();
int count = 1000;
for (int i = 0; i < count; i++) {
guids.add(GUIDGeneratorDJB.getGUID("EAI"));
}
assertEquals(count, guids.size(), "GUID 중복 발생");
}
@Test
void guid_상수_길이값_확인() {
assertEquals(40, GUIDGeneratorDJB.GUID_LENGTH);
}
}
@@ -0,0 +1,218 @@
package com.eactive.eai.custom.message;
import static org.junit.jupiter.api.Assertions.*;
import java.util.HashMap;
import java.util.Properties;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import com.eactive.eai.message.StandardMessage;
import com.eactive.eai.message.StandardMessageUtil;
import com.ext.eai.common.stdmessage.STDMessageKeys;
class InterfaceMapperDJBTest {
private InterfaceMapperDJB mapper;
private StandardMessage message;
@BeforeEach
void setUp() throws Exception {
// CSV에서 표준전문 레이아웃 로드
message = StandardMessageUtil.generateMessageFromCsvFile(
"src/main/resources/standard-layout-djb.csv");
assertNotNull(message, "standard-layout-djb.csv 로드 실패");
message.setReadPosition(0);
// 매핑 설정 로드
Properties props = new Properties();
try (java.io.InputStream in = getClass().getClassLoader()
.getResourceAsStream("standard-message-mapping-config-djb.properties")) {
assertNotNull(in, "standard-message-mapping-config-djb.properties 로드 실패");
props.load(in);
}
HashMap<String, String> pathMap = new HashMap<>();
for (String key : props.stringPropertyNames()) {
pathMap.put(key.trim(), props.getProperty(key).trim());
}
mapper = new InterfaceMapperDJB();
mapper.initPathMap(pathMap);
}
// ----------------------------------------------------------------
// GUID
// ----------------------------------------------------------------
@Test
void setGuid_40자_정상_설정() {
String guid = GUIDGeneratorDJB.getGUID("EAI");
mapper.setGuid(message, guid);
assertEquals(guid, mapper.getGuid(message));
}
@Test
void setGuid_40자_아닌경우_예외발생() {
assertThrows(RuntimeException.class, () -> mapper.setGuid(message, "SHORT"));
}
@Test
void setGuid_null이면_예외발생() {
assertThrows(RuntimeException.class, () -> mapper.setGuid(message, null));
}
// ----------------------------------------------------------------
// GUID 진행번호 (guid_prgs_no - 별도 필드)
// ----------------------------------------------------------------
@Test
void guidSeq_초기값_및_nextGuidSeq_증가() {
// 초기값 설정
mapper.setGuidSeq(message, "001");
assertEquals("001", mapper.getGuidSeq(message));
// +1 증가
String next = mapper.nextGuidSeq(message);
assertEquals("002", next);
assertEquals("002", mapper.getGuidSeq(message));
}
@Test
void nextGuidSeq_3자리_zero_padding() {
mapper.setGuidSeq(message, "009");
assertEquals("010", mapper.nextGuidSeq(message));
mapper.setGuidSeq(message, "099");
assertEquals("100", mapper.nextGuidSeq(message));
}
// ----------------------------------------------------------------
// 원거래 GUID (ortr_guid)
// ----------------------------------------------------------------
@Test
void orgGuid_설정_조회() {
String guid = GUIDGeneratorDJB.getGUID("EAI");
mapper.setOrgGuid(message, guid);
assertEquals(guid, mapper.getOrgGuid(message));
}
// ----------------------------------------------------------------
// 처리결과구분코드 (S/F ↔ 엔진 내부 NR/ER)
// ----------------------------------------------------------------
@Test
void setResponseType_정상_NR_to_S() {
mapper.setResponseType(message, STDMessageKeys.RESPONSE_TYPE_CODE_N); // "NR"
assertEquals("S", message.findItemValue("HEAD.procs_rslt_dvcd"));
}
@Test
void setResponseType_오류_ER_to_F() {
mapper.setResponseType(message, STDMessageKeys.RESPONSE_TYPE_CODE_E); // "ER"
assertEquals("F", message.findItemValue("HEAD.procs_rslt_dvcd"));
}
@Test
void getResponseType_S_to_NR() {
message.setData("HEAD.procs_rslt_dvcd", "S");
assertEquals(STDMessageKeys.RESPONSE_TYPE_CODE_N, mapper.getResponseType(message));
}
@Test
void getResponseType_F_to_ER() {
message.setData("HEAD.procs_rslt_dvcd", "F");
assertEquals(STDMessageKeys.RESPONSE_TYPE_CODE_E, mapper.getResponseType(message));
}
// ----------------------------------------------------------------
// 원거래복원여부 (Y/N ↔ 엔진 내부 1/0)
// ----------------------------------------------------------------
@Test
void recoverYn_Y면_1반환() {
message.setData("HEAD.ortr_restr_yn", "Y");
assertEquals("1", mapper.getRecoverYn(message));
}
@Test
void recoverYn_N면_0반환() {
message.setData("HEAD.ortr_restr_yn", "N");
assertEquals("0", mapper.getRecoverYn(message));
}
@Test
void setRecoverYn_1이면_Y설정() {
mapper.setRecoverYn(message, "1");
assertEquals("Y", message.findItemValue("HEAD.ortr_restr_yn"));
}
@Test
void setRecoverYn_0이면_N설정() {
mapper.setRecoverYn(message, "0");
assertEquals("N", message.findItemValue("HEAD.ortr_restr_yn"));
}
// ----------------------------------------------------------------
// 인터페이스ID (trim)
// ----------------------------------------------------------------
@Test
void getInterfaceId_공백_trim() {
message.setData("HEAD.if_id", " IF001 ");
assertEquals("IF001", mapper.getInterfaceId(message));
}
@Test
void getInterfaceId_null이면_빈문자열() {
// 초기 공백상태
String ifId = mapper.getInterfaceId(message);
assertNotNull(ifId);
assertEquals("", ifId.trim());
}
// ----------------------------------------------------------------
// EAI 서비스코드 조합
// ----------------------------------------------------------------
@Test
void getEaiSvcCode_인터페이스ID_요청응답_내외구분_조합() {
message.setData("HEAD.if_id", "IF_TEST_001");
message.setData("HEAD.dman_rspn_dvcd", "S");
message.setData("HEAD.hmab_dvcd", "1");
String svcCode = mapper.getEaiSvcCode(message);
assertTrue(svcCode.startsWith("IF_TEST_001"), "EAI 서비스코드: " + svcCode);
assertTrue(svcCode.contains("S"), "요청응답구분 포함: " + svcCode);
}
// ----------------------------------------------------------------
// 필드 경로 기본 동작 확인 (매핑 설정 유효성)
// ----------------------------------------------------------------
@Test
void sendRecvDivision_설정_조회() {
mapper.setSendRecvDivision(message, "R");
assertEquals("R", mapper.getSendRecvDivision(message));
}
@Test
void inExDivision_설정_조회() {
mapper.setInExDivision(message, "1");
assertEquals("1", mapper.getInExDivision(message));
}
@Test
void errorCode_설정_조회() {
mapper.setErrorCode(message, "ERR001");
assertEquals("ERR001", mapper.getErrorCode(message));
}
@Test
void errorMsg_설정_조회() {
mapper.setErrorMsg(message, "오류가 발생했습니다.");
assertEquals("오류가 발생했습니다.", mapper.getErrorMsg(message));
}
}
@@ -0,0 +1,163 @@
package com.eactive.eai.custom.message;
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import com.eactive.eai.message.StandardItem;
import com.eactive.eai.message.StandardMessage;
import com.eactive.eai.message.StandardMessageUtil;
/**
* standard-layout-djb.csv 로드 및 전문 구조 검증
*/
class StandardLayoutDJBTest {
private StandardMessage message;
@BeforeEach
void setUp() {
message = StandardMessageUtil.generateMessageFromCsvFile(
"src/main/resources/standard-layout-djb.csv");
assertNotNull(message, "standard-layout-djb.csv 로드 실패");
message.setReadPosition(0);
}
// ----------------------------------------------------------------
// 최상위 블록 존재 여부
// ----------------------------------------------------------------
@Test
void HEAD_블록_존재() {
assertNotNull(message.findItem("HEAD"), "HEAD 블록 없음");
}
@Test
void EZDATA_블록_존재() {
assertNotNull(message.findItem("EZDATA"), "EZDATA 블록 없음");
}
@Test
void MSG_블록_존재() {
assertNotNull(message.findItem("MSG"), "MSG 블록 없음");
}
@Test
void DATA_블록_존재() {
assertNotNull(message.findItem("DATA"), "DATA 블록 없음");
}
@Test
void 전문종료부_존재() {
assertNotNull(message.findItem("MESG_END_IDFR_VAL"), "전문종료부 없음");
}
// ----------------------------------------------------------------
// HEAD 핵심 필드 검증
// ----------------------------------------------------------------
@Test
void HEAD_guid_필드_존재_및_길이40() {
StandardItem guidItem = message.findItem("HEAD.guid");
assertNotNull(guidItem, "HEAD.guid 필드 없음");
assertEquals(40, guidItem.getLength(), "HEAD.guid 길이는 40자여야 함");
}
@Test
void HEAD_guid_prgs_no_필드_존재() {
StandardItem item = message.findItem("HEAD.guid_prgs_no");
assertNotNull(item, "HEAD.guid_prgs_no 필드 없음");
assertEquals(3, item.getLength());
}
@Test
void HEAD_nxgn_stnd_idfr_기본값_JERA() {
StandardItem item = message.findItem("HEAD.nxgn_stnd_idfr");
assertNotNull(item, "HEAD.nxgn_stnd_idfr 필드 없음");
assertEquals(4, item.getLength());
assertEquals("JERA", item.getValue());
}
@Test
void HEAD_ortr_guid_필드_존재_및_길이40() {
StandardItem item = message.findItem("HEAD.ortr_guid");
assertNotNull(item, "HEAD.ortr_guid 필드 없음");
assertEquals(40, item.getLength());
}
@Test
void HEAD_if_id_필드_길이30() {
StandardItem item = message.findItem("HEAD.if_id");
assertNotNull(item, "HEAD.if_id 필드 없음");
assertEquals(30, item.getLength(), "HEAD.if_id 길이는 30자여야 함 (기존 20자에서 변경)");
}
@Test
void HEAD_dman_rspn_dvcd_기본값_S() {
StandardItem item = message.findItem("HEAD.dman_rspn_dvcd");
assertNotNull(item, "HEAD.dman_rspn_dvcd 필드 없음");
assertEquals("S", item.getValue());
}
@Test
void HEAD_procs_rslt_dvcd_기본값_S() {
StandardItem item = message.findItem("HEAD.procs_rslt_dvcd");
assertNotNull(item, "HEAD.procs_rslt_dvcd 필드 없음");
assertEquals("S", item.getValue());
}
@Test
void HEAD_trnm_sys_dvcd_기본값_EAI() {
StandardItem item = message.findItem("HEAD.trnm_sys_dvcd");
assertNotNull(item, "HEAD.trnm_sys_dvcd 필드 없음");
assertEquals("EAI", item.getValue());
}
// ----------------------------------------------------------------
// MSG 영역 구조 검증
// ----------------------------------------------------------------
@Test
void MSG_MAIN_MSG_블록_존재() {
assertNotNull(message.findItem("MSG.MAIN_MSG"), "MSG.MAIN_MSG 블록 없음");
}
@Test
void MSG_outp_msg_cd_길이12() {
StandardItem item = message.findItem("MSG.MAIN_MSG.outp_msg_cd");
assertNotNull(item, "MSG.MAIN_MSG.outp_msg_cd 필드 없음");
assertEquals(12, item.getLength());
}
@Test
void MSG_msg_list_Grid_존재() {
assertNotNull(message.findItem("MSG.msg_list"), "MSG.msg_list Grid 없음");
}
// ----------------------------------------------------------------
// DATA 영역 구조 검증
// ----------------------------------------------------------------
@Test
void DATA_BIZ_DATA_존재() {
assertNotNull(message.findItem("DATA.BIZ_DATA"), "DATA.BIZ_DATA 없음");
}
// ----------------------------------------------------------------
// setData / findItemValue 기본 동작
// ----------------------------------------------------------------
@Test
void HEAD_필드_setData_findItemValue() {
String guid40 = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; // 40자
message.setData("HEAD.guid", guid40);
assertEquals(guid40, message.findItemValue("HEAD.guid"));
}
@Test
void MSG_필드_setData_findItemValue() {
message.setData("MSG.MAIN_MSG.outp_msg_ctnt", "테스트오류메시지");
assertEquals("테스트오류메시지", message.findItemValue("MSG.MAIN_MSG.outp_msg_ctnt"));
}
}