JSON 메시지 validation 기능 수정

- 오류발생위치 출력
- type 오류 validation test 추가
This commit is contained in:
curry772
2026-05-18 14:00:28 +09:00
parent 82c7c2bc8c
commit e0ea4a4f5d
3 changed files with 228 additions and 41 deletions
@@ -166,10 +166,12 @@ public class JSONMessage extends Message {
throw new InvalidAccessException("The number of repetitions is different from the number of repetitions of the actual data. , " + item.getName(), item.getItemPath()); throw new InvalidAccessException("The number of repetitions is different from the number of repetitions of the actual data. , " + item.getName(), item.getItemPath());
} }
} // validation } // validation
} catch(InvalidAccessException e) {
throw e;
} catch(Exception e) { } catch(Exception e) {
throw new InvalidAccessException("setValue error", getItemPath(), null, e); throw new InvalidAccessException("setValue error", getItemPath(), null, e);
} }
} }
protected void doSetValue(JsonNode node, HashMap validationTargets, String parentPath) { protected void doSetValue(JsonNode node, HashMap validationTargets, String parentPath) {
Item item = null; Item item = null;
@@ -255,40 +257,49 @@ public class JSONMessage extends Message {
} else if(value == null || value.length() == 0) { } else if(value == null || value.length() == 0) {
setObject( itemPath, value ); setObject( itemPath, value );
} else { } else {
int itemType = item.getDataTypeId(); int itemType = item.getDataTypeId();
switch(itemType) { try {
case Types.TYPE_INT : switch(itemType) {
case Types.TYPE_SHORT : case Types.TYPE_INT :
setInt( itemPath, new Integer(value) ); case Types.TYPE_SHORT :
break; setInt( itemPath, new Integer(value) );
case Types.TYPE_LONG : break;
setLong( itemPath, new Long(value) ); case Types.TYPE_LONG :
break; setLong( itemPath, new Long(value) );
case Types.TYPE_BIGDECIMAL : break;
if (SystemKeys.isValidationEnabled() && !isValid(value, item.getLength(), item.getDecimalPointLength())) { case Types.TYPE_BIGDECIMAL :
throw new InvalidAccessException( if (SystemKeys.isValidationEnabled() && !isValid(value, item.getLength(), item.getDecimalPointLength())) {
String.format("Invalid decimal point format Error value=%s,length=%d,pointLength=%d", throw new InvalidAccessException(
value, item.getLength(), item.getDecimalPointLength()), String.format("Invalid decimal point format Error value=%s,length=%d,pointLength=%d",
itemPath); value, item.getLength(), item.getDecimalPointLength()),
} itemPath);
setBigDecimal( itemPath, new BigDecimal(value) ); }
break; setBigDecimal( itemPath, new BigDecimal(value) );
case Types.TYPE_DOUBLE : break;
setDouble( itemPath, new Double(value) ); case Types.TYPE_DOUBLE :
break; setDouble( itemPath, new Double(value) );
case Types.TYPE_FLOAT : break;
setFloat( itemPath, new Float(value) ); case Types.TYPE_FLOAT :
break; setFloat( itemPath, new Float(value) );
case Types.TYPE_BOOLEAN : break;
setObject( itemPath, new Boolean(value) ); case Types.TYPE_BOOLEAN :
break; setObject( itemPath, new Boolean(value) );
case Types.TYPE_STRING : break;
setString(itemPath, value); case Types.TYPE_STRING :
break; setString(itemPath, value);
default : break;
setString(itemPath, value); default :
break; setString(itemPath, value);
} break;
}
} catch (InvalidAccessException e) {
throw e;
} catch (Exception e) {
throw new InvalidAccessException(
String.format("Type mismatch: value='%s', field='%s', dataType=%s",
value, itemPath, Types.getType(itemType)),
itemPath, null, e);
}
} }
} }
} }
@@ -28,11 +28,16 @@ import com.eactive.eai.transformer.message.MessageSetDataException;
* - LONG_VALUE : LONG, length=10, decimal=0 * - LONG_VALUE : LONG, length=10, decimal=0
* - BIG_VALUE : BIGDECIMAL, length=10, decimal=5 → 정수부 최대 4자리 * - BIG_VALUE : BIGDECIMAL, length=10, decimal=5 → 정수부 최대 4자리
* - BIG15_VALUE : BIGDECIMAL, length=15, decimal=0 → 정수부 최대 15자리 * - BIG15_VALUE : BIGDECIMAL, length=15, decimal=0 → 정수부 최대 15자리
* - STR_VALUE : STRING, length=20
* *
* 검증 시나리오 * 검증 시나리오
* 1) 타입 불일치 — 숫자형 필드에 비숫자 문자열 입력 시 setData() 에서 즉시 오류 * 1-a) 타입 불일치(오류) — 숫자형 필드에 비숫자 문자열 입력 시 setData() 에서 즉시 오류
* 2) 길이 초과 — BigDecimal 정수부/소수부 자릿수 초과 시 setData() 에서 즉시 오류 * 1-b) 타입 불일치(정상) — 숫자형 필드에 숫자 문자열 입력 시 파싱 가능하면 정상 처리
* (SystemKeys.TRANSFROMER_VALIDATION=true 필요) * 2-a) Java 타입 범위 초과 — INT/LONG 최대값 초과 시 NumberFormatException (validation 설정 무관)
* 2-b) BigDecimal 자릿수 초과 (숫자 리터럴) — 정수부/소수부 자릿수 초과 시 즉시 오류
* 2-c) BigDecimal 자릿수 초과 (문자열 형태) — JSON 문자열로 전달된 경우도 동일하게 검증
* (2-b, 2-c 모두 SystemKeys.TRANSFROMER_VALIDATION=true 필요)
* 3) 역방향 타입 — 문자열 필드에 JSON 숫자 입력 시 asText()로 자동 변환 → 오류 없음
*/ */
@DataJpaTest @DataJpaTest
@ContextConfiguration(classes = { TransformJPATestConfiguration.class }) @ContextConfiguration(classes = { TransformJPATestConfiguration.class })
@@ -110,8 +115,82 @@ class JSONMessageValidationTest {
} }
// ───────────────────────────────────────────────────────────────── // ─────────────────────────────────────────────────────────────────
// 2. 숫자형 길이 초과 테스트 — setData() 시점에 오류 발생 // 1-b. 숫자형 필드에 숫자 문자열 입력 → 정상 처리
// transformer.validation=true 설정 필요 // JSON 문자열 노드라도 파싱 가능한 숫자면 오류 없이 처리됨
// ─────────────────────────────────────────────────────────────────
@Test
@DisplayName("INT 타입 필드에 숫자 문자열 입력 → 오류 없이 정상 처리")
void testIntFieldWithNumericString() throws Exception {
String data = "{\"EAI_UJSONTYPE_REQ1\":{\"INT_VALUE\":\"42\"}}";
Message msg = MessageFactory.getFactory().getMessage("EAI_UJSONTYPE_REQ1");
msg.setData(data);
String result = (String) msg.getData();
assertNotNull(result, "결과값이 null이 아니어야 합니다");
assertTrue(result.contains("\"INT_VALUE\":42"), "INT_VALUE가 숫자 42로 출력되어야 합니다");
}
@Test
@DisplayName("LONG 타입 필드에 숫자 문자열 입력 → 오류 없이 정상 처리")
void testLongFieldWithNumericString() throws Exception {
String data = "{\"EAI_UJSONTYPE_REQ1\":{\"LONG_VALUE\":\"9876543210\"}}";
Message msg = MessageFactory.getFactory().getMessage("EAI_UJSONTYPE_REQ1");
msg.setData(data);
String result = (String) msg.getData();
assertNotNull(result, "결과값이 null이 아니어야 합니다");
assertTrue(result.contains("\"LONG_VALUE\":9876543210"), "LONG_VALUE가 숫자 9876543210으로 출력되어야 합니다");
}
@Test
@DisplayName("BIGDECIMAL 타입 필드에 숫자 문자열 입력 → 오류 없이 정상 처리")
void testBigDecimalFieldWithNumericString() throws Exception {
String data = "{\"EAI_UJSONTYPE_REQ1\":{\"BIG_VALUE\":\"1234.12345\"}}";
Message msg = MessageFactory.getFactory().getMessage("EAI_UJSONTYPE_REQ1");
msg.setData(data);
String result = (String) msg.getData();
assertNotNull(result, "결과값이 null이 아니어야 합니다");
assertTrue(result.contains("\"BIG_VALUE\":1234.12345"), "BIG_VALUE가 숫자 1234.12345로 출력되어야 합니다");
}
// ─────────────────────────────────────────────────────────────────
// 2-a. INT/LONG Java 타입 범위 초과 — validation 설정 무관하게 NumberFormatException
// ─────────────────────────────────────────────────────────────────
@Test
@DisplayName("INT 타입 필드에 Integer 최대값 초과 입력 → NumberFormatException")
void testIntFieldJavaTypeOverflow() {
// Integer.MAX_VALUE = 2147483647, 초과값 → new Integer() 파싱 실패
String data = "{\"EAI_UJSONTYPE_REQ1\":{\"INT_VALUE\":\"2147483648\"}}";
Message msg = MessageFactory.getFactory().getMessage("EAI_UJSONTYPE_REQ1");
MessageSetDataException ex = assertThrows(
MessageSetDataException.class,
() -> msg.setData(data)
);
assertTrue(containsCause(ex, NumberFormatException.class),
"원인이 NumberFormatException이어야 합니다");
}
@Test
@DisplayName("LONG 타입 필드에 Long 최대값 초과 입력 → NumberFormatException")
void testLongFieldJavaTypeOverflow() {
// Long.MAX_VALUE = 9223372036854775807, 초과값 → new Long() 파싱 실패
String data = "{\"EAI_UJSONTYPE_REQ1\":{\"LONG_VALUE\":\"9223372036854775808\"}}";
Message msg = MessageFactory.getFactory().getMessage("EAI_UJSONTYPE_REQ1");
MessageSetDataException ex = assertThrows(
MessageSetDataException.class,
() -> msg.setData(data)
);
assertTrue(containsCause(ex, NumberFormatException.class),
"원인이 NumberFormatException이어야 합니다");
}
// ─────────────────────────────────────────────────────────────────
// 2-b. BigDecimal 자릿수 초과 (JSON 숫자 리터럴) — transformer.validation=true 필요
// ───────────────────────────────────────────────────────────────── // ─────────────────────────────────────────────────────────────────
@Test @Test
@@ -180,6 +259,102 @@ class JSONMessageValidationTest {
assertTrue(result.contains("BIG_VALUE"), "BIG_VALUE가 결과에 포함되어야 합니다"); assertTrue(result.contains("BIG_VALUE"), "BIG_VALUE가 결과에 포함되어야 합니다");
} }
// ─────────────────────────────────────────────────────────────────
// 2-c. BigDecimal 자릿수 초과 (JSON 문자열 형태) — transformer.validation=true 필요
// ─────────────────────────────────────────────────────────────────
@Test
@DisplayName("BIG_VALUE 정수부 초과 문자열 입력(5자리 > 4자리 제한) → MessageSetDataException")
void testBigDecimalIntegerPartOverflowFromString() {
enableValidation();
// JSON 문자열 "12345.12345" → 정수부 5자리 초과
String data = "{\"EAI_UJSONTYPE_REQ1\":{\"BIG_VALUE\":\"12345.12345\"}}";
Message msg = MessageFactory.getFactory().getMessage("EAI_UJSONTYPE_REQ1");
MessageSetDataException ex = assertThrows(
MessageSetDataException.class,
() -> msg.setData(data)
);
assertTrue(containsMessage(ex, "Invalid decimal point format"),
"'Invalid decimal point format' 메시지가 포함되어야 합니다");
}
@Test
@DisplayName("BIG_VALUE 소수부 초과 문자열 입력(6자리 > 5자리 제한) → MessageSetDataException")
void testBigDecimalFractionalPartOverflowFromString() {
enableValidation();
// JSON 문자열 "1234.123456" → 소수부 6자리 초과
String data = "{\"EAI_UJSONTYPE_REQ1\":{\"BIG_VALUE\":\"1234.123456\"}}";
Message msg = MessageFactory.getFactory().getMessage("EAI_UJSONTYPE_REQ1");
MessageSetDataException ex = assertThrows(
MessageSetDataException.class,
() -> msg.setData(data)
);
assertTrue(containsMessage(ex, "Invalid decimal point format"),
"'Invalid decimal point format' 메시지가 포함되어야 합니다");
}
@Test
@DisplayName("BIG15_VALUE 정수부 초과 문자열 입력(16자리 > 15자리 제한) → MessageSetDataException")
void testBigDecimalIntegerOnlyOverflowFromString() {
enableValidation();
// JSON 문자열 "1234567890123456" → 정수부 16자리 초과
String data = "{\"EAI_UJSONTYPE_REQ1\":{\"BIG15_VALUE\":\"1234567890123456\"}}";
Message msg = MessageFactory.getFactory().getMessage("EAI_UJSONTYPE_REQ1");
MessageSetDataException ex = assertThrows(
MessageSetDataException.class,
() -> msg.setData(data)
);
assertTrue(containsMessage(ex, "Invalid decimal point format"),
"'Invalid decimal point format' 메시지가 포함되어야 합니다");
}
// ─────────────────────────────────────────────────────────────────
// 3. 역방향 타입 테스트 — 문자열 필드에 숫자 입력 → 정상 처리
// JSON 숫자 노드는 node.asText()로 문자열 변환되어 오류 없이 처리됨
// ─────────────────────────────────────────────────────────────────
@Test
@DisplayName("STRING 타입 필드에 JSON 정수 입력 → 오류 없이 정상 처리")
void testStringFieldWithIntegerValue() throws Exception {
String data = "{\"EAI_UJSONTYPE_REQ1\":{\"STR_VALUE\":123}}";
Message msg = MessageFactory.getFactory().getMessage("EAI_UJSONTYPE_REQ1");
msg.setData(data);
String result = (String) msg.getData();
assertNotNull(result, "결과값이 null이 아니어야 합니다");
assertTrue(result.contains("STR_VALUE"), "STR_VALUE가 결과에 포함되어야 합니다");
}
@Test
@DisplayName("STRING 타입 필드에 JSON 실수 입력 → 오류 없이 정상 처리")
void testStringFieldWithDecimalValue() throws Exception {
String data = "{\"EAI_UJSONTYPE_REQ1\":{\"STR_VALUE\":12.34}}";
Message msg = MessageFactory.getFactory().getMessage("EAI_UJSONTYPE_REQ1");
msg.setData(data);
String result = (String) msg.getData();
assertNotNull(result, "결과값이 null이 아니어야 합니다");
assertTrue(result.contains("STR_VALUE"), "STR_VALUE가 결과에 포함되어야 합니다");
}
@Test
@DisplayName("STRING 타입 필드에 JSON 큰 정수 입력 → 오류 없이 정상 처리")
void testStringFieldWithLargeIntegerValue() throws Exception {
String data = "{\"EAI_UJSONTYPE_REQ1\":{\"STR_VALUE\":9876543210}}";
Message msg = MessageFactory.getFactory().getMessage("EAI_UJSONTYPE_REQ1");
msg.setData(data);
String result = (String) msg.getData();
assertNotNull(result, "결과값이 null이 아니어야 합니다");
assertTrue(result.contains("STR_VALUE"), "STR_VALUE가 결과에 포함되어야 합니다");
}
// ───────────────────────────────────────────────────────────────── // ─────────────────────────────────────────────────────────────────
// 유틸 메서드 // 유틸 메서드
// ───────────────────────────────────────────────────────────────── // ─────────────────────────────────────────────────────────────────
@@ -6,4 +6,5 @@ INSERT INTO TSEAITR08 (loutname,loutitemname,loutitemserno,loutitemidname,parntl
('EAI_UJSONTYPE_REQ1', 'INT_VALUE', 1, 9001, 9000, 'INT 타입 필드', 2203, 2102, NULL, 5, NULL, NULL, NULL, 1, 1, NULL, 'N', 2, 0, 'Integer', 0, NULL, NULL), ('EAI_UJSONTYPE_REQ1', 'INT_VALUE', 1, 9001, 9000, 'INT 타입 필드', 2203, 2102, NULL, 5, NULL, NULL, NULL, 1, 1, NULL, 'N', 2, 0, 'Integer', 0, NULL, NULL),
('EAI_UJSONTYPE_REQ1', 'LONG_VALUE', 2, 9002, 9000, 'LONG 타입 필드', 2203, 2103, NULL, 10, NULL, NULL, NULL, 1, 1, NULL, 'N', 2, 0, 'Long', 0, NULL, NULL), ('EAI_UJSONTYPE_REQ1', 'LONG_VALUE', 2, 9002, 9000, 'LONG 타입 필드', 2203, 2103, NULL, 10, NULL, NULL, NULL, 1, 1, NULL, 'N', 2, 0, 'Long', 0, NULL, NULL),
('EAI_UJSONTYPE_REQ1', 'BIG_VALUE', 3, 9003, 9000, 'BIGDECIMAL length=10 decimal=5', 2203, 2118, NULL, 10, NULL, NULL, NULL, 1, 1, NULL, 'N', 2, 5, 'BigDecimal', 0, NULL, NULL), ('EAI_UJSONTYPE_REQ1', 'BIG_VALUE', 3, 9003, 9000, 'BIGDECIMAL length=10 decimal=5', 2203, 2118, NULL, 10, NULL, NULL, NULL, 1, 1, NULL, 'N', 2, 5, 'BigDecimal', 0, NULL, NULL),
('EAI_UJSONTYPE_REQ1', 'BIG15_VALUE', 4, 9004, 9000, 'BIGDECIMAL length=15 decimal=0', 2203, 2118, NULL, 15, NULL, NULL, NULL, 1, 1, NULL, 'N', 2, 0, 'BigDecimal', 0, NULL, NULL); ('EAI_UJSONTYPE_REQ1', 'BIG15_VALUE', 4, 9004, 9000, 'BIGDECIMAL length=15 decimal=0', 2203, 2118, NULL, 15, NULL, NULL, NULL, 1, 1, NULL, 'N', 2, 0, 'BigDecimal', 0, NULL, NULL),
('EAI_UJSONTYPE_REQ1', 'STR_VALUE', 5, 9005, 9000, 'STRING 타입 필드', 2203, 2101, NULL, 20, NULL, NULL, NULL, 1, 1, NULL, 'N', 2, 0, 'String', 0, NULL, NULL);