fix: correct scaffold package generation
All checks were successful
Deploy to OCIWP / deploy (push) Successful in 1m38s

This commit is contained in:
jade
2026-08-10 16:01:14 +09:00
parent 2712993286
commit f2e1c8d388
2 changed files with 108 additions and 23 deletions

View File

@@ -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" +
toolHintLine = (" @ToolHint(register = %s, categoryKey = \"%s\", mappingId = \"%s\",\n" +
" inputSchemaResource = \"%s\",\n" +
" outputSchemaResource = \"%s\")".formatted(register, inputSchemaClasspath, outputSchemaClasspath);
" 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<Object> 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<FieldDefinition> 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<FieldDefinition> outputFields) {
StringBuilder json = new StringBuilder("{\n");
List<FieldDefinition> 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-")

View File

@@ -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<ToolScaffolder.FieldDefinition> 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"));
}
}