Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a1d822c2d7 | |||
| 05e887f7f1 | |||
| 4653d6a50b |
@@ -36,6 +36,8 @@ public class Parser implements Serializable {
|
||||
getJep().setAllowAssignment(true);
|
||||
getJep().setAllowUndeclared(true);
|
||||
|
||||
addBooleanConstants();
|
||||
|
||||
try {
|
||||
addGenericFunctions();
|
||||
} catch (Exception e) {
|
||||
@@ -89,6 +91,13 @@ public class Parser implements Serializable {
|
||||
|
||||
public void initSymbolTable() {
|
||||
getJep().initSymTab();
|
||||
addBooleanConstants();
|
||||
}
|
||||
|
||||
/** JEP 기본 상수에 없는 true/false 를 Boolean 타입으로 등록한다. */
|
||||
private void addBooleanConstants() {
|
||||
getJep().addVariable("true", Boolean.TRUE);
|
||||
getJep().addVariable("false", Boolean.FALSE);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@@ -144,8 +153,9 @@ public class Parser implements Serializable {
|
||||
private void addFunction(String name, PostfixMathCommand command) {
|
||||
if(name == null || command == null) {
|
||||
logger.warn("addFunction ("+name+","+command+") skip null");
|
||||
return;
|
||||
return;
|
||||
}
|
||||
getJep().addFunction(name, command); // 원본 대소문자 (camelCase 표현식 지원)
|
||||
getJep().addFunction(name.toUpperCase(), command);
|
||||
getJep().addFunction(name.toLowerCase(), command);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
package com.eactive.eai.transformer.function.generic;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Stack;
|
||||
|
||||
import org.nfunk.jep.ParseException;
|
||||
import org.nfunk.jep.function.PostfixMathCommand;
|
||||
|
||||
/**
|
||||
* JEP 커스텀 함수 - 날짜/시간 형식 변환
|
||||
*
|
||||
* <p>사용법: {@code convertDateTime(source, fromFormat, toFormat)}
|
||||
* <ul>
|
||||
* <li>{@code source} - 변환할 날짜 문자열 (예: "20240508")</li>
|
||||
* <li>{@code fromFormat} - 입력 형식 패턴 (예: "yyyyMMdd")</li>
|
||||
* <li>{@code toFormat} - 출력 형식 패턴 (예: "yyyy-MM-dd")</li>
|
||||
* </ul>
|
||||
* 반환값: toFormat으로 재포맷된 날짜 문자열
|
||||
*/
|
||||
public class ConvertDateTime extends PostfixMathCommand {
|
||||
public ConvertDateTime() {
|
||||
numberOfParameters = 3;
|
||||
}
|
||||
|
||||
/**
|
||||
* 스택에서 (source, fromFormat, toFormat) 순으로 꺼내 날짜 형식을 변환한다.
|
||||
*
|
||||
* @param inStack JEP 후위 연산 스택
|
||||
* @throws ParseException 파라미터 타입 오류, 날짜 파싱 실패, 잘못된 형식 패턴
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public void run(Stack inStack) throws ParseException {
|
||||
checkStack(inStack);
|
||||
|
||||
Object p3 = inStack.pop();
|
||||
Object p2 = inStack.pop();
|
||||
Object p1 = inStack.pop();
|
||||
|
||||
if (!(p1 instanceof String)) {
|
||||
throw new ParseException("convertDateTime(): source must be a string");
|
||||
}
|
||||
if (!(p2 instanceof String)) {
|
||||
throw new ParseException("convertDateTime(): fromFormat must be a string");
|
||||
}
|
||||
if (!(p3 instanceof String)) {
|
||||
throw new ParseException("convertDateTime(): toFormat must be a string");
|
||||
}
|
||||
|
||||
String source = (String) p1;
|
||||
String fromFormat = (String) p2;
|
||||
String toFormat = (String) p3;
|
||||
|
||||
try {
|
||||
SimpleDateFormat sdfFrom = new SimpleDateFormat(fromFormat);
|
||||
sdfFrom.setLenient(false);
|
||||
Date date = sdfFrom.parse(source);
|
||||
inStack.push(new SimpleDateFormat(toFormat).format(date));
|
||||
} catch (java.text.ParseException e) {
|
||||
throw new ParseException(
|
||||
"convertDateTime(): failed to parse '" + source + "' with format '" + fromFormat + "': " + e.getMessage());
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new ParseException(
|
||||
"convertDateTime(): invalid format pattern: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.eactive.eai.transformer.function.generic;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Stack;
|
||||
|
||||
import org.nfunk.jep.ParseException;
|
||||
import org.nfunk.jep.function.PostfixMathCommand;
|
||||
|
||||
/**
|
||||
* JEP 커스텀 함수 - 현재 일시 반환
|
||||
*
|
||||
* <p>사용법: {@code getCurrent(format)}
|
||||
* <ul>
|
||||
* <li>{@code format} - SimpleDateFormat 패턴 (예: "yyyyMMddHHmmss")</li>
|
||||
* </ul>
|
||||
* 반환값: 지정 형식으로 포맷된 현재 일시 문자열
|
||||
*/
|
||||
public class GetCurrent extends PostfixMathCommand {
|
||||
public GetCurrent() {
|
||||
numberOfParameters = 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 스택에서 format 패턴을 꺼내 현재 일시를 포맷하여 반환한다.
|
||||
*
|
||||
* @param inStack JEP 후위 연산 스택
|
||||
* @throws ParseException 파라미터 타입 오류, 잘못된 형식 패턴
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public void run(Stack inStack) throws ParseException {
|
||||
checkStack(inStack);
|
||||
|
||||
Object p1 = inStack.pop();
|
||||
|
||||
if (!(p1 instanceof String)) {
|
||||
throw new ParseException("getCurrent(): format must be a string");
|
||||
}
|
||||
|
||||
try {
|
||||
inStack.push(new SimpleDateFormat((String) p1).format(new Date()));
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new ParseException("getCurrent(): invalid date format '" + p1 + "': " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package com.eactive.eai.transformer.function.generic;
|
||||
|
||||
import java.util.Stack;
|
||||
|
||||
import org.nfunk.jep.ParseException;
|
||||
import org.nfunk.jep.function.PostfixMathCommand;
|
||||
|
||||
/**
|
||||
* JEP 커스텀 함수 - 삼항 조건 평가
|
||||
*
|
||||
* <p>사용법: {@code getTrinomial(target, compare, operator, trueValue, falseValue)}
|
||||
* <ul>
|
||||
* <li>{@code target} - 비교 대상 값</li>
|
||||
* <li>{@code compare} - 비교 기준 값</li>
|
||||
* <li>{@code operator} - 비교 연산자 문자열 (=, <, >, <=, >=)</li>
|
||||
* <li>{@code trueValue} - 조건이 참일 때 반환할 값</li>
|
||||
* <li>{@code falseValue} - 조건이 거짓일 때 반환할 값</li>
|
||||
* </ul>
|
||||
* 타입별 비교: Number는 수치 비교, Boolean은 '=' 만 지원, 그 외는 문자열 사전순 비교
|
||||
*/
|
||||
public class GetTrinomial extends PostfixMathCommand {
|
||||
public GetTrinomial() {
|
||||
numberOfParameters = 5;
|
||||
}
|
||||
|
||||
/**
|
||||
* 스택에서 (target, compare, operator, trueValue, falseValue) 순으로 꺼내 조건을 평가한다.
|
||||
*
|
||||
* @param inStack JEP 후위 연산 스택
|
||||
* @throws ParseException operator 타입 오류, 지원하지 않는 연산자, boolean에 '=' 외 연산자 사용
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public void run(Stack inStack) throws ParseException {
|
||||
checkStack(inStack);
|
||||
|
||||
Object falseValue = inStack.pop();
|
||||
Object trueValue = inStack.pop();
|
||||
Object operator = inStack.pop();
|
||||
Object compare = inStack.pop();
|
||||
Object target = inStack.pop();
|
||||
|
||||
if (!(operator instanceof String)) {
|
||||
throw new ParseException("getTrinomial(): operator must be a string (=, <, >, <=, >=)");
|
||||
}
|
||||
|
||||
boolean result = compare(target, compare, ((String) operator).trim());
|
||||
inStack.push(result ? trueValue : falseValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* target과 compare를 op 연산자로 비교한다.
|
||||
* Number → 수치 비교, Boolean → 동등 비교, 그 외 → 문자열 사전순 비교
|
||||
*/
|
||||
private boolean compare(Object target, Object compare, String op) throws ParseException {
|
||||
if (target instanceof Number && compare instanceof Number) {
|
||||
double t = ((Number) target).doubleValue();
|
||||
double c = ((Number) compare).doubleValue();
|
||||
switch (op) {
|
||||
case "=": return t == c;
|
||||
case "<": return t < c;
|
||||
case ">": return t > c;
|
||||
case "<=": return t <= c;
|
||||
case ">=": return t >= c;
|
||||
default: throw new ParseException("getTrinomial(): unsupported operator: " + op);
|
||||
}
|
||||
} else if (target instanceof Boolean && compare instanceof Boolean) {
|
||||
if (!"=".equals(op)) {
|
||||
throw new ParseException("getTrinomial(): boolean comparison only supports '=' operator");
|
||||
}
|
||||
return target.equals(compare);
|
||||
} else {
|
||||
String t = target != null ? target.toString() : "";
|
||||
String c = compare != null ? compare.toString() : "";
|
||||
int cmp = t.compareTo(c);
|
||||
switch (op) {
|
||||
case "=": return cmp == 0;
|
||||
case "<": return cmp < 0;
|
||||
case ">": return cmp > 0;
|
||||
case "<=": return cmp <= 0;
|
||||
case ">=": return cmp >= 0;
|
||||
default: throw new ParseException("getTrinomial(): unsupported operator: " + op);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.eactive.eai.transformer.function.generic;
|
||||
|
||||
import java.util.Stack;
|
||||
|
||||
import org.nfunk.jep.ParseException;
|
||||
import org.nfunk.jep.function.PostfixMathCommand;
|
||||
|
||||
/**
|
||||
* JEP 커스텀 함수 - 문자열 전체 치환
|
||||
*
|
||||
* <p>사용법: {@code replaceAll(source, from, to)}
|
||||
* <ul>
|
||||
* <li>{@code source} - 원본 문자열</li>
|
||||
* <li>{@code from} - 치환 대상 문자열 (정규식 아님, 리터럴 매칭)</li>
|
||||
* <li>{@code to} - 치환 결과 문자열</li>
|
||||
* </ul>
|
||||
* 반환값: source 내 from과 일치하는 모든 부분을 to로 치환한 문자열
|
||||
*
|
||||
* <p>주의: JEP 내장 함수 {@code str()}와 충돌하므로, 변수명으로 {@code str}을 사용하지 말 것.
|
||||
*/
|
||||
public class ReplaceAll extends PostfixMathCommand {
|
||||
public ReplaceAll() {
|
||||
numberOfParameters = 3;
|
||||
}
|
||||
|
||||
/**
|
||||
* 스택에서 (source, from, to) 순으로 꺼내 전체 치환 후 결과를 반환한다.
|
||||
*
|
||||
* @param inStack JEP 후위 연산 스택
|
||||
* @throws ParseException 파라미터가 String 타입이 아닌 경우
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public void run(Stack inStack) throws ParseException {
|
||||
checkStack(inStack);
|
||||
|
||||
Object p3 = inStack.pop();
|
||||
Object p2 = inStack.pop();
|
||||
Object p1 = inStack.pop();
|
||||
|
||||
if (!(p1 instanceof String)) {
|
||||
throw new ParseException("replaceAll(): first parameter (source string) must be a string");
|
||||
}
|
||||
if (!(p2 instanceof String)) {
|
||||
throw new ParseException("replaceAll(): second parameter (from string) must be a string");
|
||||
}
|
||||
if (!(p3 instanceof String)) {
|
||||
throw new ParseException("replaceAll(): third parameter (to string) must be a string");
|
||||
}
|
||||
|
||||
inStack.push(((String) p1).replace((String) p2, (String) p3));
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ package com.eactive.eai.transformer.message;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.Stack;
|
||||
@@ -250,6 +251,18 @@ public class BytesMessage extends Message {
|
||||
item.setValue(ByteUtil.cut(data, startPosition, item.getLength()));
|
||||
startPosition += item.getLength();
|
||||
remainSize -= item.getLength();
|
||||
if (SystemKeys.isValidationEnabled() && item.getDecimalPointLength() > 0) {
|
||||
String strVal = new String((byte[]) item.getValue(), defaultCharset).trim();
|
||||
try {
|
||||
new BigDecimal(strVal);
|
||||
} catch (NumberFormatException nfe) {
|
||||
throw new MessageException(
|
||||
String.format("decimal 필드 숫자형 오류: %s = [%s]",
|
||||
item.getItemPath(), strVal));
|
||||
}
|
||||
}
|
||||
} catch(MessageException me) {
|
||||
throw me;
|
||||
} catch(Exception e) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("전문레이아웃 매핑 중 데이터 사이즈 오류 ("+e.getMessage()+")");
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.eactive.eai.transformer;
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assumptions.assumeTrue;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
@@ -18,6 +19,7 @@ import com.eactive.eai.transformer.engine.TransformEngine;
|
||||
import com.eactive.eai.transformer.function.jpa.TransformJPATestConfiguration;
|
||||
import com.eactive.eai.transformer.message.Message;
|
||||
import com.eactive.eai.transformer.message.MessageFactory;
|
||||
import com.eactive.eai.transformer.message.MessageSetDataException;
|
||||
|
||||
/**
|
||||
* BytesMessage 필드 validation 영향도 단위테스트
|
||||
@@ -71,8 +73,8 @@ class BytesMessageValidationTest {
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
System.setProperty(SystemKeys.TRANSFROMER_VALIDATION, "false");
|
||||
System.setProperty(SystemKeys.TRANSFROMER_SCIENTIFIC_NORMALIZE, "false");
|
||||
System.clearProperty(SystemKeys.TRANSFROMER_VALIDATION);
|
||||
System.clearProperty(SystemKeys.TRANSFROMER_SCIENTIFIC_NORMALIZE);
|
||||
SystemKeys.reload();
|
||||
}
|
||||
|
||||
@@ -165,21 +167,73 @@ class BytesMessageValidationTest {
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
// B-4. setString() — 자릿수 초과 문자열 + validation=false
|
||||
// validation 설정과 무관하게 예외 없음 (B-3과 결과 동일)
|
||||
// B-4. setString() — 자릿수 초과 문자열
|
||||
// setString 경로는 setDataBytes()를 통하지 않으므로
|
||||
// validation 설정과 완전히 무관하게 예외 발생 안 함 (B-3과 결과 동일)
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
@DisplayName("[B-4] BIG_VALUE 자릿수 초과 + validation=false → 예외 없음")
|
||||
void testSetStringOverflowWithValidationDisabled() {
|
||||
// validation=false (기본값)
|
||||
@DisplayName("[B-4] BIG_VALUE 자릿수 초과 setString → setString 경로는 decimal 검증 대상 아님")
|
||||
void testSetStringOverflowIsNotSubjectToDecimalValidation() {
|
||||
// setString → setDataBytes() 미경유 → decimal 숫자형 검증 실행 안 됨
|
||||
// validation 설정과 무관하게 예외 없음
|
||||
byte[] raw = VALID_RAW.getBytes();
|
||||
Message msg = MessageFactory.getFactory().getMessage("EAI_UBYTESTYPE_REQ1");
|
||||
assertDoesNotThrow(() -> msg.setData(raw));
|
||||
|
||||
assertDoesNotThrow(
|
||||
() -> msg.setString("EAI_UBYTESTYPE_REQ1.BIG_VALUE[0]", "12345.123456"),
|
||||
"validation=false(기본값) 상태에서 TYPE_BYTEARRAY 필드는 자릿수 초과에 예외가 발생하지 않아야 합니다");
|
||||
"TYPE_BYTEARRAY setString은 setDataBytes()를 통하지 않으므로 자릿수 초과에도 예외가 발생하지 않아야 합니다");
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
// C. decimal > 0 필드 숫자 검증 — 파싱(setData) 시점
|
||||
// BytesMessage는 raw bytes를 위치 기반으로 잘라 저장하므로,
|
||||
// decimal > 0 인 숫자형 필드에 비숫자 bytes가 들어오는 경우를 검증
|
||||
//
|
||||
// INVALID_RAW: BIG_VALUE 위치(5~14)에 비숫자 "HELLO_WOLD" 삽입
|
||||
// 전문 구조 동일: INT_VALUE(5) + BIG_VALUE(10) + STR_VALUE(20) = 35 bytes
|
||||
//
|
||||
// C-1) 비숫자 bytes + validation=true → MessageSetDataException
|
||||
// C-2) 비숫자 bytes + validation=false → 예외 없음 (기존 동작 유지)
|
||||
// C-3) 정상 숫자 bytes + validation=true → 예외 없음 ([A] 테스트와 동일 경로)
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
|
||||
private static final String INVALID_RAW =
|
||||
"00042" // INT_VALUE : 5자리 정수 (decimal=0, 검증 대상 아님)
|
||||
+ "HELLO_WOLD" // BIG_VALUE : 비숫자 10자리 (decimal=5 → 숫자여야 함)
|
||||
+ "HELLO WORLD "; // STR_VALUE : 20자리 문자열 (decimal=0, 검증 대상 아님)
|
||||
|
||||
@Test
|
||||
@DisplayName("[C-1] decimal>0 필드에 비숫자 bytes + validation=true → MessageSetDataException")
|
||||
void testDecimalFieldNonNumeric_withValidation() {
|
||||
// validation 기본값이 true이므로 enableValidation() 호출 불필요
|
||||
Message msg = MessageFactory.getFactory().getMessage("EAI_UBYTESTYPE_REQ1");
|
||||
assertThrows(MessageSetDataException.class,
|
||||
() -> msg.setData(INVALID_RAW.getBytes()),
|
||||
"decimal>0 필드에 비숫자 bytes 입력 시 validation=true이면 MessageSetDataException이 발생해야 합니다");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("[C-2] decimal>0 필드에 비숫자 bytes + validation=false → 예외 없음")
|
||||
void testDecimalFieldNonNumeric_withoutValidation() {
|
||||
// 기본값이 true이므로 명시적으로 비활성화
|
||||
System.setProperty(SystemKeys.TRANSFROMER_VALIDATION, "false");
|
||||
SystemKeys.reload();
|
||||
Message msg = MessageFactory.getFactory().getMessage("EAI_UBYTESTYPE_REQ1");
|
||||
assertDoesNotThrow(
|
||||
() -> msg.setData(INVALID_RAW.getBytes()),
|
||||
"validation=false 상태에서는 비숫자 bytes도 예외가 발생하지 않아야 합니다");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("[C-3] decimal>0 필드에 정상 숫자 bytes + validation=true → 예외 없음")
|
||||
void testDecimalFieldNumeric_withValidation() {
|
||||
// validation 기본값이 true이므로 enableValidation() 호출 불필요
|
||||
Message msg = MessageFactory.getFactory().getMessage("EAI_UBYTESTYPE_REQ1");
|
||||
assertDoesNotThrow(
|
||||
() -> msg.setData(VALID_RAW.getBytes()),
|
||||
"decimal>0 필드에 정상 숫자 bytes 입력 시 validation=true에서도 예외가 발생하지 않아야 합니다");
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
package com.eactive.eai.transformer;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.nfunk.jep.JEP;
|
||||
|
||||
import com.eactive.eai.transformer.function.generic.ConvertDateTime;
|
||||
|
||||
@DisplayName("ConvertDateTime 날짜 변환 함수 테스트")
|
||||
class ConvertDateTimeFunctionTest {
|
||||
|
||||
private JEP jep;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
jep = new JEP();
|
||||
jep.addStandardFunctions();
|
||||
jep.addStandardConstants();
|
||||
jep.setAllowUndeclared(true);
|
||||
jep.addFunction("convertDateTime", new ConvertDateTime());
|
||||
}
|
||||
|
||||
private String eval(String expr) {
|
||||
jep.parseExpression(expr);
|
||||
assertFalse(jep.hasError(), "파싱 오류: " + jep.getErrorInfo());
|
||||
Object result = jep.getValueAsObject();
|
||||
assertFalse(jep.hasError(), "평가 오류: " + jep.getErrorInfo());
|
||||
return (String) result;
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("yyyyMMdd → yyyy-MM-dd 변환")
|
||||
void testCompactToHyphen() {
|
||||
jep.addVariable("src", "20240508");
|
||||
assertEquals("2024-05-08", eval("convertDateTime(src, \"yyyyMMdd\", \"yyyy-MM-dd\")"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("yyyyMMddHHmmss → yyyy-MM-dd'T'HH:mm:ss 변환 (ISO 8601)")
|
||||
void testToIso8601() {
|
||||
jep.addVariable("src", "20240508143022");
|
||||
assertEquals("2024-05-08T14:30:22",
|
||||
eval("convertDateTime(src, \"yyyyMMddHHmmss\", \"yyyy-MM-dd'T'HH:mm:ss\")"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("yyyy-MM-dd'T'HH:mm:ss → yyyyMMddHHmmss 변환")
|
||||
void testFromIso8601ToCompact() {
|
||||
jep.addVariable("src", "2024-05-08T14:30:22");
|
||||
assertEquals("20240508143022",
|
||||
eval("convertDateTime(src, \"yyyy-MM-dd'T'HH:mm:ss\", \"yyyyMMddHHmmss\")"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("yyyyMMddHHmmssSSS → yyyy-MM-dd HH:mm:ss.SSS 변환 (밀리초 포함)")
|
||||
void testWithMilliseconds() {
|
||||
jep.addVariable("src", "20240508143022123");
|
||||
assertEquals("2024-05-08 14:30:22.123",
|
||||
eval("convertDateTime(src, \"yyyyMMddHHmmssSSS\", \"yyyy-MM-dd HH:mm:ss.SSS\")"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("날짜 포맷만 변환 (시간 없음)")
|
||||
void testDateOnly() {
|
||||
jep.addVariable("src", "2024/05/08");
|
||||
assertEquals("20240508",
|
||||
eval("convertDateTime(src, \"yyyy/MM/dd\", \"yyyyMMdd\")"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("잘못된 날짜 → 오류 발생")
|
||||
void testInvalidDate() {
|
||||
jep.addVariable("src", "99999999");
|
||||
jep.parseExpression("convertDateTime(src, \"yyyyMMdd\", \"yyyy-MM-dd\")");
|
||||
if (!jep.hasError()) {
|
||||
jep.getValueAsObject();
|
||||
assertTrue(jep.hasError(), "잘못된 날짜는 오류가 발생해야 합니다");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package com.eactive.eai.transformer;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.nfunk.jep.JEP;
|
||||
|
||||
import com.eactive.eai.transformer.function.generic.GetCurrent;
|
||||
|
||||
@DisplayName("GetCurrent 현재시간 반환 함수 테스트")
|
||||
class GetCurrentFunctionTest {
|
||||
|
||||
private JEP jep;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
jep = new JEP();
|
||||
jep.addStandardFunctions();
|
||||
jep.addStandardConstants();
|
||||
jep.setAllowUndeclared(true);
|
||||
jep.addFunction("getCurrent", new GetCurrent());
|
||||
}
|
||||
|
||||
private String eval(String expr) {
|
||||
jep.parseExpression(expr);
|
||||
assertFalse(jep.hasError(), "파싱 오류: " + jep.getErrorInfo());
|
||||
Object result = jep.getValueAsObject();
|
||||
assertFalse(jep.hasError(), "평가 오류: " + jep.getErrorInfo());
|
||||
return (String) result;
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("yyyyMMdd 형식 — 오늘 날짜와 일치")
|
||||
void testDateFormat() {
|
||||
String expected = new SimpleDateFormat("yyyyMMdd").format(new Date());
|
||||
String result = eval("getCurrent(\"yyyyMMdd\")");
|
||||
assertEquals(expected, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("yyyy-MM-dd 형식 — 오늘 날짜와 일치")
|
||||
void testHyphenDateFormat() {
|
||||
String expected = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
|
||||
String result = eval("getCurrent(\"yyyy-MM-dd\")");
|
||||
assertEquals(expected, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("yyyyMMddHHmmss 형식 — 결과가 14자리")
|
||||
void testDateTimeFormat() {
|
||||
String result = eval("getCurrent(\"yyyyMMddHHmmss\")");
|
||||
assertNotNull(result);
|
||||
assertTrue(result.matches("\\d{14}"), "결과가 14자리 숫자여야 합니다: " + result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("yyyy-MM-dd'T'HH:mm:ss ISO 8601 형식")
|
||||
void testIso8601Format() {
|
||||
String result = eval("getCurrent(\"yyyy-MM-dd'T'HH:mm:ss\")");
|
||||
assertNotNull(result);
|
||||
assertTrue(result.matches("\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}"),
|
||||
"ISO 8601 형식이어야 합니다: " + result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("yyyyMMddHHmmssSSS 형식 — 결과가 17자리")
|
||||
void testDateTimeMillisFormat() {
|
||||
String result = eval("getCurrent(\"yyyyMMddHHmmssSSS\")");
|
||||
assertNotNull(result);
|
||||
assertTrue(result.matches("\\d{17}"), "결과가 17자리 숫자여야 합니다: " + result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("잘못된 포맷 → 오류 발생")
|
||||
void testInvalidFormat() {
|
||||
jep.parseExpression("getCurrent(\"invalid!!!###\")");
|
||||
if (!jep.hasError()) {
|
||||
jep.getValueAsObject();
|
||||
assertTrue(jep.hasError(), "잘못된 포맷은 오류가 발생해야 합니다");
|
||||
}
|
||||
}
|
||||
|
||||
private void assertEquals(String expected, String result) {
|
||||
org.junit.jupiter.api.Assertions.assertEquals(expected, result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
package com.eactive.eai.transformer;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.nfunk.jep.JEP;
|
||||
|
||||
import com.eactive.eai.transformer.function.generic.GetTrinomial;
|
||||
|
||||
@DisplayName("GetTrinomial 삼항연산자 함수 테스트")
|
||||
class GetTrinomialFunctionTest {
|
||||
|
||||
private JEP jep;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
jep = new JEP();
|
||||
jep.addStandardFunctions();
|
||||
jep.addStandardConstants();
|
||||
jep.setAllowUndeclared(true);
|
||||
|
||||
jep.addVariable("true", Boolean.TRUE);
|
||||
jep.addVariable("false", Boolean.FALSE);
|
||||
|
||||
jep.addFunction("getTrinomial", new GetTrinomial());
|
||||
}
|
||||
|
||||
private Object eval(String expr) {
|
||||
jep.parseExpression(expr);
|
||||
assertFalse(jep.hasError(), "파싱 오류: " + jep.getErrorInfo());
|
||||
Object result = jep.getValueAsObject();
|
||||
assertFalse(jep.hasError(), "평가 오류: " + jep.getErrorInfo());
|
||||
return result;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
// 숫자형 비교
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
@DisplayName("숫자 = 비교 — 참")
|
||||
void testNumberEqualTrue() {
|
||||
jep.addVariable("a", 10.0);
|
||||
assertEquals("같음", eval("getTrinomial(a, 10, \"=\", \"같음\", \"다름\")"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("숫자 = 비교 — 거짓")
|
||||
void testNumberEqualFalse() {
|
||||
jep.addVariable("a", 5.0);
|
||||
assertEquals("다름", eval("getTrinomial(a, 10, \"=\", \"같음\", \"다름\")"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("숫자 < 비교 — 참")
|
||||
void testNumberLessThanTrue() {
|
||||
jep.addVariable("score", 70.0);
|
||||
assertEquals("미달", eval("getTrinomial(score, 80, \"<\", \"미달\", \"통과\")"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("숫자 > 비교 — 참")
|
||||
void testNumberGreaterThanTrue() {
|
||||
jep.addVariable("score", 90.0);
|
||||
assertEquals("우수", eval("getTrinomial(score, 80, \">\", \"우수\", \"보통\")"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("숫자 <= 비교 — 경계값")
|
||||
void testNumberLessOrEqual() {
|
||||
jep.addVariable("v", 80.0);
|
||||
assertEquals("이하", eval("getTrinomial(v, 80, \"<=\", \"이하\", \"초과\")"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("숫자 >= 비교 — 경계값")
|
||||
void testNumberGreaterOrEqual() {
|
||||
jep.addVariable("v", 80.0);
|
||||
assertEquals("이상", eval("getTrinomial(v, 80, \">=\", \"이상\", \"미만\")"));
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
// 문자열 비교
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
@DisplayName("문자열 = 비교 — 참")
|
||||
void testStringEqualTrue() {
|
||||
jep.addVariable("status", "Y");
|
||||
assertEquals("활성", eval("getTrinomial(status, \"Y\", \"=\", \"활성\", \"비활성\")"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("문자열 = 비교 — 거짓")
|
||||
void testStringEqualFalse() {
|
||||
jep.addVariable("status", "N");
|
||||
assertEquals("비활성", eval("getTrinomial(status, \"Y\", \"=\", \"활성\", \"비활성\")"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("문자열 < 비교 — 사전순")
|
||||
void testStringLessThan() {
|
||||
jep.addVariable("s", "apple");
|
||||
assertEquals("앞", eval("getTrinomial(s, \"banana\", \"<\", \"앞\", \"뒤\")"));
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
// 결과값 타입 — 숫자 반환
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
@DisplayName("결과값이 숫자형")
|
||||
void testNumericReturnValue() {
|
||||
jep.addVariable("flag", "Y");
|
||||
Object result = eval("getTrinomial(flag, \"Y\", \"=\", 1, 0)");
|
||||
assertTrue(result instanceof Number);
|
||||
assertEquals(1.0, ((Number) result).doubleValue());
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
// boolean 비교
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
@DisplayName("boolean = 비교 — 참 (JEP는 boolean 리터럴 미지원, 변수로 바인딩)")
|
||||
void testBooleanEqual() {
|
||||
jep.addVariable("b", Boolean.TRUE);
|
||||
jep.addVariable("bTrue", Boolean.TRUE);
|
||||
assertEquals("참", eval("getTrinomial(b, bTrue, \"=\", \"참\", \"거짓\")"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("boolean = 비교 — 거짓")
|
||||
void testBooleanEqualFalse() {
|
||||
jep.addVariable("b", Boolean.FALSE);
|
||||
jep.addVariable("bTrue", Boolean.TRUE);
|
||||
assertEquals("거짓", eval("getTrinomial(b, bTrue, \"=\", \"참\", \"거짓\")"));
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
// 실전 Transformer 표현식 패턴
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
@DisplayName("실전 패턴 — true/false 결과값 (JEP 상수 1.0/0.0 반환 검증)")
|
||||
void testBooleanLiteralReturnValue() {
|
||||
// JEP addStandardConstants()가 true=1.0, false=0.0 으로 등록하는지 확인
|
||||
// → 등록되어 있다면 변수 바인딩 없이 숫자로 반환됨
|
||||
// → 등록 안 됐다면 파싱 오류 또는 undeclared 변수(NaN)로 처리됨
|
||||
jep.addVariable("intVal", 10.0);
|
||||
Object result = eval("getTrinomial(intVal, 10, \"=\", true, false)");
|
||||
assertTrue(result instanceof Number,
|
||||
"true/false 결과값이 Number 타입이어야 함 (실제: " + result.getClass().getSimpleName() + ")");
|
||||
assertEquals(1.0, ((Number) result).doubleValue(), 0.0,
|
||||
"조건 참일 때 true(=1.0) 반환 기대");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("실전 패턴 — dot 경로 변수(AGW_…SRCS.INT_VALUE) + boolean 결과값")
|
||||
void testRealWorldExpression() {
|
||||
// Transformer 실사용 표현식:
|
||||
// getTrinomial(AGW_TESTREQ10031S1_SRCS.INT_VALUE, 10, "=", true, false)
|
||||
//
|
||||
// JEP에서 dot(.)은 element-access 연산자로 파싱됨
|
||||
// → 파싱 성공 여부와 변수 바인딩 방법을 확인하기 위한 테스트
|
||||
String expr = "getTrinomial(AGW_TESTREQ10031S1_SRCS.INT_VALUE, 10, \"=\", true, false)";
|
||||
|
||||
// [1] 파싱만 먼저 검증
|
||||
jep.parseExpression(expr);
|
||||
assertFalse(jep.hasError(), "파싱 오류: " + jep.getErrorInfo());
|
||||
|
||||
// [2] 파싱 후 JEP 심볼 테이블에 등록된 변수명 확인
|
||||
// dot-expression 이므로 "AGW_TESTREQ10031S1_SRCS" 가 심볼로 잡힐 것으로 예상
|
||||
java.util.Set<String> symbols = jep.getSymbolTable().keySet();
|
||||
assertTrue(
|
||||
symbols.contains("AGW_TESTREQ10031S1_SRCS") || symbols.contains("AGW_TESTREQ10031S1_SRCS.INT_VALUE"),
|
||||
"심볼 테이블에 소스 변수가 포함되어야 함. 실제 심볼: " + symbols
|
||||
);
|
||||
|
||||
// [3] 심볼에 값 바인딩 후 평가
|
||||
// dot-expression(AGW_…SRCS.INT_VALUE)은 JEP vector element 접근이므로
|
||||
// 스칼라 바인딩 시 평가 오류 발생 가능 → 오류 메시지로 대응 방법 확인
|
||||
jep.addVariable("AGW_TESTREQ10031S1_SRCS", 10.0);
|
||||
|
||||
Object result = jep.getValueAsObject();
|
||||
if (jep.hasError()) {
|
||||
// dot element-access 실패 시: Transformer는 심볼 전체 경로를 변수명으로 등록해야 함
|
||||
// → Parser.addVariable("AGW_TESTREQ10031S1_SRCS.INT_VALUE", value) 방식 필요
|
||||
jep.addVariable("AGW_TESTREQ10031S1_SRCS.INT_VALUE", 10.0);
|
||||
jep.parseExpression(expr);
|
||||
assertFalse(jep.hasError(), "재파싱 오류: " + jep.getErrorInfo());
|
||||
result = jep.getValueAsObject();
|
||||
assertFalse(jep.hasError(), "평가 오류: " + jep.getErrorInfo());
|
||||
}
|
||||
|
||||
assertTrue(result instanceof Number,
|
||||
"결과값이 Number 타입이어야 함 (실제: " + (result == null ? "null" : result.getClass().getSimpleName()) + ")");
|
||||
assertEquals(1.0, ((Number) result).doubleValue(), 0.0,
|
||||
"INT_VALUE=10, 비교값=10, 연산자=등호 → 참(true=1.0) 반환 기대");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package com.eactive.eai.transformer;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.nfunk.jep.JEP;
|
||||
|
||||
import com.eactive.eai.transformer.function.generic.ReplaceAll;
|
||||
|
||||
@DisplayName("ReplaceAll 문자열 치환 함수 테스트")
|
||||
class ReplaceAllFunctionTest {
|
||||
|
||||
private JEP jep;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
jep = new JEP();
|
||||
jep.addStandardFunctions();
|
||||
jep.addStandardConstants();
|
||||
jep.setAllowUndeclared(true);
|
||||
jep.addFunction("replaceAll", new ReplaceAll());
|
||||
}
|
||||
|
||||
private String eval(String expr) {
|
||||
jep.parseExpression(expr);
|
||||
assertFalse(jep.hasError(), "파싱 오류: " + jep.getErrorInfo());
|
||||
Object result = jep.getValueAsObject();
|
||||
assertFalse(jep.hasError(), "평가 오류: " + jep.getErrorInfo());
|
||||
return (String) result;
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("단일 치환 — 홍길동 → 홍길순")
|
||||
void testSingleReplace() {
|
||||
jep.addVariable("name", "홍길동");
|
||||
assertEquals("홍길순", eval("replaceAll(name, \"동\", \"순\")"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("다중 치환 — 모든 매칭 문자열 변경")
|
||||
void testMultipleOccurrences() {
|
||||
// "aababab" 안의 'a' 4개가 모두 'X'로 치환 → "XXbXbXb"
|
||||
jep.addVariable("src", "aababab");
|
||||
assertEquals("XXbXbXb", eval("replaceAll(src, \"a\", \"X\")"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("치환 대상 없음 — 원본 그대로 반환")
|
||||
void testNoMatch() {
|
||||
jep.addVariable("src", "hello");
|
||||
assertEquals("hello", eval("replaceAll(src, \"z\", \"X\")"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("치환 후 문자열이 빈 문자열 (JEP는 \"\" 리터럴 미지원 → 변수 바인딩)")
|
||||
void testReplaceWithEmpty() {
|
||||
jep.addVariable("src", "abc123def");
|
||||
assertEquals("abcdef", eval("replaceAll(src, \"123\", \"\")"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("날짜 구분자 변환 — '-' → '/'")
|
||||
void testDateSeparator() {
|
||||
jep.addVariable("date", "2024-05-08");
|
||||
assertEquals("2024/05/08", eval("replaceAll(date, \"-\", \"/\")"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("리터럴만 사용")
|
||||
void testLiteralsOnly() {
|
||||
assertEquals("world world",
|
||||
eval("replaceAll(\"hello hello\", \"hello\", \"world\")"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("비문자열 파라미터 → 오류 발생")
|
||||
void testNonStringParamCausesError() {
|
||||
jep.parseExpression("replaceAll(123, \"1\", \"X\")");
|
||||
if (!jep.hasError()) {
|
||||
jep.getValueAsObject();
|
||||
assertTrue(jep.hasError(), "숫자 파라미터는 오류가 발생해야 합니다");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user