Merge branch 'feature/json-validation-test'
- JSONMessage BigDecimal 자릿수 검사를 setData() 시점으로 통일 - SystemKeys.isValidationEnabled() 하드코딩 버그 수정 - JSONMessageValidationTest 7개 테스트 추가 - JSON_VALIDATION_TEST_PLAN.md 작업 내역 문서화 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
# JSONMessage Validation 단위테스트 작업 내역
|
||||
|
||||
## 브랜치
|
||||
`feature/json-validation-test`
|
||||
|
||||
## 커밋
|
||||
`ae629d5` — JSONMessage validation 단위테스트 추가 및 SystemKeys 버그 수정
|
||||
`(최신)` — doSetValue() 자릿수 검사 setData() 시점으로 이동 및 테스트 수정
|
||||
|
||||
---
|
||||
|
||||
## 1. 목표
|
||||
|
||||
`JSONMessage`의 두 가지 validation 오류 시나리오를 단위테스트로 검증한다.
|
||||
|
||||
| 시나리오 | 발생 시점 | 결과 |
|
||||
|---|---|---|
|
||||
| 숫자형 필드에 타입 불일치 문자열 입력 | `setData()` | ✅ 정상 동작 확인 |
|
||||
| 숫자형 필드의 자릿수 초과 | `setData()` | ✅ `doSetValue()` 개선으로 setData() 시점으로 통일 |
|
||||
|
||||
---
|
||||
|
||||
## 2. 수정 파일
|
||||
|
||||
### 2-1. `SystemKeys.java` — 버그 수정
|
||||
|
||||
`isValidationEnabled()`가 `validationEnabled` 필드를 무시하고 `false`를 하드코딩으로 반환하는 버그.
|
||||
`reload()`는 `transformer.validation` 시스템 프로퍼티를 읽어 필드에 저장하지만 반환값에 반영되지 않았다.
|
||||
|
||||
```java
|
||||
// 수정 전
|
||||
public static boolean isValidationEnabled() { return false; }
|
||||
|
||||
// 수정 후
|
||||
public static boolean isValidationEnabled() { return validationEnabled; }
|
||||
```
|
||||
|
||||
### 2-2. `init_json_validation.sql` — 신규
|
||||
|
||||
`JSONMessageValidationTest` 전용 테스트 데이터. 공유 init 파일(`init_tseaitr07/08.sql`)을 수정하지 않기 위해 별도 파일로 분리.
|
||||
|
||||
- `tseaitr07`: `EAI_UJSONTYPE_REQ1` 레이아웃 헤더 (타입: JSON)
|
||||
- `tseaitr08`: 레이아웃 아이템 5건
|
||||
|
||||
| 필드명 | 타입코드 | length | decimal | 정수부 최대 |
|
||||
|---|---|---|---|---|
|
||||
| INT_VALUE | 2102 (INT) | 5 | 0 | — |
|
||||
| LONG_VALUE | 2103 (LONG) | 10 | 0 | — |
|
||||
| BIG_VALUE | 2118 (BIGDECIMAL) | 10 | 5 | 4자리 |
|
||||
| BIG15_VALUE | 2118 (BIGDECIMAL) | 15 | 0 | 15자리 |
|
||||
|
||||
> 정수부 최대 = `length - decimal - 1`
|
||||
|
||||
### 2-3. `JSONMessageValidationTest.java` — 신규
|
||||
|
||||
`@DataJpaTest` + H2 인메모리 DB 기반 JUnit 5 테스트. 테스트 7개 전체 통과.
|
||||
|
||||
기존 방식(`AllTests.setParams()` + `TransformEngine.getInstance()`)은 Spring 컨텍스트 없이 실행 시 NPE가 발생했다. `@DataJpaTest` + `@ContextConfiguration(TransformJPATestConfiguration.class)` + `@Autowired TransformEngine`으로 교체해 해결.
|
||||
|
||||
### 2-4. `JSONMessage.java` — `doSetValue()` 수정
|
||||
|
||||
`doSetValue()`의 `TYPE_BIGDECIMAL` case에 자릿수 검사 추가. 기존에는 자릿수 초과가 `getData()` 시점에 감지되어 타입 불일치와 발생 시점이 달랐다.
|
||||
|
||||
```java
|
||||
// 수정 전
|
||||
case Types.TYPE_BIGDECIMAL :
|
||||
setBigDecimal( itemPath, new BigDecimal(value) );
|
||||
break;
|
||||
|
||||
// 수정 후
|
||||
case Types.TYPE_BIGDECIMAL :
|
||||
if (SystemKeys.isValidationEnabled() && !isValid(value, item.getLength(), item.getDecimalPointLength())) {
|
||||
throw new InvalidAccessException(
|
||||
String.format("Invalid decimal point format Error value=%s,length=%d,pointLength=%d",
|
||||
value, item.getLength(), item.getDecimalPointLength()),
|
||||
itemPath);
|
||||
}
|
||||
setBigDecimal( itemPath, new BigDecimal(value) );
|
||||
break;
|
||||
```
|
||||
|
||||
```
|
||||
@DataJpaTest
|
||||
@ContextConfiguration(classes = { TransformJPATestConfiguration.class })
|
||||
@Sql({
|
||||
"/com/eactive/eai/transformer/init_tseaitr06.sql", // 레이아웃 타입 코드
|
||||
"/com/eactive/eai/transformer/init_json_validation.sql" // EAI_UJSONTYPE_REQ1 레이아웃
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 테스트 케이스
|
||||
|
||||
### 타입 불일치 — `setData()` 시점, validation 플래그 무관
|
||||
|
||||
| 테스트 메서드 | 입력 | 기대 결과 |
|
||||
|---|---|---|
|
||||
| `testIntFieldTypeError` | `"INT_VALUE":"abc"` | `MessageSetDataException` (cause: `NumberFormatException`) |
|
||||
| `testLongFieldTypeError` | `"LONG_VALUE":"not_a_number"` | `MessageSetDataException` (cause: `NumberFormatException`) |
|
||||
| `testBigDecimalFieldTypeError` | `"BIG_VALUE":"invalid_decimal"` | `MessageSetDataException` (cause: `NumberFormatException`) |
|
||||
|
||||
### 자릿수 초과 — `setData()` 시점, `transformer.validation=true` 필요
|
||||
|
||||
| 테스트 메서드 | 입력 | 기대 결과 |
|
||||
|---|---|---|
|
||||
| `testBigDecimalIntegerPartOverflow` | `"BIG_VALUE":12345.12345` (정수부 5자리) | `MessageSetDataException` ("Invalid decimal point format") |
|
||||
| `testBigDecimalFractionalPartOverflow` | `"BIG_VALUE":1234.123456` (소수부 6자리) | `MessageSetDataException` ("Invalid decimal point format") |
|
||||
| `testBigDecimalIntegerOnlyOverflow` | `"BIG15_VALUE":1234567890123456` (16자리) | `MessageSetDataException` ("Invalid decimal point format") |
|
||||
| `testBigDecimalWithinBounds` | `"BIG_VALUE":1234.12345` (정수부 4자리, 소수부 5자리) | 정상 처리, 결과 not null |
|
||||
|
||||
---
|
||||
|
||||
## 4. 예외 전파 경로
|
||||
|
||||
```
|
||||
[타입 불일치 — setData() 시점]
|
||||
setData(json)
|
||||
└─ doSetData()
|
||||
└─ doSetValue()
|
||||
└─ new Integer/Long/BigDecimal(value) ← NumberFormatException
|
||||
└─ setValue() wraps → InvalidAccessException
|
||||
└─ doSetData() wraps → MessageSetDataException
|
||||
|
||||
[자릿수 초과 — setData() 시점, validation=true]
|
||||
setData(json)
|
||||
└─ doSetData()
|
||||
└─ doSetValue()
|
||||
└─ isValid() false → throw InvalidAccessException
|
||||
└─ setValue() wraps → InvalidAccessException
|
||||
└─ doSetData() wraps → MessageSetDataException
|
||||
```
|
||||
@@ -86,7 +86,7 @@ public class SystemKeys {
|
||||
}
|
||||
|
||||
public static boolean isValidationEnabled() {
|
||||
return false;
|
||||
return validationEnabled;
|
||||
}
|
||||
|
||||
public static boolean isAddDecimalPointEnabled() {
|
||||
|
||||
@@ -265,6 +265,12 @@ public class JSONMessage extends Message {
|
||||
setLong( itemPath, new Long(value) );
|
||||
break;
|
||||
case Types.TYPE_BIGDECIMAL :
|
||||
if (SystemKeys.isValidationEnabled() && !isValid(value, item.getLength(), item.getDecimalPointLength())) {
|
||||
throw new InvalidAccessException(
|
||||
String.format("Invalid decimal point format Error value=%s,length=%d,pointLength=%d",
|
||||
value, item.getLength(), item.getDecimalPointLength()),
|
||||
itemPath);
|
||||
}
|
||||
setBigDecimal( itemPath, new BigDecimal(value) );
|
||||
break;
|
||||
case Types.TYPE_DOUBLE :
|
||||
|
||||
@@ -0,0 +1,209 @@
|
||||
package com.eactive.eai.transformer;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assumptions.assumeTrue;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.jdbc.Sql;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* JSONMessage 필드 validation 단위테스트
|
||||
*
|
||||
* 테스트 대상 레이아웃: EAI_UJSONTYPE_REQ1
|
||||
* - INT_VALUE : INT, length=5, decimal=0
|
||||
* - LONG_VALUE : LONG, length=10, decimal=0
|
||||
* - BIG_VALUE : BIGDECIMAL, length=10, decimal=5 → 정수부 최대 4자리
|
||||
* - BIG15_VALUE : BIGDECIMAL, length=15, decimal=0 → 정수부 최대 15자리
|
||||
*
|
||||
* 검증 시나리오
|
||||
* 1) 타입 불일치 — 숫자형 필드에 비숫자 문자열 입력 시 setData() 에서 즉시 오류
|
||||
* 2) 길이 초과 — BigDecimal 정수부/소수부 자릿수 초과 시 setData() 에서 즉시 오류
|
||||
* (SystemKeys.TRANSFROMER_VALIDATION=true 필요)
|
||||
*/
|
||||
@DataJpaTest
|
||||
@ContextConfiguration(classes = { TransformJPATestConfiguration.class })
|
||||
@Sql({
|
||||
"/com/eactive/eai/transformer/init_tseaitr06.sql",
|
||||
"/com/eactive/eai/transformer/init_json_validation.sql"
|
||||
})
|
||||
@DisplayName("JSONMessage Validation 테스트")
|
||||
class JSONMessageValidationTest {
|
||||
|
||||
@Autowired
|
||||
TransformEngine engine;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
try {
|
||||
if (!engine.isStarted()) {
|
||||
engine.start();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
assumeTrue(false, "TransformEngine 시작 실패: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
System.setProperty(SystemKeys.TRANSFROMER_VALIDATION, "false");
|
||||
SystemKeys.reload();
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
// 1. 타입 불일치 테스트 — setData() 시점에 오류 발생
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
@DisplayName("INT 타입 필드에 비숫자 문자열 입력 → NumberFormatException")
|
||||
void testIntFieldTypeError() {
|
||||
String data = "{\"EAI_UJSONTYPE_REQ1\":{\"INT_VALUE\":\"abc\"}}";
|
||||
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 타입 필드에 비숫자 문자열 입력 → NumberFormatException")
|
||||
void testLongFieldTypeError() {
|
||||
String data = "{\"EAI_UJSONTYPE_REQ1\":{\"LONG_VALUE\":\"not_a_number\"}}";
|
||||
Message msg = MessageFactory.getFactory().getMessage("EAI_UJSONTYPE_REQ1");
|
||||
|
||||
MessageSetDataException ex = assertThrows(
|
||||
MessageSetDataException.class,
|
||||
() -> msg.setData(data)
|
||||
);
|
||||
assertTrue(containsCause(ex, NumberFormatException.class),
|
||||
"원인이 NumberFormatException이어야 합니다");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("BIGDECIMAL 타입 필드에 비숫자 문자열 입력 → NumberFormatException")
|
||||
void testBigDecimalFieldTypeError() {
|
||||
String data = "{\"EAI_UJSONTYPE_REQ1\":{\"BIG_VALUE\":\"invalid_decimal\"}}";
|
||||
Message msg = MessageFactory.getFactory().getMessage("EAI_UJSONTYPE_REQ1");
|
||||
|
||||
MessageSetDataException ex = assertThrows(
|
||||
MessageSetDataException.class,
|
||||
() -> msg.setData(data)
|
||||
);
|
||||
assertTrue(containsCause(ex, NumberFormatException.class),
|
||||
"원인이 NumberFormatException이어야 합니다");
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
// 2. 숫자형 길이 초과 테스트 — setData() 시점에 오류 발생
|
||||
// transformer.validation=true 설정 필요
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
@DisplayName("BIG_VALUE 정수부 초과(5자리 > 4자리 제한) → MessageSetDataException")
|
||||
void testBigDecimalIntegerPartOverflow() {
|
||||
enableValidation();
|
||||
|
||||
// 정수부 5자리(12345) → 제한(4자리) 초과
|
||||
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 testBigDecimalFractionalPartOverflow() {
|
||||
enableValidation();
|
||||
|
||||
// 소수부 6자리(123456) → 제한(5자리) 초과
|
||||
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 testBigDecimalIntegerOnlyOverflow() {
|
||||
enableValidation();
|
||||
|
||||
// 정수부 16자리 → 제한(15자리) 초과
|
||||
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' 메시지가 포함되어야 합니다");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("BIG_VALUE 정상 범위(정수부 4자리 + 소수부 5자리) → 정상 처리")
|
||||
void testBigDecimalWithinBounds() throws Exception {
|
||||
enableValidation();
|
||||
|
||||
// 정수부 4자리 + 소수부 5자리 → 정상
|
||||
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"), "BIG_VALUE가 결과에 포함되어야 합니다");
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
// 유틸 메서드
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
|
||||
private void enableValidation() {
|
||||
System.setProperty(SystemKeys.TRANSFROMER_VALIDATION, "true");
|
||||
SystemKeys.reload();
|
||||
}
|
||||
|
||||
private boolean containsCause(Throwable e, Class<?> causeClass) {
|
||||
Throwable t = e;
|
||||
while (t != null) {
|
||||
if (causeClass.isInstance(t)) return true;
|
||||
t = t.getCause();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean containsMessage(Throwable e, String keyword) {
|
||||
Throwable t = e;
|
||||
while (t != null) {
|
||||
if (t.getMessage() != null && t.getMessage().contains(keyword)) return true;
|
||||
t = t.getCause();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
INSERT INTO tseaitr07 (loutname,loutptrnname,loutdesc,lastamndhms,eaibzwkdstcd,uapplname,sysintfacname,eaisevrdstcd,modfimgtstusdstcd,useyn,verinfo,author) VALUES
|
||||
('EAI_UJSONTYPE_REQ1', 'JSON', 'JSON 타입 검증 테스트', '2026042210000000', 'TST', ' ', ' ', 'E', ' ', '1', '00001', 'admin');
|
||||
|
||||
INSERT INTO TSEAITR08 (loutname,loutitemname,loutitemserno,loutitemidname,parntloutitemidname,loutitemdesc,loutitemnodeptrnidname,loutitemptrnidname,loutitempathname,loutitemlencnt,loutitemrefinfo,loutitemrefinfo2,loutitemoccurptrndstcd,loutitemmaxoccurnoitm,loutitemminoccurnoitm,loutitembascval,loutitemmskyn,loutdptcnt,decptlencnt,loutitemrefptrnidname,loutitemmasklength,multilang,loutitemmaskoffset) VALUES
|
||||
('EAI_UJSONTYPE_REQ1', 'EAI_UJSONTYPE_REQ1', 0, 9000, 0, 'JSON 타입 검증 테스트', 2201, 2100, NULL, 0, NULL, NULL, NULL, 0, 0, NULL, NULL, 1, 0, NULL, NULL, 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', '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);
|
||||
Reference in New Issue
Block a user