From eff7b31490a3efb29051e569a6dfc4fa65ce6342 Mon Sep 17 00:00:00 2001 From: curry772 Date: Mon, 1 Jun 2026 08:58:08 +0900 Subject: [PATCH] =?UTF-8?q?=EC=95=94=EB=B3=B5=ED=98=B8=ED=99=94=20?= =?UTF-8?q?=ED=95=A8=EC=88=98=20=EC=9D=B4=EB=A6=84=20=EB=B3=80=EA=B2=BD=20?= =?UTF-8?q?=EB=B0=8F=20=EB=8B=A8=EC=9C=84=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 클래스이름 오타 수정 - Parser 단위 테스트 추가 --- .../{DJBDecryt.java => DJBDecrypt.java} | 12 +- .../{DJBEncryt.java => DJBEncrypt.java} | 4 +- .../function/DJBCryptoFunctionParserTest.java | 242 ++++++++++++++++++ .../function/DJBCryptoFunctionTest.java | 8 +- 4 files changed, 254 insertions(+), 12 deletions(-) rename src/main/java/com/eactive/eai/custom/transformer/function/{DJBDecryt.java => DJBDecrypt.java} (76%) rename src/main/java/com/eactive/eai/custom/transformer/function/{DJBEncryt.java => DJBEncrypt.java} (95%) create mode 100644 src/test/java/com/eactive/eai/custom/transformer/function/DJBCryptoFunctionParserTest.java diff --git a/src/main/java/com/eactive/eai/custom/transformer/function/DJBDecryt.java b/src/main/java/com/eactive/eai/custom/transformer/function/DJBDecrypt.java similarity index 76% rename from src/main/java/com/eactive/eai/custom/transformer/function/DJBDecryt.java rename to src/main/java/com/eactive/eai/custom/transformer/function/DJBDecrypt.java index 2851cc7..b527ad1 100644 --- a/src/main/java/com/eactive/eai/custom/transformer/function/DJBDecryt.java +++ b/src/main/java/com/eactive/eai/custom/transformer/function/DJBDecrypt.java @@ -13,17 +13,17 @@ import com.eactive.eai.common.security.CryptoModuleService; /** * JEP 트랜스포머 함수 — DJB ERP 연동 복호화. * - * 사용법: DJBErpDecryt(encBase64, moduleName) DJBErpDecryt(encBase64, moduleName, - * groupSeq) + * 사용법: DJBErpDecryt(encBase64, moduleName) * - * 파라미터: encBase64 : Base64 인코딩된 암호문 (String) moduleName : DB에 등록된 암호화 모듈명 - * (String) groupSeq : 키 도출용 그룹 시퀀스 (String, 선택). 미전달 시 빈 context로 호출. + * 파라미터: + * encBase64 : Base64 인코딩된 암호문 + * moduleName : DB에 등록된 암호화 모듈명 * * 반환값: 복호화된 평문 (String) */ -public class DJBDecryt extends PostfixMathCommand { +public class DJBDecrypt extends PostfixMathCommand { - public DJBDecryt() { + public DJBDecrypt() { numberOfParameters = -1; } diff --git a/src/main/java/com/eactive/eai/custom/transformer/function/DJBEncryt.java b/src/main/java/com/eactive/eai/custom/transformer/function/DJBEncrypt.java similarity index 95% rename from src/main/java/com/eactive/eai/custom/transformer/function/DJBEncryt.java rename to src/main/java/com/eactive/eai/custom/transformer/function/DJBEncrypt.java index 39e484b..67afa9e 100644 --- a/src/main/java/com/eactive/eai/custom/transformer/function/DJBEncryt.java +++ b/src/main/java/com/eactive/eai/custom/transformer/function/DJBEncrypt.java @@ -21,9 +21,9 @@ import com.eactive.eai.common.security.CryptoModuleService; * * 반환값: Base64 인코딩된 암호문 (String) */ -public class DJBEncryt extends PostfixMathCommand { +public class DJBEncrypt extends PostfixMathCommand { - public DJBEncryt() { + public DJBEncrypt() { numberOfParameters = -1; } diff --git a/src/test/java/com/eactive/eai/custom/transformer/function/DJBCryptoFunctionParserTest.java b/src/test/java/com/eactive/eai/custom/transformer/function/DJBCryptoFunctionParserTest.java new file mode 100644 index 0000000..12879e5 --- /dev/null +++ b/src/test/java/com/eactive/eai/custom/transformer/function/DJBCryptoFunctionParserTest.java @@ -0,0 +1,242 @@ +package com.eactive.eai.custom.transformer.function; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +import java.nio.charset.StandardCharsets; +import java.util.Base64; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.context.support.GenericApplicationContext; + +import com.eactive.eai.common.security.CryptoModuleService; +import com.eactive.eai.common.util.ApplicationContextProvider; +import com.eactive.eai.transformer.engine.Parser; + +/** + * DJBEncrypt / DJBDecrypt — Parser 표현식 통합 테스트. + * + * DJBCryptoFunctionTest 는 Stack 직접 호출 방식으로 run() 을 검증하고, + * 이 클래스는 Parser 를 통해 JEP 수식으로 함수를 호출하는 통합 경로를 검증한다. + */ +@TestMethodOrder(MethodOrderer.DisplayName.class) +@DisplayName("DJBEncrypt / DJBDecrypt — Parser 변수/상수 혼합 테스트") +class DJBCryptoFunctionParserTest { + + private static final String MODULE = "AES_MODULE"; + private static final String PLAIN_TEXT = "hello-djb"; + private static final byte[] PLAIN_BYTES = PLAIN_TEXT.getBytes(StandardCharsets.UTF_8); + private static final byte[] CIPHER_BYTES = {0x01, 0x02, 0x03, 0x04}; + private static final String ENC_BASE64 = Base64.getEncoder().encodeToString(CIPHER_BYTES); + + private static GenericApplicationContext ctx; + private static CryptoModuleService mockService; + + private Parser parser; + + // ========================================================================= + // 클래스 초기화 — mock CryptoModuleService 를 Spring 컨텍스트에 등록 + // ========================================================================= + + @BeforeAll + static void setUpClass() throws Exception { + mockService = mock(CryptoModuleService.class); + + ctx = new GenericApplicationContext(); + ctx.getBeanFactory().registerSingleton("cryptoModuleService", mockService); + ctx.registerBeanDefinition("applicationContextProvider", + BeanDefinitionBuilder.genericBeanDefinition(ApplicationContextProvider.class) + .getBeanDefinition()); + ctx.refresh(); + } + + @AfterAll + static void tearDownClass() { + if (ctx != null) ctx.close(); + } + + @BeforeEach + void setUp() throws Exception { + reset(mockService); + when(mockService.encrypt(any(), anyMap(), isNull(), any(byte[].class))).thenReturn(CIPHER_BYTES); + when(mockService.decrypt(any(), anyMap(), isNull(), any(byte[].class))).thenReturn(PLAIN_BYTES); + + parser = new Parser(); + parser.getJep().addFunction("DJBEncrypt", new DJBEncrypt()); + parser.getJep().addFunction("DJBDecrypt", new DJBDecrypt()); + } + + // ========================================================================= + // 1. DJBEncrypt + // ========================================================================= + + @Test + @DisplayName("1-1. 암호화 — 평문 변수 + 상수 모듈명 → Base64 반환") + void encrypt_variablePlain_constantModule() throws Exception { + parser.parse("DJBEncrypt(plain, \"" + MODULE + "\")"); + parser.addVariable("plain", PLAIN_TEXT); + + assertEquals(ENC_BASE64, parser.getValueAsObject()); + } + + @Test + @DisplayName("1-2. 암호화 — 평문·모듈명 모두 변수") + void encrypt_bothVariables() throws Exception { + parser.parse("DJBEncrypt(plain, moduleName)"); + parser.addVariable("plain", PLAIN_TEXT); + parser.addVariable("moduleName", MODULE); + + assertEquals(ENC_BASE64, parser.getValueAsObject()); + } + + @Test + @DisplayName("1-3. 암호화 — parse 1회, 변수값 교체 반복 평가") + void encrypt_reuseParser_multipleEvaluations() throws Exception { + // identity: plainBytes 그대로 반환 → Base64 encoding 경로 검증에 집중 + when(mockService.encrypt(any(), anyMap(), isNull(), any(byte[].class))) + .thenAnswer(inv -> (byte[]) inv.getArgument(3)); + + parser.parse("DJBEncrypt(plain, \"" + MODULE + "\")"); + + parser.addVariable("plain", "first"); + String r1 = (String) parser.getValueAsObject(); + + parser.addVariable("plain", "second"); + String r2 = (String) parser.getValueAsObject(); + + assertEquals(Base64.getEncoder().encodeToString("first".getBytes(StandardCharsets.UTF_8)), r1); + assertEquals(Base64.getEncoder().encodeToString("second".getBytes(StandardCharsets.UTF_8)), r2); + } + + @Test + @DisplayName("1-4. 암호화 — 한글 평문이 UTF-8 인코딩되어 서비스에 전달된다") + void encrypt_koreanPlainText_utf8Encoded() throws Exception { + String korean = "한글평문"; + parser.parse("DJBEncrypt(plain, \"" + MODULE + "\")"); + parser.addVariable("plain", korean); + parser.getValueAsObject(); + + verify(mockService).encrypt(eq(MODULE), anyMap(), isNull(), + eq(korean.getBytes(StandardCharsets.UTF_8))); + } + + // ========================================================================= + // 2. DJBDecrypt + // ========================================================================= + + @Test + @DisplayName("2-1. 복호화 — Base64 변수 + 상수 모듈명 → 평문 반환") + void decrypt_variableEnc_constantModule() throws Exception { + parser.parse("DJBDecrypt(enc, \"" + MODULE + "\")"); + parser.addVariable("enc", ENC_BASE64); + + assertEquals(PLAIN_TEXT, parser.getValueAsObject()); + } + + @Test + @DisplayName("2-2. 복호화 — Base64·모듈명 모두 변수") + void decrypt_bothVariables() throws Exception { + parser.parse("DJBDecrypt(enc, moduleName)"); + parser.addVariable("enc", ENC_BASE64); + parser.addVariable("moduleName", MODULE); + + assertEquals(PLAIN_TEXT, parser.getValueAsObject()); + } + + @Test + @DisplayName("2-3. 복호화 — parse 1회, 변수값 교체 반복 평가") + void decrypt_reuseParser_multipleEvaluations() throws Exception { + // identity: cipherBytes 그대로 반환 → Base64 decoding 경로 검증에 집중 + when(mockService.decrypt(any(), anyMap(), isNull(), any(byte[].class))) + .thenAnswer(inv -> (byte[]) inv.getArgument(3)); + + parser.parse("DJBDecrypt(enc, \"" + MODULE + "\")"); + + String firstEnc = Base64.getEncoder().encodeToString("first".getBytes(StandardCharsets.UTF_8)); + String secondEnc = Base64.getEncoder().encodeToString("second".getBytes(StandardCharsets.UTF_8)); + + parser.addVariable("enc", firstEnc); + assertEquals("first", parser.getValueAsObject()); + + parser.addVariable("enc", secondEnc); + assertEquals("second", parser.getValueAsObject()); + } + + // ========================================================================= + // 3. 라운드트립 + // ========================================================================= + + @Test + @DisplayName("3-1. 라운드트립 — 암호화 결과를 복호화하면 원문과 일치") + void roundTrip_encryptThenDecrypt_returnsOriginal() throws Exception { + // 암호화 + parser.parse("DJBEncrypt(plain, \"" + MODULE + "\")"); + parser.addVariable("plain", PLAIN_TEXT); + String encryptedBase64 = (String) parser.getValueAsObject(); + + // 복호화 + parser.parse("DJBDecrypt(enc, \"" + MODULE + "\")"); + parser.addVariable("enc", encryptedBase64); + + assertEquals(PLAIN_TEXT, parser.getValueAsObject()); + } + + @Test + @DisplayName("3-2. 라운드트립 — 한글 평문") + void roundTrip_koreanPlainText() throws Exception { + String korean = "한글평문테스트"; + when(mockService.encrypt(any(), anyMap(), isNull(), any(byte[].class))) + .thenReturn(korean.getBytes(StandardCharsets.UTF_8)); + when(mockService.decrypt(any(), anyMap(), isNull(), any(byte[].class))) + .thenReturn(korean.getBytes(StandardCharsets.UTF_8)); + + parser.parse("DJBEncrypt(plain, \"" + MODULE + "\")"); + parser.addVariable("plain", korean); + String enc = (String) parser.getValueAsObject(); + + parser.parse("DJBDecrypt(enc, \"" + MODULE + "\")"); + parser.addVariable("enc", enc); + assertEquals(korean, parser.getValueAsObject()); + } + + // ========================================================================= + // 4. 오류 처리 + // ========================================================================= + + @Test + @DisplayName("4-1. 암호화 — 파라미터 1개이면 예외") + void encrypt_singleParam_throwsException() { + assertThrows(Exception.class, () -> { + parser.parse("DJBEncrypt(plain)"); + parser.addVariable("plain", PLAIN_TEXT); + parser.getValueAsObject(); + }); + } + + @Test + @DisplayName("4-2. 복호화 — 파라미터 1개이면 예외") + void decrypt_singleParam_throwsException() { + assertThrows(Exception.class, () -> { + parser.parse("DJBDecrypt(enc)"); + parser.addVariable("enc", ENC_BASE64); + parser.getValueAsObject(); + }); + } + + @Test + @DisplayName("4-3. 복호화 — 유효하지 않은 Base64 상수이면 예외") + void decrypt_invalidBase64Constant_throwsException() { + assertThrows(Exception.class, () -> { + parser.parse("DJBDecrypt(\"!!invalid!!\", \"" + MODULE + "\")"); + parser.getValueAsObject(); + }); + } +} diff --git a/src/test/java/com/eactive/eai/custom/transformer/function/DJBCryptoFunctionTest.java b/src/test/java/com/eactive/eai/custom/transformer/function/DJBCryptoFunctionTest.java index bce6ef1..cbaeb75 100644 --- a/src/test/java/com/eactive/eai/custom/transformer/function/DJBCryptoFunctionTest.java +++ b/src/test/java/com/eactive/eai/custom/transformer/function/DJBCryptoFunctionTest.java @@ -44,8 +44,8 @@ class DJBCryptoFunctionTest { private static GenericApplicationContext ctx; private static CryptoModuleService mockService; - private DJBDecryt decryt; - private DJBEncryt encryt; + private DJBDecrypt decryt; + private DJBEncrypt encryt; // ========================================================================= // 클래스 초기화 @@ -73,8 +73,8 @@ class DJBCryptoFunctionTest { reset(mockService); when(mockService.decrypt(any(), anyMap(), isNull(), any(byte[].class))).thenReturn(PLAIN_BYTES); when(mockService.encrypt(any(), anyMap(), isNull(), any(byte[].class))).thenReturn(CIPHER_BYTES); - decryt = new DJBDecryt(); - encryt = new DJBEncryt(); + decryt = new DJBDecrypt(); + encryt = new DJBEncrypt(); } // =========================================================================