feat: add scaffold HTTP mock support
Some checks failed
Deploy to OCIWP / deploy (push) Has been cancelled

This commit is contained in:
jade
2026-08-11 23:12:30 +09:00
parent 45e4332adb
commit 0e27937687
18 changed files with 347 additions and 5 deletions

View File

@@ -0,0 +1,61 @@
package io.shinhanlife.dap.lib.adapter.test;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.Map;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.core.io.ClassPathResource;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* Local HTTP mock server for scaffolded HTTP Tools.
*
* <p>Each Tool Pod returns the JSON generated under
* {@code src/main/resources/mock-responses/{toolName}.json}. It is enabled only
* when {@code axhub.mock.http.enabled=true}, which the HTTP Scaffold adds to local configuration.</p>
*/
@Slf4j
@RestController
@RequiredArgsConstructor
@ConditionalOnProperty(prefix = "axhub.mock.http", name = "enabled", havingValue = "true")
@RequestMapping("/api")
public class MockEimsHttpServer {
private final ObjectMapper objectMapper;
@PostMapping("/mock/http/{toolName:[a-z0-9_-]+}")
public ResponseEntity<JsonNode> mockToolHttpResponse(
@PathVariable String toolName,
@RequestBody(required = false) JsonNode request) {
ClassPathResource resource = new ClassPathResource("mock-responses/" + toolName + ".json");
if (!resource.exists()) {
return ResponseEntity.notFound().build();
}
try {
log.info("[MockEimsHttpServer] HTTP mock request. toolName={}, body={}", toolName, request);
return ResponseEntity.ok(objectMapper.readTree(resource.getInputStream()));
} catch (Exception e) {
log.warn("[MockEimsHttpServer] Unable to read mock response. toolName={}", toolName, e);
return ResponseEntity.internalServerError().build();
}
}
@PostMapping("/gateway")
public ResponseEntity<?> mockEimsReceiver(
@RequestHeader(value = "X-Trace-Id", required = false) String traceId,
@RequestBody Map<String, Object> request) {
String interfaceId = String.valueOf(request.getOrDefault("interfaceId", ""));
log.info("[MockEimsHttpServer] Legacy gateway mock request. traceId={}, interfaceId={}", traceId, interfaceId);
return ResponseEntity.ok(Map.of(
"status", "404",
"message", "MOCK data is not defined for interfaceId: " + interfaceId));
}
}

View File

@@ -853,12 +853,17 @@ public class ToolScaffolder {
Path projectRoot = moduleRoot.getParent();
Path wireMockBodyPath = projectRoot.resolve(Paths.get("mci-mock", "__files", toolName + ".json"));
Path wireMockMappingPath = projectRoot.resolve(Paths.get("mci-mock", "mappings", toolName + ".json"));
Path podMockResponsePath = moduleRoot.resolve(Paths.get("src/main/resources/mock-responses", toolName + ".json"));
Files.createDirectories(wireMockBodyPath.getParent());
Files.createDirectories(wireMockMappingPath.getParent());
Files.createDirectories(podMockResponsePath.getParent());
Files.writeString(wireMockBodyPath, mockResponse);
Files.writeString(wireMockMappingPath, wireMockMappingContent(interfaceId, wireMockBodyPath.getFileName().toString()));
Files.writeString(podMockResponsePath, mockResponse);
ensureLocalHttpApiConfiguration(moduleRoot, httpApiName, toolName);
log.append("[WireMock Response] ").append(wireMockBodyPath).append("\n");
log.append("[WireMock Mapping] ").append(wireMockMappingPath).append("\n");
log.append("[Pod Mock Response] ").append(podMockResponsePath).append("\n");
} else {
Path mockResponsePath = rootDir.resolve(Paths.get(moduleName, "src/main/resources/mock-responses", toolName + ".json"));
Files.createDirectories(mockResponsePath.getParent());
@@ -884,6 +889,36 @@ public class ToolScaffolder {
.toLowerCase(Locale.ROOT);
}
private static void ensureLocalHttpApiConfiguration(Path moduleRoot, String httpApiName, String toolName) throws IOException {
Path localConfigPath = moduleRoot.resolve("src/main/resources/application-local.yml");
Files.createDirectories(localConfigPath.getParent());
String existing = Files.exists(localConfigPath) ? Files.readString(localConfigPath) : "";
if (java.util.regex.Pattern.compile("(?m)^\\s*-\\s+name:\\s*"
+ java.util.regex.Pattern.quote(httpApiName) + "\\s*$").matcher(existing).find()) {
return;
}
String environmentKey = toPackageSegment(httpApiName).toUpperCase(Locale.ROOT).replace('-', '_');
String config = """
axhub:
mock:
http:
enabled: true
glow:
communication:
http:
api-list:
- name: %s
domain: ${AXHUB_%s_HTTP_DOMAIN:http://localhost:${server.port}}
url: ${AXHUB_%s_HTTP_URL:/api/mock/http/%s}
method: POST
content-type: application/json;charset=UTF-8
biz-pod: false
""".formatted(httpApiName, environmentKey, environmentKey, toolName);
Files.writeString(localConfigPath, existing + config);
}
private static String dtoContent(String packageName, String className, List<FieldDefinition> fields,
String author, String createDate, boolean request) {
String body = fieldLines(fields, request ? Set.of() : Set.of("resultCode", "resultMessage"));
@@ -999,21 +1034,24 @@ public class ToolScaffolder {
import %s.dto.%sRequest;
import %s.dto.%sResponse;
import %s.%s.io.%sHttpRequest;
import %s.%s.io.%sHttpResponse;
import %s.io.%sHttpRequest;
import %s.io.%sHttpResponse;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.ReportingPolicy;
@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface %sConverter {
// Field names differ? Add mappings like this before the method.
// @Mapping(source = "sourceField", target = "targetField")
%sHttpRequest toHttpRequest(%sRequest request);
%sResponse toResponse(%sHttpResponse httpResponse);
}
""".formatted(bizPackage,
bizPackage, baseName,
bizPackage, baseName,
BASE_PACKAGE, httpPackage, baseName,
BASE_PACKAGE, httpPackage, baseName,
httpPackage, baseName,
httpPackage, baseName,
baseName, baseName, baseName, baseName, baseName);
}
@@ -1032,9 +1070,13 @@ public class ToolScaffolder {
import %s.%s.io.%s_I;
import %s.%s.io.%s_O;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.ReportingPolicy;
@Mapper(componentModel = "spring")
@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface %sConverter {
// Field names differ? Add mappings like this before the method.
// @Mapping(source = "sourceField", target = "targetField")
%s_I toLegacyRequest(%sRequest request);
%sRequest toRequest(%s_I mciRequest);
%sResponse toResponse(%s_O mciRes);
@@ -1053,10 +1095,13 @@ public class ToolScaffolder {
import %s.legacy.%sLegacyRequest;
import %s.legacy.%sLegacyResponse;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.ReportingPolicy;
@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface %sConverter {
// Field names differ? Add mappings like this before the method.
// @Mapping(source = "sourceField", target = "targetField")
%sLegacyRequest toLegacyRequest(%sRequest request);
%sRequest toRequest(%sLegacyRequest legacyRequest);
%sResponse toResponse(%sLegacyResponse legacyResponse);