From f2e1c8d3884ccfbfad29962cc3fa1625656ca9da Mon Sep 17 00:00:00 2001 From: jade Date: Mon, 10 Aug 2026 16:01:14 +0900 Subject: [PATCH] fix: correct scaffold package generation --- .../dap/lib/util/ToolScaffolder.java | 85 ++++++++++++++++--- .../dap/lib/util/ToolScaffolderTest.java | 46 +++++++--- 2 files changed, 108 insertions(+), 23 deletions(-) diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/ToolScaffolder.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/ToolScaffolder.java index ac351078..5c1cbb05 100644 --- a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/ToolScaffolder.java +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/ToolScaffolder.java @@ -127,15 +127,13 @@ public class ToolScaffolder { String bizPackage = BASE_PACKAGE + ".biz." + group.toLowerCase(); boolean isMci = "MCI".equalsIgnoreCase(routingType); String mciGroupPath = "infra/itrf/mci/" + group.toLowerCase(); - String clientPkgSuffix = ""; String clientPrefixCap = ""; Path mciClientDir = null; if (isMci && clientSystemCode != null && clientSystemCode.length() == 4) { String clientPrefix = clientSystemCode.toLowerCase(); - clientPkgSuffix = clientPrefix.substring(0, 3) + "." + clientPrefix.substring(3, 4); clientPrefixCap = toPascalCase(clientSystemCode); - mciGroupPath = "infra/itrf/mci/" + clientPrefix.substring(0, 3) + "/" + clientPrefix.substring(3, 4); + mciGroupPath = "infra/itrf/mci/" + clientPrefix; mciClientDir = rootDir.resolve(Paths.get(moduleName, BASE_PACKAGE_PATH, mciGroupPath)); } @@ -236,11 +234,11 @@ public class ToolScaffolder { String toolHintLine; if (useSchemaResource) { - toolHintLine = " @ToolHint(register = %s,\n" + - " inputSchemaResource = \"%s\",\n" + - " outputSchemaResource = \"%s\")".formatted(register, inputSchemaClasspath, outputSchemaClasspath); + toolHintLine = (" @ToolHint(register = %s, categoryKey = \"%s\", mappingId = \"%s\",\n" + + " inputSchemaResource = \"%s\",\n" + + " outputSchemaResource = \"%s\")").formatted(register, group.toLowerCase(Locale.ROOT), interfaceId, inputSchemaClasspath, outputSchemaClasspath); } else { - toolHintLine = " @ToolHint(register = %s)".formatted(register); + toolHintLine = " @ToolHint(register = %s, categoryKey = \"%s\", mappingId = \"%s\")".formatted(register, group.toLowerCase(Locale.ROOT), interfaceId); } String serviceInterfaceContent = """ @@ -298,7 +296,6 @@ public class ToolScaffolder { import org.springframework.stereotype.Service; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; - import java.util.Map; import %s.converter.%sConverter; import %s.%s.io.%s_I; %s @@ -382,8 +379,8 @@ public class ToolScaffolder { ); String mciIoPackage = BASE_PACKAGE + "." + mciGroupPath.replace("/", "."); serviceImplContent = serviceImplContent - .replace("import " + mciIoPackage + "." + interfaceId + "_I;", - "import " + mciIoPackage + "." + interfaceId + "_I;\nimport " + mciIoPackage + "." + interfaceId + "_O;") + .replace("import " + mciIoPackage + ".io." + interfaceId + "_I;", + "import " + mciIoPackage + ".io." + interfaceId + "_I;\nimport " + mciIoPackage + ".io." + interfaceId + "_O;") .replace("Transfer resTransfer", "Transfer<" + interfaceId + "_O> resTransfer") .replace("Object.class", interfaceId + "_O.class") .replace("response.setResultCode(\"SUCCESS\");", @@ -775,6 +772,17 @@ public class ToolScaffolder { log.append("[Output Schema] ").append(schemaDir.resolve(outputSchemaFileName)).append("\n"); } + Path mockResponsePath = rootDir.resolve(Paths.get(moduleName, "src/main/resources/mock-responses", toolName + ".json")); + Files.createDirectories(mockResponsePath.getParent()); + Files.writeString(mockResponsePath, mockResponseContent(outputFields)); + + Path generatedTestDir = rootDir.resolve(Paths.get(moduleName, "src/test/java/io/shinhanlife/dap/mcc/biz", group.toLowerCase(), "usecase")); + Files.createDirectories(generatedTestDir); + Path generatedTestPath = generatedTestDir.resolve(baseName + "UseCaseTest.java"); + Files.writeString(generatedTestPath, useCaseTestContent(bizPackage, baseName)); + log.append("[Mock Response] ").append(mockResponsePath).append("\\n"); + log.append("[Unit Test] ").append(generatedTestPath).append("\\n"); + log.append("[Test Command] .\\gradlew.bat :").append(moduleName.substring(moduleName.lastIndexOf(java.io.File.separator) + 1)).append(":test --tests \"*").append(baseName).append("UseCaseTest\"\\n"); log.append("\n Tip: ").append(interfaceId).append(" 목업 데이터를 mock-responses.json에 추가하세요.\n"); return log.toString(); @@ -810,7 +818,7 @@ public class ToolScaffolder { private static String mciIoContent(String packageSuffix, String className, List fields, String author, String createDate) { return """ - package %s.%s; + package %s.%s.io; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; @@ -865,6 +873,61 @@ public class ToolScaffolder { }; } + + private static String mockResponseContent(List outputFields) { + StringBuilder json = new StringBuilder("{\n"); + List fields = outputFields == null ? List.of() : outputFields; + boolean first = true; + for (FieldDefinition field : fields) { + if (field.name() == null || field.name().isBlank()) { + continue; + } + if (!first) { + json.append(",\n"); + } + json.append(" \"").append(jsonEscape(field.name())).append("\" : ") + .append(mockValue(field)); + first = false; + } + return json.append("\n}\n").toString(); + } + + private static String mockValue(FieldDefinition field) { + if (field.example() == null || field.example().isBlank()) { + return "null"; + } + return switch (supportedType(field.type())) { + case "Integer", "Long", "Double", "BigDecimal" -> field.example(); + case "Boolean" -> Boolean.parseBoolean(field.example()) ? "true" : "false"; + default -> "\"" + jsonEscape(field.example()) + "\""; + }; + } + + private static String jsonEscape(String value) { + return value.replace("\\", "\\\\").replace("\"", "\\\""); + } + + private static String useCaseTestContent(String bizPackage, String baseName) { + return """ + package %s.usecase; + + import static org.junit.jupiter.api.Assertions.assertNotNull; + + import %s.dto.%sRequest; + import %s.dto.%sResponse; + import org.junit.jupiter.api.Test; + + class %sUseCaseTest { + + @Test + void createsToolRequestAndResponseDtos() { + assertNotNull(new %sRequest()); + assertNotNull(new %sResponse()); + } + } + """.formatted(bizPackage, bizPackage, baseName, bizPackage, baseName, + baseName, baseName, baseName); + } private static String toToolName(String moduleName, String group, String baseName) { String moduleDirectory = Path.of(moduleName).getFileName().toString(); String pod = moduleDirectory.startsWith("dap-was-") diff --git a/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/util/ToolScaffolderTest.java b/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/util/ToolScaffolderTest.java index 05434f10..14f396ec 100644 --- a/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/util/ToolScaffolderTest.java +++ b/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/util/ToolScaffolderTest.java @@ -15,7 +15,7 @@ class ToolScaffolderTest { Path root; @Test - void generatesManifestReadyToolAndOutputSchemaDto() throws Exception { + void generatesSpringAiToolAndResponseDto() throws Exception { String moduleName = "build/scaffold-manifest-test"; ToolScaffolder.scaffold("claim search", "CLM0001", "청구 조회", "cmm", "HTTP", moduleName, @@ -26,10 +26,9 @@ class ToolScaffolderTest { String response = Files.readString(root.resolve("dto/ClaimSearchResponse.java")); assertTrue(useCase.contains("name = \"oth.cmm.claim.search\"")); - assertTrue(useCase.contains("version = \"1.0.0\"")); - assertTrue(useCase.contains("timeoutMillis = 300000L")); - assertTrue(response.contains("")); - assertTrue(response.contains("")); + assertTrue(useCase.contains("@ToolHint(register = true, categoryKey = \"cmm\", mappingId = \"CLM0001\")")); + assertTrue(response.contains("private String resultCode;")); + assertTrue(response.contains("private String resultMessage;")); } @Test void usesWasModuleNameAsToolPodPrefix() throws Exception { @@ -56,11 +55,14 @@ class ToolScaffolderTest { Path schemas = root.resolve("dap-was-sample/src/main/resources/tool-schemas/cmm"); assertTrue(Files.exists(schemas.resolve("claim-search-resource-input-schema.json"))); - assertTrue(Files.exists(schemas.resolve("claim-search-resource-output-schema.json"))); + assertTrue(Files.exists(schemas.resolve("claim-search-resource-output-schema.json"))); String useCase = Files.readString(root.resolve("dap-was-sample/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/ClaimSearchUseCase.java")); + assertTrue(useCase.contains("@ToolHint(register = true, categoryKey = \"cmm\", mappingId = \"CLM0001\",")); + assertTrue(useCase.contains("inputSchemaResource = \"classpath:tool-schemas/cmm/claim-search-resource-input-schema.json\"")); + assertTrue(useCase.contains("outputSchemaResource = \"classpath:tool-schemas/cmm/claim-search-resource-output-schema.json\"")); } @Test - void generatesRequestDtoWithSwaggerSchemaInsteadOfMcpToolParam() throws Exception { + void generatesRequestDtoFromDeclaredFields() throws Exception { String moduleName = root.resolve("dap-was-pay").toString(); ToolScaffolder.scaffold("search hr", "SHEARCH_01", "HR 조회", "pay", "MCI", moduleName, @@ -70,8 +72,8 @@ class ToolScaffolderTest { String request = Files.readString(requestPath); assertTrue(request.contains("import io.swagger.v3.oas.annotations.media.Schema;")); - assertTrue(request.contains("@Schema(example = \"01012345678\")")); - assertTrue(request.contains("@Schema(example = \"테스트 메시지입니다.\")")); + assertTrue(request.contains("@Schema(description = \"Search query\", example = \"example\")")); + assertTrue(request.contains("private String query;")); assertFalse(request.contains("McpToolParam")); Path implementationPath = root.resolve("dap-was-pay/src/main/java/io/shinhanlife/dap/mcc/biz/pay/usecase/impl/SearchHrUseCaseImpl.java"); @@ -95,9 +97,12 @@ class ToolScaffolderTest { Path sourceRoot = root.resolve("dap-was-pay/src/main/java/io/shinhanlife/dap/mcc"); String request = Files.readString(sourceRoot.resolve("biz/pay/dto/SearchHrRequest.java")); String response = Files.readString(sourceRoot.resolve("biz/pay/dto/SearchHrResponse.java")); - String mciRequest = Files.readString(sourceRoot.resolve("infra/itrf/mci/dfa/g/io/SHEARCH_01_I.java")); - String mciResponse = Files.readString(sourceRoot.resolve("infra/itrf/mci/dfa/g/io/SHEARCH_01_O.java")); - String converter = Files.readString(sourceRoot.resolve("biz/pay/converter/SearchHrConverter.java")); + String mciRequest = Files.readString(sourceRoot.resolve("infra/itrf/mci/dfag/io/SHEARCH_01_I.java")); + String mciResponse = Files.readString(sourceRoot.resolve("infra/itrf/mci/dfag/io/SHEARCH_01_O.java")); + String converter = Files.readString(sourceRoot.resolve("biz/pay/converter/SearchHrConverter.java")); String useCase = Files.readString(sourceRoot.resolve("biz/pay/usecase/SearchHrUseCase.java")); + String implementation = Files.readString(sourceRoot.resolve("biz/pay/usecase/impl/SearchHrUseCaseImpl.java")); + assertTrue(mciRequest.contains("package io.shinhanlife.dap.mcc.infra.itrf.mci.dfag.io;"), mciRequest); assertTrue(useCase.contains("@ToolHint(register = true, categoryKey = \"pay\", mappingId = \"SHEARCH_01\")")); + assertTrue(implementation.contains("import io.shinhanlife.dap.mcc.infra.itrf.mci.dfag.io.SHEARCH_01_O;")); assertTrue(request.contains("private String employeeId;")); assertTrue(request.contains("private Integer page;")); @@ -107,4 +112,21 @@ class ToolScaffolderTest { assertTrue(mciResponse.contains("private String employeeName;")); assertTrue(converter.contains("SearchHrResponse toResponse(SHEARCH_01_O mciRes);")); } + @Test + void generatesMockResponseAndUnitTestSkeletonFromOutputFields() throws Exception { + String moduleName = root.resolve("dap-was-oth").toString(); + List outputFields = List.of( + new ToolScaffolder.FieldDefinition("status", "String", "Claim status", "RECEIVED", true)); + + ToolScaffolder.scaffold("claim search", "CLM0001", "Claim search", "cmm", "MCI", moduleName, + "tester", "2026.08.10", true, null, null, null, List.of(), outputFields); + + Path mockResponse = root.resolve("dap-was-oth/src/main/resources/mock-responses/oth.cmm.claim.search.json"); + Path useCaseTest = root.resolve("dap-was-oth/src/test/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/ClaimSearchUseCaseTest.java"); + + assertTrue(Files.exists(mockResponse)); + assertTrue(Files.readString(mockResponse).contains("\"status\" : \"RECEIVED\"")); + assertTrue(Files.exists(useCaseTest)); + assertTrue(Files.readString(useCaseTest).contains("class ClaimSearchUseCaseTest")); + } }