diff --git a/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/presentation/ScaffoldingController.java b/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/presentation/ScaffoldingController.java index f47aff4b..dc87d206 100644 --- a/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/presentation/ScaffoldingController.java +++ b/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/presentation/ScaffoldingController.java @@ -39,7 +39,7 @@ import org.springframework.web.bind.annotation.*; public class ScaffoldingController { private static final Set SUPPORTED_FIELD_TYPES = Set.of( - "String", "Integer", "Long", "Double", "Boolean", "BigDecimal"); + "String", "Integer", "Long", "Double", "Boolean", "BigDecimal", "Enum", "List"); private static final Set SUPPORTED_AI_MODELS = Set.of( "inclusionai/ling-3.0-flash:free", "openai/gpt-oss-20b:free", @@ -116,6 +116,26 @@ public class ScaffoldingController { } } + @PostMapping("/tool-group") + public String scaffoldToolGroup(@RequestBody ToolGroupRequest request) { + try { + if (request == null || request.useCaseName() == null + || !request.useCaseName().trim().matches("^[A-Z][A-Za-z0-9]*$")) { + throw new IllegalArgumentException("UseCase name must be PascalCase."); + } + String moduleName = request.moduleName() == null || request.moduleName().isBlank() + ? "dap-was-oth" : request.moduleName().trim(); + String author = request.author() == null || request.author().isBlank() + ? System.getProperty("user.name") : request.author().trim(); + String date = request.date() == null || request.date().isBlank() + ? LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy.MM.dd")) : request.date().trim(); + return ToolScaffolder.scaffoldUseCase(request.useCaseName().trim(), moduleName, author, date, + request.tools() == null ? List.of() : request.tools()); + } catch (Exception e) { + return "Error: " + safeMessage(e); + } + } + @PostMapping("/field-draft") public ResponseEntity generateFieldDraft(@RequestBody Map req) { String description = req.getOrDefault("description", "").trim(); @@ -133,8 +153,9 @@ public class ScaffoldingController { Generate Java DTO fields for an MCP tool. Return JSON only. Do not add Markdown, explanations, or code fences. The response must have this exact shape: - {"fields":[{"name":"camelCaseName","type":"String","description":"short description","example":"example","required":true}]} - Allowed type values: String, Integer, Long, Double, Boolean, BigDecimal. + {"fields":[{"name":"camelCaseName","type":"String","description":"short description","example":"example","required":true,"enumValues":[],"itemType":null,"itemFields":[]}]} + Allowed type values: String, Integer, Long, Double, Boolean, BigDecimal, Enum, List. + Enum fields must include enumValues. List fields must include itemType; use Object plus itemFields for object lists. Generate fields only for the requested target: %s. For OUTPUT fields, include resultCode and resultMessage when appropriate. Keep field names valid Java camelCase identifiers. Generate at most 10 fields. @@ -162,12 +183,12 @@ public class ScaffoldingController { Generate an MCP Tool scaffold from the user request. Return JSON only. Do not add Markdown, explanations, or code fences. The response must have this exact shape: - {"baseName":"PascalCaseName","title":"short Korean title","description":"clear Korean LLM tool guidance","categoryKey":"cmm","routingType":"HTTP","httpApiName":"simple-api-name","functionDescription":"core business function","displayDescription":"short portal description","whenToUse":"specific user requests that should select this tool","whenNotToUse":"requests or conditions that must not select this tool","ioLimits":"allowed input and output scope and limits","exampleQueries":["query 1","query 2","query 3"],"tags":["domain","action"],"ownerOrg":"MCP_TOOL","inputFields":[{"name":"camelCaseName","type":"String","description":"short description","example":"example","required":true}],"outputFields":[{"name":"resultCode","type":"String","description":"result code","example":"SUCCESS","required":true}]} + {"baseName":"PascalCaseName","title":"short Korean title","description":"clear Korean LLM tool guidance","categoryKey":"cmm","routingType":"HTTP","httpApiName":"simple-api-name","functionDescription":"core business function","displayDescription":"short portal description","whenToUse":"specific user requests that should select this tool","whenNotToUse":"requests or conditions that must not select this tool","ioLimits":"allowed input and output scope and limits","exampleQueries":["query 1","query 2","query 3"],"tags":["domain","action"],"ownerOrg":"MCP_TOOL","inputFields":[{"name":"camelCaseName","type":"String","description":"short description","example":"example","required":true,"enumValues":[],"itemType":null,"itemFields":[]}],"outputFields":[{"name":"resultCode","type":"String","description":"result code","example":"SUCCESS","required":true,"enumValues":[],"itemType":null,"itemFields":[]}]} categoryKey must be exactly three lowercase letters or digits. routingType must be either HTTP or MCI. httpApiName can contain only letters, digits, hyphens, and underscores. Write every V17 metadata field for its distinct purpose; do not copy the same sentence into all fields. Generate 3 to 10 realistic exampleQueries and concise search tags. Use MCP_TOOL for ownerOrg unless the user names an owner. - Allowed field type values: String, Integer, Long, Double, Boolean, BigDecimal. + Allowed field type values: String, Integer, Long, Double, Boolean, BigDecimal, Enum, List. Enum must include enumValues; List must include itemType and object lists include itemFields. Keep all field names valid Java camelCase identifiers. Generate at most 10 fields per list. Do not generate interfaceId or clientSystemCode; those must come from a real integration contract. User request: %s @@ -248,7 +269,10 @@ public class ScaffoldingController { field.type() == null ? "String" : field.type().trim(), field.description() == null ? "" : field.description().trim(), field.example() == null ? "" : field.example().trim(), - field.required())) + field.required(), + field.enumValues() == null ? List.of() : field.enumValues(), + field.itemType(), + field.itemFields() == null ? List.of() : field.itemFields())) .peek(field -> { if (!field.name().matches("^[A-Za-z_$][A-Za-z0-9_$]*$")) { throw new IllegalArgumentException("AI가 올바르지 않은 필드명을 생성했습니다: " + field.name()); @@ -256,6 +280,7 @@ public class ScaffoldingController { if (!SUPPORTED_FIELD_TYPES.contains(field.type())) { throw new IllegalArgumentException("AI가 지원하지 않는 Type을 생성했습니다: " + field.type()); } + validateStructuredField(field); if (!names.add(field.name())) { throw new IllegalArgumentException("AI가 중복 필드명을 생성했습니다: " + field.name()); } @@ -263,6 +288,18 @@ public class ScaffoldingController { .toList(); } + private void validateStructuredField(ToolScaffolder.FieldDefinition field) { + if ("Enum".equals(field.type()) && field.enumValues().isEmpty()) { + throw new IllegalArgumentException("Enum field needs enumValues: " + field.name()); + } + if ("List".equals(field.type()) && (field.itemType() == null || field.itemType().isBlank())) { + throw new IllegalArgumentException("List field needs itemType: " + field.name()); + } + if ("List".equals(field.type()) && "Object".equals(field.itemType()) && field.itemFields().isEmpty()) { + throw new IllegalArgumentException("Object List field needs itemFields: " + field.name()); + } + } + private ToolDraft validateToolDraft(ToolDraft draft) { if (draft == null || draft.baseName() == null || !draft.baseName().trim().matches("^[A-Z][A-Za-z0-9]*$")) { throw new IllegalArgumentException("AI가 올바르지 않은 Base Name을 생성했습니다."); @@ -355,4 +392,8 @@ public class ScaffoldingController { List inputFields, List outputFields) { } + + private record ToolGroupRequest(String useCaseName, String moduleName, String author, String date, + List tools) { + } } diff --git a/dap-gateway/src/main/resources/static/admin/scaffold.html b/dap-gateway/src/main/resources/static/admin/scaffold.html index dfdf4e89..6f73afbe 100644 --- a/dap-gateway/src/main/resources/static/admin/scaffold.html +++ b/dap-gateway/src/main/resources/static/admin/scaffold.html @@ -824,6 +824,19 @@
+
+
+
+ +
현재 Tool을 같은 UseCase에 추가하면, Tool별 Client를 호출하는 여러 MCP Tool 메서드가 생성됩니다.
+
+ +
+
+
+
묶음에 추가된 Tool 없음 — 일반 Generate Tool은 기존 단일 Tool 생성입니다.
+
+
@@ -1158,6 +1171,7 @@ Name Type + Enum values / List item type Description Example Required @@ -1469,6 +1483,95 @@ }); handleFormSubmit('podForm', '/api/v1/scaffold/pod'); + + const groupedTools = []; + + function toMethodName(baseName) { + return baseName ? baseName.charAt(0).toLowerCase() + baseName.slice(1) : ''; + } + + function parseToolFields(id) { + const text = document.getElementById(id).value.trim(); + if (!text) return []; + const fields = JSON.parse(text); + if (!Array.isArray(fields)) throw new Error(`${id} must be a JSON array.`); + return fields; + } + + function currentToolDefinition() { + const form = document.getElementById('toolForm'); + const data = Object.fromEntries(new FormData(form).entries()); + if (!data.baseName || !data.interfaceId || !data.clientSystemCode) { + throw new Error('Base Name, Legacy Interface ID, Client System Code를 입력해주세요.'); + } + if (data.routingType !== 'MCI') { + throw new Error('여러 Tool을 하나의 UseCase로 생성하는 기능은 현재 MCI 전용입니다.'); + } + return { + baseName: data.baseName, + methodName: toMethodName(data.baseName), + interfaceId: data.interfaceId, + title: data.title || data.baseName, + description: data.description || '', + group: data.categoryKey, + routingType: data.routingType, + register: data.register === 'true', + clientSystemCode: data.clientSystemCode, + httpApiName: data.httpApiName || null, + inputFields: parseToolFields('inputFields'), + outputFields: parseToolFields('outputFields'), + definitionOptions: { + functionDescription: data.functionDescription || '', displayDescription: data.displayDescription || '', + whenToUse: data.whenToUse || '', whenNotToUse: data.whenNotToUse || '', ioLimits: data.ioLimits || '', + exampleQueries: (data.exampleQueries || '').split(/[\n,]+/).map(value => value.trim()).filter(Boolean), + tags: (data.tags || '').split(',').map(value => value.trim()).filter(Boolean), ownerOrg: data.ownerOrg || 'MCP_TOOL' + } + }; + } + + function addCurrentToolToGroup() { + try { + const tool = currentToolDefinition(); + if (groupedTools.some(item => item.methodName === tool.methodName || item.baseName === tool.baseName)) { + throw new Error('같은 Base Name 또는 메서드명이 이미 묶음에 있습니다.'); + } + groupedTools.push(tool); + renderToolGroupSummary(); + } catch (error) { + alert(error.message); + } + } + + function renderToolGroupSummary() { + const summary = document.getElementById('toolGroupSummary'); + summary.textContent = groupedTools.length + ? `${groupedTools.length}개 Tool: ${groupedTools.map(tool => `${tool.baseName} → ${tool.methodName}()`).join(', ')}` + : '묶음에 추가된 Tool 없음 — 일반 Generate Tool은 기존 단일 Tool 생성입니다.'; + } + + document.getElementById('toolForm').addEventListener('submit', function(e) { + if (!groupedTools.length) return; + e.preventDefault(); + e.stopImmediatePropagation(); + try { + const useCaseName = document.getElementById('toolGroupUseCaseName').value.trim(); + if (!/^[A-Z][A-Za-z0-9]*$/.test(useCaseName)) throw new Error('UseCase 이름은 PascalCase로 입력해주세요. 예: Customer'); + const current = currentToolDefinition(); + const tools = groupedTools.some(item => item.methodName === current.methodName) ? groupedTools : [...groupedTools, current]; + const data = Object.fromEntries(new FormData(this).entries()); + fetch('/api/v1/scaffold/tool-group', { + method: 'POST', headers: {'Content-Type': 'application/json'}, + body: JSON.stringify({useCaseName, moduleName: data.moduleName, author: data.author, date: data.date, tools}) + }).then(response => response.text()).then(result => { + const resultBox = document.getElementById('resultBox'); + resultBox.style.display = 'block'; resultBox.className = result.startsWith('Error:') ? 'error' : 'success'; + resultBox.textContent = result; + }); + } catch (error) { + alert(error.message); + } + }); + handleFormSubmit('toolForm', '/api/v1/scaffold/tool'); const fieldExamples = { @@ -1498,14 +1601,16 @@ } } - const fieldTypes = ['String', 'Integer', 'Long', 'Double', 'Boolean', 'BigDecimal']; + const fieldTypes = ['String', 'Integer', 'Long', 'Double', 'Boolean', 'BigDecimal', 'Enum', 'List']; const typeExamples = { String: 'example', Integer: '1', Long: '1', Double: '1.0', Boolean: 'true', - BigDecimal: '1000.00' + BigDecimal: '1000.00', + Enum: 'OPEN', + List: 'C001' }; const fieldTemplates = { customer: { @@ -1608,18 +1713,23 @@ updateFieldEditorPreview(); }); + const detailsInput = makeInput( + field.type === 'Enum' ? (field.enumValues || []).join(', ') : (field.type === 'List' ? (field.itemType || 'String') : ''), + 'Enum: OPEN, CLOSED / List: String'); + detailsInput.dataset.field = 'details'; + const requiredSelect = document.createElement('select'); requiredSelect.className = 'form-select form-select-sm'; requiredSelect.dataset.field = 'required'; requiredSelect.add(new Option('Required', 'true', false, field.required === true || field.required === 'true')); requiredSelect.add(new Option('Optional', 'false', false, !(field.required === true || field.required === 'true'))); - [nameInput, descriptionInput, exampleInput, requiredSelect].forEach(control => { + [nameInput, descriptionInput, exampleInput, requiredSelect, detailsInput].forEach(control => { control.addEventListener('input', updateFieldEditorPreview); control.addEventListener('change', updateFieldEditorPreview); }); - [nameInput, typeSelect, descriptionInput, exampleInput, requiredSelect].forEach(control => { + [nameInput, typeSelect, detailsInput, descriptionInput, exampleInput, requiredSelect].forEach(control => { const cell = document.createElement('td'); cell.appendChild(control); row.appendChild(cell); @@ -1641,13 +1751,19 @@ function currentEditorFields() { return [...document.querySelectorAll('#fieldEditorBody tr')] - .map(row => ({ - name: row.querySelector('[data-field="name"]').value.trim(), - type: row.querySelector('[data-field="type"]').value, - description: row.querySelector('[data-field="description"]').value.trim(), - example: row.querySelector('[data-field="example"]').value.trim(), - required: row.querySelector('[data-field="required"]').value === 'true' - })) + .map(row => { + const type = row.querySelector('[data-field="type"]').value; + const details = row.querySelector('[data-field="details"]').value.trim(); + return { + name: row.querySelector('[data-field="name"]').value.trim(), type, + description: row.querySelector('[data-field="description"]').value.trim(), + example: row.querySelector('[data-field="example"]').value.trim(), + required: row.querySelector('[data-field="required"]').value === 'true', + enumValues: type === 'Enum' ? details.split(',').map(value => value.trim()).filter(Boolean) : [], + itemType: type === 'List' ? (details || 'String') : null, + itemFields: [] + }; + }) .filter(field => field.name); } diff --git a/dap-gateway/src/test/java/io/shinhanlife/dap/mcg/presentation/ScaffoldingControllerToolDraftTest.java b/dap-gateway/src/test/java/io/shinhanlife/dap/mcg/presentation/ScaffoldingControllerToolDraftTest.java index 14fd1dc9..507ad145 100644 --- a/dap-gateway/src/test/java/io/shinhanlife/dap/mcg/presentation/ScaffoldingControllerToolDraftTest.java +++ b/dap-gateway/src/test/java/io/shinhanlife/dap/mcg/presentation/ScaffoldingControllerToolDraftTest.java @@ -8,6 +8,9 @@ import org.springframework.http.MediaType; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.junit.jupiter.api.io.TempDir; + +import java.nio.file.Path; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; @@ -16,9 +19,33 @@ import static org.mockito.Mockito.when; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; class ScaffoldingControllerToolDraftTest { + @TempDir + Path root; + + @Test + void groupedToolRequestGeneratesOneUseCase() throws Exception { + ChatClient.Builder builder = mock(ChatClient.Builder.class); + MockMvc mockMvc = MockMvcBuilders.standaloneSetup( + new ScaffoldingController(builder, new ObjectMapper())) + .setMessageConverters(new MappingJackson2HttpMessageConverter()) + .build(); + String moduleName = root.resolve("dap-was-customer").toString().replace("\\", "\\\\"); + String request = """ + {"useCaseName":"Customer","moduleName":"%s","author":"tester","date":"2026.08.12","tools":[ + {"baseName":"CustomerGuidance","methodName":"searchGuidance","interfaceId":"CTMNILO00007","title":"Customer guidance","description":"Search guidance","group":"cmm","routingType":"MCI","register":false,"clientSystemCode":"NILD","inputFields":[{"name":"customerId","type":"String","description":"Customer ID","example":"C001","required":true}],"outputFields":[]} + ]} + """.formatted(moduleName); + + mockMvc.perform(post("/api/v1/scaffold/tool-group") + .contentType(MediaType.APPLICATION_JSON).content(request)) + .andExpect(status().isOk()) + .andExpect(content().string(org.hamcrest.Matchers.containsString("CustomerUseCase.java"))); + } + @Test void toolDraftEndpointIsAvailable() throws Exception { ChatClient.Builder builder = mock(ChatClient.Builder.class); 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 d6da6dd5..514912f4 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 @@ -45,7 +45,18 @@ public class ToolScaffolder { private static final String BASE_PACKAGE = "io.shinhanlife.dap.mcc"; private static final String BASE_PACKAGE_PATH = "src/main/java/io/shinhanlife/dap/mcc"; - public record FieldDefinition(String name, String type, String description, String example, boolean required) { + public record FieldDefinition(String name, String type, String description, String example, boolean required, + List enumValues, String itemType, List itemFields) { + public FieldDefinition(String name, String type, String description, String example, boolean required) { + this(name, type, description, example, required, List.of(), null, List.of()); + } + } + + public record ToolMethodDefinition(String baseName, String methodName, String interfaceId, + String title, String description, String group, String routingType, + boolean register, String clientSystemCode, String httpApiName, + List inputFields, List outputFields, + ToolDefinitionOptions definitionOptions) { } public record ToolDefinitionOptions( @@ -59,6 +70,206 @@ public class ToolScaffolder { String ownerOrg) { } + public static String scaffoldUseCase(String useCaseName, String moduleName, String author, + String createDate, List tools) throws IOException { + if (tools == null || tools.isEmpty()) { + throw new IllegalArgumentException("At least one Tool method is required."); + } + String useCaseBaseName = toPascalCase(useCaseName); + String group = tools.getFirst().group().toLowerCase(Locale.ROOT); + validateToolMethods(tools, group); + + String sourceDir = System.getenv("AXHUB_SOURCE_DIR"); + Path rootDir = sourceDir == null ? Paths.get(".") : Paths.get(sourceDir); + Path configuredModule = Paths.get(moduleName); + Path moduleRoot = configuredModule.isAbsolute() ? configuredModule : rootDir.resolve(configuredModule); + Path sourceRoot = moduleRoot.resolve(BASE_PACKAGE_PATH); + Path useCaseDir = sourceRoot.resolve(Paths.get("biz", group, "usecase")); + Path implDir = useCaseDir.resolve("impl"); + Path dtoDir = sourceRoot.resolve(Paths.get("biz", group, "dto")); + Path converterDir = sourceRoot.resolve(Paths.get("biz", group, "converter")); + Path definitionDir = moduleRoot.resolve(Paths.get("src", "main", "resources", "tool-definitions", group)); + Path mockDir = moduleRoot.resolve(Paths.get("src", "main", "resources", "mock-responses")); + Files.createDirectories(useCaseDir); + Files.createDirectories(implDir); + Files.createDirectories(dtoDir); + Files.createDirectories(converterDir); + Files.createDirectories(definitionDir); + Files.createDirectories(mockDir); + + String bizPackage = BASE_PACKAGE + ".biz." + group; + writeUtf8(useCaseDir.resolve(useCaseBaseName + "UseCase.java"), + groupedUseCaseContent(bizPackage, useCaseBaseName, moduleName, tools)); + writeUtf8(implDir.resolve(useCaseBaseName + "UseCaseImpl.java"), + groupedUseCaseImplContent(bizPackage, useCaseBaseName, tools)); + writeUtf8(converterDir.resolve(useCaseBaseName + "Converter.java"), + groupedConverterContent(bizPackage, useCaseBaseName, tools)); + + StringBuilder log = new StringBuilder("\n=========================================\n") + .append(" Multi Tool Scaffolding Complete\n") + .append("=========================================\n") + .append("[Usecase Interface] ").append(useCaseDir.resolve(useCaseBaseName + "UseCase.java")).append("\n") + .append("[Usecase Impl] ").append(implDir.resolve(useCaseBaseName + "UseCaseImpl.java")).append("\n"); + for (ToolMethodDefinition tool : tools) { + writeGroupedToolFiles(moduleRoot, sourceRoot, dtoDir, definitionDir, mockDir, bizPackage, tool, moduleName, log); + } + log.append("[Converter] ").append(converterDir.resolve(useCaseBaseName + "Converter.java")).append("\n"); + return log.toString(); + } + + private static void validateToolMethods(List tools, String expectedGroup) { + Set methods = new LinkedHashSet<>(); + Set toolNames = new LinkedHashSet<>(); + for (ToolMethodDefinition tool : tools) { + if (tool == null || tool.baseName() == null || tool.baseName().isBlank() + || tool.methodName() == null || !tool.methodName().matches("^[a-zA-Z_$][a-zA-Z0-9_$]*$")) { + throw new IllegalArgumentException("Every Tool needs a valid base name and Java method name."); + } + if (!expectedGroup.equalsIgnoreCase(tool.group())) { + throw new IllegalArgumentException("All Tool methods in one UseCase must use the same category."); + } + if (!"MCI".equalsIgnoreCase(tool.routingType())) { + throw new IllegalArgumentException("Grouped Tool scaffolding currently supports MCI Tools only."); + } + if (tool.interfaceId() == null || tool.interfaceId().isBlank() + || tool.clientSystemCode() == null || tool.clientSystemCode().isBlank()) { + throw new IllegalArgumentException("MCI Tool needs an interface ID and Client system code."); + } + String toolName = toToolName("", tool.group(), toPascalCase(tool.baseName())); + if (!methods.add(tool.methodName()) || !toolNames.add(toolName)) { + throw new IllegalArgumentException("Tool method names and MCP Tool names must be unique."); + } + } + } + + private static void writeGroupedToolFiles(Path moduleRoot, Path sourceRoot, Path dtoDir, Path definitionDir, + Path mockDir, String bizPackage, ToolMethodDefinition tool, + String moduleName, StringBuilder log) throws IOException { + String baseName = toPascalCase(tool.baseName()); + String code = tool.clientSystemCode().toLowerCase(Locale.ROOT); + String ioPackage = BASE_PACKAGE + ".infra.itrf.mci." + code; + Path clientDir = sourceRoot.resolve(Paths.get("infra", "itrf", "mci", code)); + Path ioDir = clientDir.resolve("io"); + Files.createDirectories(ioDir); + writeUtf8(dtoDir.resolve(baseName + "Request.java"), + dtoContent(bizPackage + ".dto", baseName + "Request", tool.inputFields(), "", "", true)); + writeUtf8(dtoDir.resolve(baseName + "Response.java"), + dtoContent(bizPackage + ".dto", baseName + "Response", tool.outputFields(), "", "", false)); + writeStructuredFieldTypes(dtoDir, bizPackage + ".dto", baseName + "Request", tool.inputFields()); + writeStructuredFieldTypes(dtoDir, bizPackage + ".dto", baseName + "Response", tool.outputFields()); + writeUtf8(ioDir.resolve(baseName + "_I.java"), + mciIoContent("infra.itrf.mci." + code, baseName + "_I", tool.inputFields(), "", "")); + writeUtf8(ioDir.resolve(baseName + "_O.java"), + mciIoContent("infra.itrf.mci." + code, baseName + "_O", tool.outputFields(), "", "")); + writeStructuredFieldTypes(ioDir, ioPackage + ".io", baseName + "_I", tool.inputFields()); + writeStructuredFieldTypes(ioDir, ioPackage + ".io", baseName + "_O", tool.outputFields()); + writeUtf8(clientDir.resolve(baseName + "Client.java"), + groupedMciClientContent(ioPackage, baseName, tool.interfaceId())); + + String toolName = toToolName(moduleName, tool.group(), baseName); + writeUtf8(definitionDir.resolve(toolName + ".yml"), toolDefinitionContentV17(toolName, + option(tool.title(), baseName), tool.description(), tool.group(), tool.interfaceId(), + tool.inputFields(), isMutationTool(baseName), tool.definitionOptions())); + writeUtf8(mockDir.resolve(toolName + ".json"), mockResponseContent(tool.outputFields())); + log.append("[Tool] ").append(toolName).append(" -> ").append(clientDir.resolve(baseName + "Client.java")).append("\n"); + } + + private static String groupedUseCaseContent(String bizPackage, String useCaseBaseName, String moduleName, + List tools) { + StringBuilder imports = new StringBuilder(); + StringBuilder methods = new StringBuilder(); + for (ToolMethodDefinition tool : tools) { + String baseName = toPascalCase(tool.baseName()); + imports.append("import ").append(bizPackage).append(".dto.").append(baseName).append("Request;\n") + .append("import ").append(bizPackage).append(".dto.").append(baseName).append("Response;\n"); + methods.append(" @McpTool(name = \"").append(toToolName(moduleName, tool.group(), baseName)) + .append("\", title = \"").append(javaText(option(tool.title(), baseName))) + .append("\", description = \"").append(javaText(option(tool.description(), ""))).append("\")\n") + .append(" @ToolHint(register = ").append(tool.register()).append(", categoryKey = \"") + .append(tool.group().toLowerCase(Locale.ROOT)).append("\", mappingId = \"") + .append(javaText(tool.interfaceId())).append("\")\n") + .append(" ").append(baseName).append("Response ").append(tool.methodName()).append("(") + .append(baseName).append("Request req);\n\n"); + } + return "package " + bizPackage + ".usecase;\n\n" + + "import org.springaicommunity.mcp.annotation.McpTool;\n" + + "import io.shinhanlife.dap.lib.annotation.ToolHint;\n" + + imports + "\npublic interface " + useCaseBaseName + "UseCase {\n\n" + methods + "}\n"; + } + + private static String groupedUseCaseImplContent(String bizPackage, String useCaseBaseName, + List tools) { + StringBuilder imports = new StringBuilder(); + StringBuilder fields = new StringBuilder(); + StringBuilder methods = new StringBuilder(); + for (ToolMethodDefinition tool : tools) { + String baseName = toPascalCase(tool.baseName()); + String code = tool.clientSystemCode().toLowerCase(Locale.ROOT); + String clientVariable = Character.toLowerCase(baseName.charAt(0)) + baseName.substring(1) + "Client"; + imports.append("import ").append(bizPackage).append(".dto.").append(baseName).append("Request;\n") + .append("import ").append(bizPackage).append(".dto.").append(baseName).append("Response;\n") + .append("import ").append(BASE_PACKAGE).append(".infra.itrf.mci.").append(code).append(".").append(baseName).append("Client;\n") + .append("import ").append(BASE_PACKAGE).append(".infra.itrf.mci.").append(code).append(".io.").append(baseName).append("_I;\n") + .append("import ").append(BASE_PACKAGE).append(".infra.itrf.mci.").append(code).append(".io.").append(baseName).append("_O;\n"); + fields.append(" private final ").append(baseName).append("Client ").append(clientVariable).append(";\n"); + methods.append(" @Override\n public ").append(baseName).append("Response ").append(tool.methodName()) + .append("(").append(baseName).append("Request req) {\n") + .append(" ").append(baseName).append("_I request = converter.to").append(baseName).append("Request(req);\n") + .append(" ").append(baseName).append("_O response = ").append(clientVariable).append(".call").append(baseName).append("(request);\n") + .append(" ").append(baseName).append("Response toolResponse = converter.to").append(baseName).append("Response(response);\n") + .append(" if (toolResponse == null) toolResponse = new ").append(baseName).append("Response();\n") + .append(" toolResponse.setResultCode(\"SUCCESS\");\n") + .append(" return toolResponse;\n }\n\n"); + } + return "package " + bizPackage + ".usecase.impl;\n\n" + + "import " + bizPackage + ".converter." + useCaseBaseName + "Converter;\n" + + "import " + bizPackage + ".usecase." + useCaseBaseName + "UseCase;\n" + + "import lombok.RequiredArgsConstructor;\nimport org.springframework.stereotype.Service;\n" + imports + + "\n@Service\n@RequiredArgsConstructor\npublic class " + useCaseBaseName + "UseCaseImpl implements " + useCaseBaseName + "UseCase {\n\n" + + " private final " + useCaseBaseName + "Converter converter;\n" + fields + "\n" + methods + "}\n"; + } + + private static String groupedConverterContent(String bizPackage, String useCaseBaseName, + List tools) { + StringBuilder imports = new StringBuilder(); + StringBuilder methods = new StringBuilder(); + for (ToolMethodDefinition tool : tools) { + String baseName = toPascalCase(tool.baseName()); + String code = tool.clientSystemCode().toLowerCase(Locale.ROOT); + imports.append("import ").append(bizPackage).append(".dto.").append(baseName).append("Request;\n") + .append("import ").append(bizPackage).append(".dto.").append(baseName).append("Response;\n") + .append("import ").append(BASE_PACKAGE).append(".infra.itrf.mci.").append(code).append(".io.").append(baseName).append("_I;\n") + .append("import ").append(BASE_PACKAGE).append(".infra.itrf.mci.").append(code).append(".io.").append(baseName).append("_O;\n"); + methods.append(" ").append(baseName).append("_I to").append(baseName).append("Request(").append(baseName).append("Request request);\n") + .append(" ").append(baseName).append("Response to").append(baseName).append("Response(").append(baseName).append("_O response);\n\n"); + } + return "package " + bizPackage + ".converter;\n\nimport org.mapstruct.Mapper;\nimport org.mapstruct.ReportingPolicy;\n" + + imports + "\n@Mapper(componentModel = \"spring\", unmappedTargetPolicy = ReportingPolicy.IGNORE)\n" + + "public interface " + useCaseBaseName + "Converter {\n\n" + methods + "}\n"; + } + + private static String groupedMciClientContent(String ioPackage, String baseName, String interfaceId) { + return "package " + ioPackage + ";\n\n" + + "import io.shinhanlife.dap.lib.integration.mci.component.AxhubMciComponent;\n" + + "import io.shinhanlife.glow.communication.dto.Transfer;\n" + + "import lombok.RequiredArgsConstructor;\nimport org.springframework.stereotype.Component;\n" + + "import " + ioPackage + ".io." + baseName + "_I;\n" + + "import " + ioPackage + ".io." + baseName + "_O;\n\n" + + "@Component\n@RequiredArgsConstructor\npublic class " + baseName + "Client {\n" + + " private final AxhubMciComponent mci;\n\n" + + " public " + baseName + "_O call" + baseName + "(" + baseName + "_I request) {\n" + + " try {\n" + + " Transfer<" + baseName + "_O> transfer = mci.callTo(\"" + javaText(interfaceId) + "\", null, request, " + baseName + "_O.class);\n" + + " return transfer.getBody();\n" + + " } catch (Exception e) {\n" + + " throw new IllegalStateException(\"MCI call failed: " + javaText(interfaceId) + "\", e);\n" + + " }\n }\n}\n"; + } + + private static String javaText(String value) { + return value == null ? "" : value.replace("\\", "\\\\").replace("\"", "\\\"").replace("\r", " ").replace("\n", " "); + } + public static void main(String[] args) throws IOException { Scanner scanner = new Scanner(System.in); @@ -254,6 +465,7 @@ public class ToolScaffolder { .replace("private String message;", "@Schema(example = \"테스트 메시지입니다.\")\n private String message;"); reqContent = dtoContent(bizPackage + ".dto", baseName + "Request", inputFields, author, createDate, true); writeUtf8(dtoDir.resolve(baseName + "Request.java"), reqContent); + writeStructuredFieldTypes(dtoDir, bizPackage + ".dto", baseName + "Request", inputFields); // Generate Response DTO String resContent = """ @@ -288,6 +500,7 @@ public class ToolScaffolder { """.formatted(bizPackage, bizPackage, baseName, author, createDate, createDate, author, baseName); resContent = dtoContent(bizPackage + ".dto", baseName + "Response", outputFields, author, createDate, false); writeUtf8(dtoDir.resolve(baseName + "Response.java"), resContent); + writeStructuredFieldTypes(dtoDir, bizPackage + ".dto", baseName + "Response", outputFields); String toolName = toToolName(moduleName, group, baseName); @@ -551,6 +764,7 @@ public class ToolScaffolder { """.formatted(BASE_PACKAGE, mciGroupPath.replace("/", "."), BASE_PACKAGE, mciGroupPath.replace("/", "."), interfaceId, author, createDate, createDate, author, interfaceId); mciReqContent = mciIoContent(mciGroupPath.replace("/", "."), interfaceId + "_I", inputFields, author, createDate); writeUtf8(mciIoDir.resolve(interfaceId + "_I.java"), mciReqContent); + writeStructuredFieldTypes(mciIoDir, BASE_PACKAGE + "." + mciGroupPath.replace("/", ".") + ".io", interfaceId + "_I", inputFields); String mciResContent = """ package %s.%s.io; @@ -578,6 +792,7 @@ public class ToolScaffolder { """.formatted(BASE_PACKAGE, mciGroupPath.replace("/", "."), BASE_PACKAGE, mciGroupPath.replace("/", "."), interfaceId, author, createDate, createDate, author, interfaceId); mciResContent = mciIoContent(mciGroupPath.replace("/", "."), interfaceId + "_O", outputFields, author, createDate); writeUtf8(mciIoDir.resolve(interfaceId + "_O.java"), mciResContent); + writeStructuredFieldTypes(mciIoDir, BASE_PACKAGE + "." + mciGroupPath.replace("/", ".") + ".io", interfaceId + "_O", outputFields); String converterContent = """ package %s.converter; @@ -692,6 +907,8 @@ public class ToolScaffolder { dtoContent(httpPackage + ".io", httpRequestClass, inputFields, author, createDate, true)); writeUtf8(httpIoDir.resolve(httpResponseClass + ".java"), dtoContent(httpPackage + ".io", httpResponseClass, outputFields, author, createDate, false)); + writeStructuredFieldTypes(httpIoDir, httpPackage + ".io", httpRequestClass, inputFields); + writeStructuredFieldTypes(httpIoDir, httpPackage + ".io", httpResponseClass, outputFields); writeUtf8(httpClientDir.resolve(httpClientClass + ".java"), httpClientContent(httpPackage, httpClientClass, httpApiName)); writeUtf8(converterDir.resolve(baseName + "Converter.java"), @@ -925,9 +1142,7 @@ public class ToolScaffolder { || !generatedNames.add(field.name().trim())) { continue; } - properties.append(" ").append(field.name()).append(":\n") - .append(" type: ").append(jsonSchemaType(field.type())).append("\n") - .append(" description: ").append(yamlText(field.description())).append("\n"); + appendSchemaProperty(properties, field); if (field.required()) { required.append(" - ").append(field.name()).append("\n"); } @@ -996,9 +1211,7 @@ public class ToolScaffolder { || !generatedNames.add(field.name().trim())) { continue; } - properties.append(" ").append(field.name().trim()).append(":\n") - .append(" type: ").append(jsonSchemaType(field.type())).append("\n") - .append(" description: ").append(yamlText(field.description())).append("\n"); + appendSchemaProperty(properties, field); if (field.required()) { required.append(" - ").append(field.name().trim()).append("\n"); } @@ -1070,10 +1283,34 @@ public class ToolScaffolder { case "Integer", "Long" -> "integer"; case "Double", "BigDecimal" -> "number"; case "Boolean" -> "boolean"; + case "List" -> "array"; default -> "string"; }; } + private static void appendSchemaProperty(StringBuilder properties, FieldDefinition field) { + properties.append(" ").append(field.name().trim()).append(":\n") + .append(" type: ").append(jsonSchemaType(field.type())).append("\n") + .append(" description: ").append(yamlText(field.description())).append("\n"); + if ("Enum".equals(field.type()) && field.enumValues() != null && !field.enumValues().isEmpty()) { + properties.append(" enum: [").append(field.enumValues().stream() + .filter(value -> value != null && !value.isBlank()).map(String::trim) + .collect(java.util.stream.Collectors.joining(", "))).append("]\n"); + } + if ("List".equals(field.type())) { + properties.append(" items:\n") + .append(" type: ").append("Object".equals(field.itemType()) ? "object" : jsonSchemaType(field.itemType())).append("\n"); + if ("Object".equals(field.itemType()) && field.itemFields() != null && !field.itemFields().isEmpty()) { + properties.append(" properties:\n"); + for (FieldDefinition itemField : field.itemFields()) { + properties.append(" ").append(itemField.name()).append(":\n") + .append(" type: ").append(jsonSchemaType(itemField.type())).append("\n") + .append(" description: ").append(yamlText(itemField.description())).append("\n"); + } + } + } + } + private static String yamlText(String value) { String safe = value == null ? "" : value.replace("\\", "\\\\").replace("\"", "\\\"") .replace("\r", " ").replace("\n", " "); @@ -1143,36 +1380,110 @@ public class ToolScaffolder { private static String dtoContent(String packageName, String className, List fields, String author, String createDate, boolean request) { - String body = fieldLines(fields, request ? Set.of() : Set.of("resultCode", "resultMessage")); + String body = fieldLines(fields, request ? Set.of() : Set.of("resultCode", "resultMessage"), className); if (!request) { body = " private String resultCode;\n\n private String resultMessage;\n" + body; } + String listImport = hasListField(fields) ? "import java.util.List;\n" : ""; return """ package %s; import com.fasterxml.jackson.annotation.JsonInclude; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; + %s @Data @JsonInclude(JsonInclude.Include.NON_NULL) public class %s { %s} - """.formatted(packageName, className, body); + """.formatted(packageName, listImport, className, body); } private static String mciIoContent(String packageSuffix, String className, List fields, String author, String createDate) { + String listImport = hasListField(fields) ? "import java.util.List;\n" : ""; return """ package %s.%s.io; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; + %s @Data public class %s { %s} - """.formatted(BASE_PACKAGE, packageSuffix, className, fieldLines(fields)); + """.formatted(BASE_PACKAGE, packageSuffix, listImport, className, fieldLines(fields, Set.of(), className)); + } + + private static boolean hasListField(List fields) { + return fields != null && fields.stream().anyMatch(field -> field != null && "List".equals(field.type())); + } + + private static void writeStructuredFieldTypes(Path directory, String packageName, String ownerClass, + List fields) throws IOException { + for (FieldDefinition field : fields == null ? List.of() : fields) { + if (field == null || field.name() == null || field.name().isBlank()) { + continue; + } + if ("Enum".equals(field.type())) { + String enumName = toPascalCase(field.name()); + List values = field.enumValues() == null ? List.of() : field.enumValues().stream() + .filter(value -> value != null && !value.isBlank()).map(String::trim).distinct().toList(); + if (values.isEmpty()) { + throw new IllegalArgumentException("Enum field needs at least one allowed value: " + field.name()); + } + String constants = values.stream().map(value -> " " + enumConstant(value) + "(\"" + javaText(value) + "\")") + .collect(java.util.stream.Collectors.joining(",\n")); + writeUtf8(directory.resolve(enumName + ".java"), """ + package %s; + + import com.fasterxml.jackson.annotation.JsonCreator; + import com.fasterxml.jackson.annotation.JsonValue; + + public enum %s { + %s; + + private final String value; + + %s(String value) { + this.value = value; + } + + @JsonValue + public String getValue() { + return value; + } + + @JsonCreator + public static %s fromValue(String value) { + for (%s candidate : values()) { + if (candidate.value.equals(value)) return candidate; + } + throw new IllegalArgumentException("Unsupported value: " + value); + } + } + """.formatted(packageName, enumName, constants, enumName, enumName, enumName)); + } + if ("List".equals(field.type()) && "Object".equals(field.itemType())) { + List itemFields = field.itemFields() == null ? List.of() : field.itemFields(); + if (itemFields.isEmpty()) { + throw new IllegalArgumentException("Object List field needs item fields: " + field.name()); + } + String itemName = listItemClassName(ownerClass, field); + writeUtf8(directory.resolve(itemName + ".java"), dtoContent(packageName, itemName, itemFields, "", "", true)); + writeStructuredFieldTypes(directory, packageName, itemName, itemFields); + } + } + } + + private static String enumConstant(String value) { + String constant = value.toUpperCase(Locale.ROOT).replaceAll("[^A-Z0-9]+", "_").replaceAll("^_+|_+$", ""); + return constant.isBlank() ? "VALUE" : (Character.isDigit(constant.charAt(0)) ? "VALUE_" + constant : constant); + } + + private static String listItemClassName(String ownerClass, FieldDefinition field) { + return ownerClass + toPascalCase(field.name()) + "Item"; } private static String httpUseCaseImplContent(String bizPackage, String baseName, String httpPackage, @@ -1334,10 +1645,14 @@ public class ToolScaffolder { baseName, baseName, baseName, baseName, baseName, baseName, baseName); } private static String fieldLines(List fields) { - return fieldLines(fields, Set.of()); + return fieldLines(fields, Set.of(), ""); } private static String fieldLines(List fields, Set excludedNames) { + return fieldLines(fields, excludedNames, ""); + } + + private static String fieldLines(List fields, Set excludedNames, String ownerClass) { StringBuilder source = new StringBuilder(); Set generatedNames = new LinkedHashSet<>(); for (FieldDefinition field : fields == null ? List.of() : fields) { @@ -1348,7 +1663,7 @@ public class ToolScaffolder { if (excludedNames.contains(fieldName) || !generatedNames.add(fieldName)) { continue; } - String type = supportedType(field.type()); + String type = javaFieldType(field, ownerClass); String description = field.description() == null ? "" : field.description().replace("\"", "\\\""); String example = field.example() == null ? "" : field.example().replace("\"", "\\\""); source.append(" @Schema(description = \"").append(description).append("\", example = \"") @@ -1360,6 +1675,22 @@ public class ToolScaffolder { } return source.toString(); } + + private static String javaFieldType(FieldDefinition field, String ownerClass) { + return switch (field.type() == null ? "String" : field.type()) { + case "Enum" -> toPascalCase(field.name()); + case "List" -> "List<" + listItemJavaType(field, ownerClass) + ">"; + default -> supportedType(field.type()); + }; + } + + private static String listItemJavaType(FieldDefinition field, String ownerClass) { + String itemType = field.itemType() == null ? "" : field.itemType(); + if ("Object".equals(itemType)) { + return listItemClassName(ownerClass, field); + } + return supportedType(itemType); + } private static String supportedType(String type) { return switch (type == null ? "String" : type) { case "String", "Integer", "Long", "Double", "Boolean", "BigDecimal" -> type; @@ -1404,9 +1735,26 @@ public class ToolScaffolder { } private static String mockValue(FieldDefinition field) { + if ("List".equals(field.type())) { + if ("Object".equals(field.itemType())) { + StringBuilder object = new StringBuilder("{"); + boolean first = true; + for (FieldDefinition itemField : field.itemFields() == null ? List.of() : field.itemFields()) { + if (!first) object.append(", "); + object.append("\"").append(jsonEscape(itemField.name())).append("\" : ").append(mockValue(itemField)); + first = false; + } + return "[" + object + "]"; + } + FieldDefinition item = new FieldDefinition("item", field.itemType(), "", field.example(), false); + return "[" + mockValue(item) + "]"; + } if (field.example() == null || field.example().isBlank()) { return "null"; } + if ("Enum".equals(field.type())) { + return "\"" + jsonEscape(field.example()) + "\""; + } return switch (supportedType(field.type())) { case "Integer", "Long", "Double", "BigDecimal" -> field.example(); case "Boolean" -> Boolean.parseBoolean(field.example()) ? "true" : "false"; 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 6a48c94f..e1ab620d 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 @@ -7,6 +7,7 @@ import io.shinhanlife.dap.lib.metadata.ToolDefinitionValidator; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import java.nio.file.Files; import java.nio.file.Path; @@ -17,6 +18,70 @@ import org.junit.jupiter.api.io.TempDir; class ToolScaffolderTest { + @Test + void exposesGroupedUseCaseScaffoldApi() { + assertDoesNotThrow(() -> ToolScaffolder.class.getMethod( + "scaffoldUseCase", String.class, String.class, String.class, String.class, List.class)); + } + + @Test + void generatesOneUseCaseWithTwoMcpToolMethodsAndTypedClients() throws Exception { + String moduleName = root.resolve("dap-was-customer").toString(); + ToolScaffolder.scaffoldUseCase("Customer", moduleName, "tester", "2026.08.12", List.of( + new ToolScaffolder.ToolMethodDefinition( + "CustomerGuidance", "searchGuidance", "CTMNILO00007", "Customer guidance", "Search guidance", "cmm", "MCI", + false, "NILD", null, + List.of(new ToolScaffolder.FieldDefinition("customerId", "String", "Customer ID", "C001", true)), + List.of(new ToolScaffolder.FieldDefinition("guidanceStatus", "String", "Guidance status", "OPEN", false)), null), + new ToolScaffolder.ToolMethodDefinition( + "CustomerContract", "searchContract", "CTMCNT00001", "Customer contract", "Search contract", "cmm", "MCI", + false, "CNTD", null, + List.of(new ToolScaffolder.FieldDefinition("customerId", "String", "Customer ID", "C001", true)), + List.of(new ToolScaffolder.FieldDefinition("contractStatus", "String", "Contract status", "ACTIVE", false)), null))); + + Path sourceRoot = root.resolve("dap-was-customer/src/main/java/io/shinhanlife/dap/mcc"); + String useCase = Files.readString(sourceRoot.resolve("biz/cmm/usecase/CustomerUseCase.java")); + String implementation = Files.readString(sourceRoot.resolve("biz/cmm/usecase/impl/CustomerUseCaseImpl.java")); + String guidanceClient = Files.readString(sourceRoot.resolve("infra/itrf/mci/nild/CustomerGuidanceClient.java")); + + assertTrue(useCase.contains("CustomerGuidanceResponse searchGuidance(CustomerGuidanceRequest req)"), useCase); + assertTrue(useCase.contains("CustomerContractResponse searchContract(CustomerContractRequest req)"), useCase); + assertTrue(implementation.contains("private final CustomerGuidanceClient customerGuidanceClient;"), implementation); + assertTrue(implementation.contains("customerGuidanceClient.callCustomerGuidance(request)"), implementation); + assertTrue(guidanceClient.contains("CustomerGuidance_O callCustomerGuidance(CustomerGuidance_I request)"), guidanceClient); + } + + @Test + void generatesEnumAndListFieldsInDtoSchemaAndMockResponse() throws Exception { + String moduleName = root.resolve("dap-was-claim").toString(); + List inputFields = List.of( + new ToolScaffolder.FieldDefinition("claimStatus", "Enum", "Claim status", "OPEN", true, + List.of("OPEN", "CLOSED"), null, List.of()), + new ToolScaffolder.FieldDefinition("customerIds", "List", "Customer IDs", "C001", false, + List.of(), "String", List.of())); + List outputFields = List.of( + new ToolScaffolder.FieldDefinition("guidanceItems", "List", "Guidance items", "", false, + List.of(), "Object", List.of(new ToolScaffolder.FieldDefinition("status", "String", "Status", "OPEN", true)))); + + ToolScaffolder.scaffold("claim search", "CLM0001", "Claim search", "cmm", "MCI", moduleName, + "tester", "2026.08.12", false, "CLM1", null, null, inputFields, outputFields); + + Path dtoRoot = root.resolve("dap-was-claim/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto"); + String request = Files.readString(dtoRoot.resolve("ClaimSearchRequest.java")); + String response = Files.readString(dtoRoot.resolve("ClaimSearchResponse.java")); + String definition = Files.readString(root.resolve("dap-was-claim/src/main/resources/tool-definitions/cmm/cmm_claim_search.yml")); + String mock = Files.readString(root.resolve("dap-was-claim/src/main/resources/mock-responses/cmm_claim_search.json")); + + assertTrue(request.contains("private ClaimStatus claimStatus;"), request); + assertTrue(request.contains("private List customerIds;"), request); + assertTrue(Files.exists(dtoRoot.resolve("ClaimStatus.java"))); + assertTrue(response.contains("private List guidanceItems;"), response); + assertTrue(Files.exists(dtoRoot.resolve("ClaimSearchResponseGuidanceItemsItem.java"))); + assertTrue(definition.contains("enum: [OPEN, CLOSED]"), definition); + assertTrue(definition.contains("type: array"), definition); + assertTrue(mock.contains("\"guidanceItems\" : [{"), mock); + } + @Test void generatesEveryToolSourceAsUtf8WithoutBrokenKoreanOrBom() throws Exception { String moduleName = root.resolve("dap-was-korean").toString(); diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/CustomerGuidanceToolUseCase.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/CustomerGuidanceToolUseCase.java index 362c12fa..b3d2ba6f 100644 --- a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/CustomerGuidanceToolUseCase.java +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/CustomerGuidanceToolUseCase.java @@ -26,5 +26,5 @@ public interface CustomerGuidanceToolUseCase { @McpTool(name = "cmm_customer_tool", title = "고객 통합 안내이력 조회", description = "고객의 통합 안내 이력을 조회하는 도구입니다. 고객 ID와 조회 기간을 입력하면 해당 기간 동안의 안내 이력을 반환합니다.") @ToolHint(register = false, categoryKey = "cmm", mappingId = "ONILD0320") - CustomerGuidanceToolResponse execute(CustomerGuidanceToolRequest req) throws Exception; + CustomerGuidanceToolResponse searchCustomerGuidance(CustomerGuidanceToolRequest req) throws Exception; } diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/MetaCommonCodeUseCase.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/MetaCommonCodeUseCase.java index 9e9823f6..60f42783 100644 --- a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/MetaCommonCodeUseCase.java +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/MetaCommonCodeUseCase.java @@ -22,5 +22,5 @@ import io.shinhanlife.dap.mcc.biz.cmm.dto.MetaCommonCodeRequest; public interface MetaCommonCodeUseCase { @McpTool(name = "cmm_comcode_lookup", title = "메타 공통코드 조회 툴", description = "메타 통합코드 목록을 조회해줘", annotations = @McpTool.McpAnnotations(openWorldHint = true)) @ToolHint(register = false, requiresApproval = false, categoryKey = "cmm", mappingId = "CLCNNB00001") - Object execute(MetaCommonCodeRequest req); + Object searchCommonCode(MetaCommonCodeRequest req); } diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/MetaTableUseCase.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/MetaTableUseCase.java index 6fe6c90f..2de94d99 100644 --- a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/MetaTableUseCase.java +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/MetaTableUseCase.java @@ -22,5 +22,5 @@ import io.shinhanlife.dap.mcc.biz.cmm.dto.MetaTableRequest; public interface MetaTableUseCase { @McpTool(name = "cmm_meta_table", title = "메타 테이블 조회 툴", description = "메타 테이블 정보 목록을 조회해줘", annotations = @McpTool.McpAnnotations(openWorldHint = true)) @ToolHint(register = false, requiresApproval = false, categoryKey = "cmm", mappingId = "CLCNNB00001") - Object execute(MetaTableRequest req); + Object searchMetaTable(MetaTableRequest req); } diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/impl/CustomerGuidanceToolUseCaseImpl.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/impl/CustomerGuidanceToolUseCaseImpl.java index a5e0e07a..688d406f 100644 --- a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/impl/CustomerGuidanceToolUseCaseImpl.java +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/impl/CustomerGuidanceToolUseCaseImpl.java @@ -43,7 +43,7 @@ public class CustomerGuidanceToolUseCaseImpl implements CustomerGuidanceToolUseC private final CustomerGuidanceToolConverter converter; @Override - public CustomerGuidanceToolResponse execute(CustomerGuidanceToolRequest req) throws Exception { + public CustomerGuidanceToolResponse searchCustomerGuidance(CustomerGuidanceToolRequest req) throws Exception { ONILD0320_I onild0320_i = converter.toONILD0320_I(req); ONILD0320_O onild0320_o = mci.callOnild0320(onild0320_i); diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/impl/MetaCommonCodeUseCaseImpl.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/impl/MetaCommonCodeUseCaseImpl.java index 92ddf781..5aa0bbba 100644 --- a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/impl/MetaCommonCodeUseCaseImpl.java +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/impl/MetaCommonCodeUseCaseImpl.java @@ -38,7 +38,7 @@ public class MetaCommonCodeUseCaseImpl implements MetaCommonCodeUseCase { private final MetaCommonCodeConverter converter; @Override - public Object execute(MetaCommonCodeRequest req) { + public Object searchCommonCode(MetaCommonCodeRequest req) { log.info("[MCI Tool] {} 요청 수신. 파라미터: {}", "metaCommonCode", req); try { CLCNNB00001_I mciRequest = converter.toLegacyRequest(req); diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/impl/MetaTableUseCaseImpl.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/impl/MetaTableUseCaseImpl.java index ef981898..9b45776e 100644 --- a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/impl/MetaTableUseCaseImpl.java +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/impl/MetaTableUseCaseImpl.java @@ -38,7 +38,7 @@ public class MetaTableUseCaseImpl implements MetaTableUseCase { private final MetaTableConverter converter; @Override - public Object execute(MetaTableRequest req) { + public Object searchMetaTable(MetaTableRequest req) { log.info("[MCI Tool] {} 요청 수신. 파라미터: {}", "metaTable", req); try { CLCNNB00001_I mciRequest = converter.toLegacyRequest(req); diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/ins/usecase/InsuranceClaimProcessorUseCase.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/ins/usecase/InsuranceClaimProcessorUseCase.java index e061235d..1eaffba3 100644 --- a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/ins/usecase/InsuranceClaimProcessorUseCase.java +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/ins/usecase/InsuranceClaimProcessorUseCase.java @@ -23,5 +23,5 @@ public interface InsuranceClaimProcessorUseCase { @McpTool(name = "ins_insurance_processor", title = "보험금 청구", description = "보험금 청구 요청을 처리하고 결과를 반환하는 LLM 도구 가이드") @ToolHint(register = false, categoryKey = "ins", mappingId = "CLAIM0000001") - InsuranceClaimProcessorResponse execute(InsuranceClaimProcessorRequest req); + InsuranceClaimProcessorResponse processInsuranceClaim(InsuranceClaimProcessorRequest req); } diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/ins/usecase/impl/InsuranceClaimProcessorUseCaseImpl.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/ins/usecase/impl/InsuranceClaimProcessorUseCaseImpl.java index e18e2cc2..7003480b 100644 --- a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/ins/usecase/impl/InsuranceClaimProcessorUseCaseImpl.java +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/ins/usecase/impl/InsuranceClaimProcessorUseCaseImpl.java @@ -18,7 +18,7 @@ public class InsuranceClaimProcessorUseCaseImpl implements InsuranceClaimProcess private final InsuranceClient insuranceClient; @Override - public InsuranceClaimProcessorResponse execute(InsuranceClaimProcessorRequest req) { + public InsuranceClaimProcessorResponse processInsuranceClaim(InsuranceClaimProcessorRequest req) { InsuranceClaimProcessorHttpRequest httpRequest = converter.toHttpRequest(req); InsuranceClaimProcessorHttpResponse httpResponse = insuranceClient.call(httpRequest, InsuranceClaimProcessorHttpResponse.class); diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/oth/usecase/Onnba3011UseCase.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/oth/usecase/Onnba3011UseCase.java index 1e88db13..b33a6619 100644 --- a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/oth/usecase/Onnba3011UseCase.java +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/oth/usecase/Onnba3011UseCase.java @@ -7,5 +7,5 @@ import io.shinhanlife.dap.mcc.biz.oth.dto.*; public interface Onnba3011UseCase { @McpTool(name = "oth_onnba3011_call", description = "Onnba3011 호출 툴") @ToolHint(categoryKey = "oth", register = false) - Object execute(Onnba3011Request req); + Object callOnnba3011(Onnba3011Request req); } diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/oth/usecase/impl/Onnba3011UseCaseImpl.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/oth/usecase/impl/Onnba3011UseCaseImpl.java index bf8e2138..2bd62e68 100644 --- a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/oth/usecase/impl/Onnba3011UseCaseImpl.java +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/oth/usecase/impl/Onnba3011UseCaseImpl.java @@ -37,7 +37,7 @@ public class Onnba3011UseCaseImpl implements Onnba3011UseCase { * AI Agent가 호출하게 될 메서드입니다. */ @Override - public Object execute(Onnba3011Request req) { + public Object callOnnba3011(Onnba3011Request req) { log.info("[MCI Tool] 보종By가입설계한도계산조회 요청 수신."); try { diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/DailyQuoteToolUseCase.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/DailyQuoteToolUseCase.java index 6c5fe77a..6182da8b 100644 --- a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/DailyQuoteToolUseCase.java +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/DailyQuoteToolUseCase.java @@ -9,5 +9,5 @@ import io.shinhanlife.dap.mcc.biz.smp.dto.DailyQuoteResponse; public interface DailyQuoteToolUseCase { @McpTool(name = "smp_quote_daily", title = "오늘의 명언 툴", description = "무작위로 영감을 주는 명언을 하나 가져옵니다.") @ToolHint(register = false, categoryKey = "smp", mappingId = "QUOTE_001") - DailyQuoteResponse execute(DailyQuoteRequest req); + DailyQuoteResponse getDailyQuote(DailyQuoteRequest req); } diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/ExchangeRateToolUseCase.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/ExchangeRateToolUseCase.java index 67861184..36cabb91 100644 --- a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/ExchangeRateToolUseCase.java +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/ExchangeRateToolUseCase.java @@ -11,5 +11,5 @@ import io.shinhanlife.dap.mcc.biz.smp.dto.ExchangeRateResponse; public interface ExchangeRateToolUseCase { @McpTool(name = "smp_exchange_inquiry", title = "실시간 환율 조회 툴", description = "원하는 통화의 실시간 환율을 조회합니다. (예: USD, EUR, JPY)") @ToolHint(register = false, categoryKey = "smp", mappingId = "EXCHANGE_001") - ExchangeRateResponse execute(ExchangeRateRequest req); + ExchangeRateResponse getExchangeRate(ExchangeRateRequest req); } diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/TeamMemberUseCase.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/TeamMemberUseCase.java index b947f7e7..41a46e3c 100644 --- a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/TeamMemberUseCase.java +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/TeamMemberUseCase.java @@ -8,5 +8,5 @@ import io.shinhanlife.dap.mcc.biz.smp.dto.TeamMemberRequest; public interface TeamMemberUseCase { @McpTool(name = "smp_team_list", title = "신한라이프 MCP, TOOL 파트 구성원 조회", description = "신한라이프 MCP, TOOL 파트 구성원을 조회합니다.", annotations = @McpTool.McpAnnotations(openWorldHint = true)) @ToolHint(register = false, requiresApproval = false, categoryKey = "smp", mappingId = "DIRECT0001") - Object execute(TeamMemberRequest req); + Object getTeamMember(TeamMemberRequest req); } diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/WeatherToolUseCase.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/WeatherToolUseCase.java index b960ade9..e8b42ca4 100644 --- a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/WeatherToolUseCase.java +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/WeatherToolUseCase.java @@ -8,5 +8,5 @@ import io.shinhanlife.dap.mcc.biz.smp.dto.*; public interface WeatherToolUseCase { @McpTool(name = "smp_weather_inquiry", title = "날씨 조회 툴", description = "특정 도시의 현재 날씨, 온도, 풍속 정보를 조회합니다.") @ToolHint(register = false, categoryKey = "smp", mappingId = "WEATHER_001") - WeatherResponse execute(WeatherRequest req); + WeatherResponse getWeather(WeatherRequest req); } diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/impl/DailyQuoteToolUseCaseImpl.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/impl/DailyQuoteToolUseCaseImpl.java index ed9f04ff..8e297347 100644 --- a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/impl/DailyQuoteToolUseCaseImpl.java +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/impl/DailyQuoteToolUseCaseImpl.java @@ -35,7 +35,7 @@ public class DailyQuoteToolUseCaseImpl implements DailyQuoteToolUseCase { ); @Override - public DailyQuoteResponse execute(DailyQuoteRequest req) { + public DailyQuoteResponse getDailyQuote(DailyQuoteRequest req) { int index = new Random().nextInt(quotes.size()); DailyQuoteResponse selected = quotes.get(index); diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/impl/ExchangeRateToolUseCaseImpl.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/impl/ExchangeRateToolUseCaseImpl.java index 029d47e5..9a121d17 100644 --- a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/impl/ExchangeRateToolUseCaseImpl.java +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/impl/ExchangeRateToolUseCaseImpl.java @@ -32,7 +32,7 @@ public class ExchangeRateToolUseCaseImpl implements ExchangeRateToolUseCase { } @Override - public ExchangeRateResponse execute(ExchangeRateRequest req) { + public ExchangeRateResponse getExchangeRate(ExchangeRateRequest req) { String targetCurrency = req.getCurrencyCode() != null ? req.getCurrencyCode().toUpperCase().trim() : "USD"; // 간단한 모의 데이터로 반환 (실제 구현 시 외부 연동) diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/impl/TeamMemberUseCaseImpl.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/impl/TeamMemberUseCaseImpl.java index c7927e2b..ecf0e273 100644 --- a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/impl/TeamMemberUseCaseImpl.java +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/impl/TeamMemberUseCaseImpl.java @@ -27,7 +27,7 @@ import lombok.extern.slf4j.Slf4j; public class TeamMemberUseCaseImpl implements TeamMemberUseCase { @Override - public Object execute(TeamMemberRequest req) { + public Object getTeamMember(TeamMemberRequest req) { log.info("[A01] 신한라이프 MCP, TOOL 파트 구성원 조회 요청: {}", req); String filter = req != null && req.getTeamName() != null ? req.getTeamName().toUpperCase() : "전체"; diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/impl/WeatherToolUseCaseImpl.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/impl/WeatherToolUseCaseImpl.java index 9cd244fc..820f1bf4 100644 --- a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/impl/WeatherToolUseCaseImpl.java +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/impl/WeatherToolUseCaseImpl.java @@ -35,7 +35,7 @@ public class WeatherToolUseCaseImpl implements WeatherToolUseCase { public WeatherToolUseCaseImpl() { this.restClient = RestClient.create(); } - public WeatherResponse execute(WeatherRequest req) { + public WeatherResponse getWeather(WeatherRequest req) { String city = req.city() != null ? req.city().trim() : "서울"; // 지역별 위경도 매핑 (간단한 예시) diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/usecase/SolReqDetailUseCase.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/usecase/SolReqDetailUseCase.java index ef8521ea..66966c3f 100644 --- a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/usecase/SolReqDetailUseCase.java +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/usecase/SolReqDetailUseCase.java @@ -23,5 +23,5 @@ public interface SolReqDetailUseCase { @McpTool(name = "sol_request_detail", title = "SolReqDetail 툴", description = "SOL 의뢰서 상세 조회", annotations = @McpTool.McpAnnotations(openWorldHint = true, readOnlyHint = true)) @ToolHint(register = false, requiresApproval = false, categoryKey = "sol", mappingId = "SOLG00000002") - Object execute(SolReqDetailRequest req); + Object getSolRequestDetail(SolReqDetailRequest req); } diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/usecase/SolReqListUseCase.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/usecase/SolReqListUseCase.java index f82ebe8d..62378a0d 100644 --- a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/usecase/SolReqListUseCase.java +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/usecase/SolReqListUseCase.java @@ -8,5 +8,5 @@ import io.shinhanlife.dap.mcc.biz.sol.dto.SolReqListRequest; public interface SolReqListUseCase { @McpTool(name = "sol_request_list", title = "SolReqList 툴", description = "SOL 의뢰서 목록 조회해줘", annotations = @McpTool.McpAnnotations(openWorldHint = true)) @ToolHint(register = false, requiresApproval = false, categoryKey = "sol", mappingId = "SOLG00000001") - Object execute(SolReqListRequest req); + Object searchSolRequests(SolReqListRequest req); } diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/usecase/impl/SolReqDetailUseCaseImpl.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/usecase/impl/SolReqDetailUseCaseImpl.java index be197500..9d0e5457 100644 --- a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/usecase/impl/SolReqDetailUseCaseImpl.java +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/usecase/impl/SolReqDetailUseCaseImpl.java @@ -40,7 +40,7 @@ public class SolReqDetailUseCaseImpl implements SolReqDetailUseCase { private boolean mockEnabled; @Override - public Object execute(SolReqDetailRequest req) { + public Object getSolRequestDetail(SolReqDetailRequest req) { log.info("[MCI Tool] {} 요청 수신. 파라미터: {}", "solReqDetail", req); if (req == null || req.getSrId() == null || req.getSrId().isBlank()) { return Map.of("status", "ERROR", "message", "srId는 필수입니다."); diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/usecase/impl/SolReqListUseCaseImpl.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/usecase/impl/SolReqListUseCaseImpl.java index 35ea868e..441d21e1 100644 --- a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/usecase/impl/SolReqListUseCaseImpl.java +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/usecase/impl/SolReqListUseCaseImpl.java @@ -39,7 +39,7 @@ public class SolReqListUseCaseImpl implements SolReqListUseCase { private final SolReqListConverter converter; @Override - public Object execute(SolReqListRequest req) { + public Object searchSolRequests(SolReqListRequest req) { log.info("[MCI Tool] {} 요청 수신. 파라미터: {}", "solReqList", req); try { SOLG00000001_I mciRequest = converter.toLegacyRequest(req); diff --git a/dap-was-oth/src/test/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/CustomerGuidanceToolUseCaseTest.java b/dap-was-oth/src/test/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/CustomerGuidanceToolUseCaseTest.java index 4e4b23e7..6e7929f2 100644 --- a/dap-was-oth/src/test/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/CustomerGuidanceToolUseCaseTest.java +++ b/dap-was-oth/src/test/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/CustomerGuidanceToolUseCaseTest.java @@ -10,7 +10,7 @@ class CustomerGuidanceToolUseCaseTest { @Test void createsToolRequestAndResponseDtos() { - assertNotNull(new CustomerGuidanceToolRequest()); - assertNotNull(new CustomerGuidanceToolResponse()); + assertNotNull(CustomerGuidanceToolRequest.builder().build()); + assertNotNull(CustomerGuidanceToolResponse.builder().build()); } } diff --git a/dap-was-oth/src/test/java/io/shinhanlife/dap/mcc/biz/oth/usecase/impl/Onnba3011UseCaseImplTest.java b/dap-was-oth/src/test/java/io/shinhanlife/dap/mcc/biz/oth/usecase/impl/Onnba3011UseCaseImplTest.java index 5fbb51df..cc909c81 100644 --- a/dap-was-oth/src/test/java/io/shinhanlife/dap/mcc/biz/oth/usecase/impl/Onnba3011UseCaseImplTest.java +++ b/dap-was-oth/src/test/java/io/shinhanlife/dap/mcc/biz/oth/usecase/impl/Onnba3011UseCaseImplTest.java @@ -37,7 +37,7 @@ class Onnba3011UseCaseImplTest { when(converter.toMciRequest(request)).thenReturn(mciRequest); when(mciCfpaClient.callCfpa0001(mciRequest)).thenReturn("success"); - Object result = useCase.execute(request); + Object result = useCase.callOnnba3011(request); assertEquals("success", result); verify(converter).toMciRequest(request); diff --git a/dap-was-oth/src/test/java/io/shinhanlife/dap/mcc/biz/sol/usecase/impl/SolReqDetailUseCaseImplTest.java b/dap-was-oth/src/test/java/io/shinhanlife/dap/mcc/biz/sol/usecase/impl/SolReqDetailUseCaseImplTest.java index e2b245f0..e22e7d8c 100644 --- a/dap-was-oth/src/test/java/io/shinhanlife/dap/mcc/biz/sol/usecase/impl/SolReqDetailUseCaseImplTest.java +++ b/dap-was-oth/src/test/java/io/shinhanlife/dap/mcc/biz/sol/usecase/impl/SolReqDetailUseCaseImplTest.java @@ -36,7 +36,7 @@ class SolReqDetailUseCaseImplTest { SolReqDetailRequest request = new SolReqDetailRequest(); request.setSrId("SR-2026-001"); - SolReqDetailResponse response = (SolReqDetailResponse) useCase.execute(request); + SolReqDetailResponse response = (SolReqDetailResponse) useCase.getSolRequestDetail(request); assertThat(response.getSrId()).isEqualTo("SR-2026-001"); assertThat(response.getSrName()).isEqualTo("AX HUB 메인 화면 UI 개편"); diff --git a/dap-was-oth/src/test/java/io/shinhanlife/dap/mcc/biz/sol/usecase/impl/SolReqListUseCaseImplTest.java b/dap-was-oth/src/test/java/io/shinhanlife/dap/mcc/biz/sol/usecase/impl/SolReqListUseCaseImplTest.java index 26ef60f7..97465122 100644 --- a/dap-was-oth/src/test/java/io/shinhanlife/dap/mcc/biz/sol/usecase/impl/SolReqListUseCaseImplTest.java +++ b/dap-was-oth/src/test/java/io/shinhanlife/dap/mcc/biz/sol/usecase/impl/SolReqListUseCaseImplTest.java @@ -29,7 +29,7 @@ class SolReqListUseCaseImplTest { when(mci.callTo(eq("SOLG00000001"), eq("SOLG00000001"), eq(mciRequest), eq(SOLG00000001_O.class))) .thenReturn(new Transfer<>()); - SolReqListResponse response = (SolReqListResponse) useCase.execute(request); + SolReqListResponse response = (SolReqListResponse) useCase.searchSolRequests(request); verify(converter).toLegacyRequest(request); verify(mci).callTo("SOLG00000001", "SOLG00000001", mciRequest, SOLG00000001_O.class); diff --git a/dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/ClaimSearchUseCase.java b/dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/ClaimSearchUseCase.java index e9e02557..76dbed35 100644 --- a/dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/ClaimSearchUseCase.java +++ b/dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/ClaimSearchUseCase.java @@ -25,5 +25,5 @@ public interface ClaimSearchUseCase { @ToolHint(register = false, categoryKey = "cmm", mappingId = "CLCNNB00001", inputSchemaResource = "classpath:tool-schemas/cmm/claim-search-resource-input-schema.json", outputSchemaResource = "classpath:tool-schemas/cmm/claim-search-resource-output-schema.json") - ClaimSearchResponse execute(ClaimSearchRequest req); + ClaimSearchResponse searchClaim(ClaimSearchRequest req); } diff --git a/dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/MemoListRetrieverUseCase.java b/dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/MemoListRetrieverUseCase.java index 47a74a93..65fb36fc 100644 --- a/dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/MemoListRetrieverUseCase.java +++ b/dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/MemoListRetrieverUseCase.java @@ -23,5 +23,5 @@ public interface MemoListRetrieverUseCase { @McpTool(name = "cmm_memo_retriever", title = "의뢰서 목록 조회", description = "의뢰서 목록을 조회하여 의뢰 정보를 반환합니다.") @ToolHint(register = false, categoryKey = "cmm", mappingId = "MEMO0000001") - MemoListRetrieverResponse execute(MemoListRetrieverRequest req); + MemoListRetrieverResponse retrieveMemoList(MemoListRetrieverRequest req); } diff --git a/dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/impl/ClaimSearchUseCaseImpl.java b/dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/impl/ClaimSearchUseCaseImpl.java index b6dfd97f..675c32e3 100644 --- a/dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/impl/ClaimSearchUseCaseImpl.java +++ b/dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/impl/ClaimSearchUseCaseImpl.java @@ -36,7 +36,7 @@ public class ClaimSearchUseCaseImpl implements ClaimSearchUseCase { private final ClaimSearchConverter converter; @Override - public ClaimSearchResponse execute(ClaimSearchRequest req) { + public ClaimSearchResponse searchClaim(ClaimSearchRequest req) { log.info("[MCI Tool] {} 요청 수신.", "cmm_claim_search"); try { // MapStruct를 이용한 자동 매핑 (AI DTO -> MCI DTO) diff --git a/dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/impl/MemoListRetrieverUseCaseImpl.java b/dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/impl/MemoListRetrieverUseCaseImpl.java index 474832e5..7fa29b46 100644 --- a/dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/impl/MemoListRetrieverUseCaseImpl.java +++ b/dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/impl/MemoListRetrieverUseCaseImpl.java @@ -18,7 +18,7 @@ public class MemoListRetrieverUseCaseImpl implements MemoListRetrieverUseCase { private final MemoClient memoClient; @Override - public MemoListRetrieverResponse execute(MemoListRetrieverRequest req) { + public MemoListRetrieverResponse retrieveMemoList(MemoListRetrieverRequest req) { MemoListRetrieverHttpRequest httpRequest = converter.toHttpRequest(req); MemoListRetrieverHttpResponse httpResponse = memoClient.call(httpRequest, MemoListRetrieverHttpResponse.class);