From f4b277125ce57f3bfd2bec8a48e125950f2824bb Mon Sep 17 00:00:00 2001 From: AgAngle <1323481023@qq.com> Date: Mon, 15 Jul 2024 18:04:52 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E6=8E=A5=E5=8F=A3=E6=B5=8B=E8=AF=95):=20j?= =?UTF-8?q?son-schema=E8=87=AA=E5=8A=A8=E7=94=9F=E6=88=90=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E6=95=B0=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --story=1015366 --user=陈建星 【接口测试】json-schema组件 https://www.tapd.cn/55049933/s/1547748 --- backend/services/api-test/pom.xml | 6 + .../definition/ApiDefinitionController.java | 9 +- .../api/dto/schema/JsonSchemaItem.java | 2 +- .../api/parser/api/Swagger3Parser.java | 7 +- .../definition/ApiDefinitionService.java | 8 + .../api/utils/JsonSchemaBuilder.java | 168 ++++++++++++++---- .../ApiDefinitionControllerTests.java | 35 +++- .../project/constants/PropertyConstant.java | 7 + .../src/api/modules/api-test/management.ts | 6 + .../src/api/requrls/api-test/management.ts | 3 +- .../components/pure/ms-json-schema/index.vue | 6 +- .../components/requestComposition/body.vue | 4 +- .../requestComposition/response/edit.vue | 4 +- pom.xml | 2 +- 14 files changed, 215 insertions(+), 52 deletions(-) diff --git a/backend/services/api-test/pom.xml b/backend/services/api-test/pom.xml index cb462d66f4..c7f7208769 100644 --- a/backend/services/api-test/pom.xml +++ b/backend/services/api-test/pom.xml @@ -94,6 +94,12 @@ + + + com.github.krraghavan + xeger + ${xeger.version} + diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/controller/definition/ApiDefinitionController.java b/backend/services/api-test/src/main/java/io/metersphere/api/controller/definition/ApiDefinitionController.java index 7cd1e3efa0..0c4f8bc06b 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/controller/definition/ApiDefinitionController.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/controller/definition/ApiDefinitionController.java @@ -278,13 +278,20 @@ public class ApiDefinitionController { return apiFileResourceService.transfer(request, SessionUtils.getUserId(), apiDefinitionDir); } - @PostMapping("/preview") + @PostMapping("/json-schema/preview") @Operation(summary = "接口测试-接口管理-接口-json-schema-预览") @RequiresPermissions(PermissionConstants.PROJECT_API_DEFINITION_READ) public String preview(@RequestBody JsonSchemaItem jsonSchemaItem) { return apiDefinitionService.preview(jsonSchemaItem); } + @PostMapping("/json-schema/auto-generate") + @Operation(summary = "接口测试-接口管理-接口-json-schema-自动生成测试数据") + @RequiresPermissions(PermissionConstants.PROJECT_API_DEFINITION_READ) + public String jsonSchemaAutoGenerate(@RequestBody JsonSchemaItem jsonSchemaItem) { + return apiDefinitionService.jsonSchemaAutoGenerate(jsonSchemaItem); + } + @PostMapping("/debug") @Operation(summary = "接口调试") @RequiresPermissions(PermissionConstants.PROJECT_API_DEFINITION_EXECUTE) diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/dto/schema/JsonSchemaItem.java b/backend/services/api-test/src/main/java/io/metersphere/api/dto/schema/JsonSchemaItem.java index e102493be5..bbbdd304ef 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/dto/schema/JsonSchemaItem.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/dto/schema/JsonSchemaItem.java @@ -106,7 +106,7 @@ public class JsonSchemaItem { /** * 参数值的枚举 */ - private List enumValues; + private List enumValues; /** * 是否启用 */ diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/parser/api/Swagger3Parser.java b/backend/services/api-test/src/main/java/io/metersphere/api/parser/api/Swagger3Parser.java index 3b4abe3164..e035b308a5 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/parser/api/Swagger3Parser.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/parser/api/Swagger3Parser.java @@ -310,7 +310,7 @@ public class Swagger3Parser extends ApiImportAbstractParser } else { String jsonString = JSON.toJSONString(jsonSchemaItem); if (StringUtils.isNotBlank(jsonString)) { - jsonBody.setJsonValue(JsonSchemaBuilder.jsonSchemaToJson(jsonString)); + jsonBody.setJsonValue(JsonSchemaBuilder.jsonSchemaToJson(jsonString, true)); } } return jsonBody; @@ -663,7 +663,10 @@ public class Swagger3Parser extends ApiImportAbstractParser jsonSchemaItem.setFormat(StringUtils.isNotBlank(integerSchema.getFormat()) ? integerSchema.getFormat() : StringUtils.EMPTY); jsonSchemaItem.setMaximum(integerSchema.getMaximum()); jsonSchemaItem.setMinimum(integerSchema.getMinimum()); - jsonSchemaItem.setEnumValues(integerSchema.getEnum()); + List enumValues = integerSchema.getEnum(); + if (CollectionUtils.isNotEmpty(enumValues)) { + jsonSchemaItem.setEnumValues(enumValues.stream().map(item -> item.toString()).toList()); + } return jsonSchemaItem; } diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/service/definition/ApiDefinitionService.java b/backend/services/api-test/src/main/java/io/metersphere/api/service/definition/ApiDefinitionService.java index 52df63e36a..b0982e77b0 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/service/definition/ApiDefinitionService.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/service/definition/ApiDefinitionService.java @@ -1195,6 +1195,14 @@ public class ApiDefinitionService extends MoveNodeService { return JsonSchemaBuilder.preview(JSON.toJSONString(jsonSchemaItem)); } + public String jsonSchemaAutoGenerate(JsonSchemaItem jsonSchemaItem) { + if (BooleanUtils.isFalse(jsonSchemaItem.getEnable())) { + return "{}"; + } + filterDisableItem(jsonSchemaItem); + return JsonSchemaBuilder.jsonSchemaAutoGenerate(JSON.toJSONString(jsonSchemaItem)); + } + private void filterDisableItem(JsonSchemaItem jsonSchemaItem) { if (isObjectItem(jsonSchemaItem)) { Map properties = jsonSchemaItem.getProperties(); diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/utils/JsonSchemaBuilder.java b/backend/services/api-test/src/main/java/io/metersphere/api/utils/JsonSchemaBuilder.java index 05d0dbcbc4..2221ce9769 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/utils/JsonSchemaBuilder.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/utils/JsonSchemaBuilder.java @@ -2,26 +2,26 @@ package io.metersphere.api.utils; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.*; -import io.metersphere.jmeter.mock.Mock; import io.metersphere.project.constants.PropertyConstant; +import nl.flotsam.xeger.Xeger; import org.apache.commons.collections4.MapUtils; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.text.RandomStringGenerator; import org.jetbrains.annotations.NotNull; import java.math.BigDecimal; import java.util.HashMap; import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; +import java.util.Random; public class JsonSchemaBuilder { - public static String jsonSchemaToJson(String jsonSchemaString) { + public static String jsonSchemaToJson(String jsonSchemaString, boolean isPreview) { // 解析 JSON Schema 字符串为 JsonNode JsonNode jsonSchemaNode = ApiDataUtils.readTree(jsonSchemaString); Map processMap = new HashMap<>(); // 生成符合 JSON Schema 的 JSON - JsonNode jsonNode = generateJson(jsonSchemaNode, processMap); + JsonNode jsonNode = generateJson(jsonSchemaNode, processMap, isPreview); String jsonString = ApiDataUtils.writerWithDefaultPrettyPrinter(jsonNode); if (MapUtils.isNotEmpty(processMap)) { for (String str : processMap.keySet()) { @@ -31,13 +31,13 @@ public class JsonSchemaBuilder { return jsonString; } - private static JsonNode generateJson(JsonNode jsonSchemaNode, Map processMap) { + private static JsonNode generateJson(JsonNode jsonSchemaNode, Map processMap, boolean isPreview) { ObjectNode jsonNode = ApiDataUtils.createObjectNode(); if (jsonSchemaNode instanceof NullNode) { return NullNode.getInstance(); } - String type = jsonSchemaNode.get(PropertyConstant.TYPE) == null ? StringUtils.EMPTY : jsonSchemaNode.get(PropertyConstant.TYPE).asText(); + String type = getPropertyTextValue(jsonSchemaNode, PropertyConstant.TYPE); if (StringUtils.equals(type, PropertyConstant.OBJECT)) { JsonNode propertiesNode = jsonSchemaNode.get(PropertyConstant.PROPERTIES); // 遍历 properties @@ -46,7 +46,8 @@ public class JsonSchemaBuilder { String propertyName = entry.getKey(); JsonNode propertyNode = entry.getValue(); // 根据属性类型生成对应的值 - JsonNode valueNode = generateValue(entry.getKey(), propertyNode, processMap); + JsonNode valueNode = isPreview ? generateValueForPreview(entry.getKey(), propertyNode, processMap) + : generateValue(entry.getKey(), propertyNode, processMap); // 将属性和值添加到 JSON 对象节点 jsonNode.set(propertyName, valueNode); }); @@ -55,23 +56,27 @@ public class JsonSchemaBuilder { JsonNode items = jsonSchemaNode.get(PropertyConstant.ITEMS); if (items != null) { ArrayNode arrayNode = new ArrayNode(JsonNodeFactory.instance); - items.forEach(item -> arrayNode.add(generateValue(null, item, processMap))); + items.forEach(item -> { + JsonNode valueNode = isPreview ? generateValueForPreview(null, item, processMap) + : generateValue(null, item, processMap); + arrayNode.add(valueNode); + }); return arrayNode; } } return jsonNode; } - private static JsonNode generateValue(String propertyName, JsonNode propertyNode, Map processMap) { + private static JsonNode generateValueForPreview(String propertyName, JsonNode propertyNode, Map processMap) { // 获取属性类型 if (propertyNode instanceof NullNode) { return NullNode.getInstance(); } - String type = propertyNode.get(PropertyConstant.TYPE) == null ? StringUtils.EMPTY : propertyNode.get(PropertyConstant.TYPE).asText(); - String value = propertyNode.get(PropertyConstant.EXAMPLE) == null ? StringUtils.EMPTY : propertyNode.get(PropertyConstant.EXAMPLE).asText(); + String type = getPropertyTextValue(propertyNode, PropertyConstant.TYPE); + String value = getPropertyTextValue(propertyNode, PropertyConstant.EXAMPLE); return switch (type) { case PropertyConstant.STRING -> - new TextNode(!StringUtils.equals(value, PropertyConstant.NULL) ? value : "string"); + new TextNode(StringUtils.isBlank(value) ? "string" : value); case PropertyConstant.INTEGER -> { if (isVariable(value)) { yield getJsonNodes(propertyName, processMap, value); @@ -97,8 +102,112 @@ public class JsonSchemaBuilder { yield BooleanNode.valueOf(propertyNode.get(PropertyConstant.EXAMPLE).asBoolean()); } } + case PropertyConstant.OBJECT -> generateJson(propertyNode, processMap, true); + case PropertyConstant.ARRAY -> { + ArrayNode arrayNode = new ArrayNode(JsonNodeFactory.instance); + JsonNode items = propertyNode.get(PropertyConstant.ITEMS); + if (items != null) { + items.forEach(item -> arrayNode.add(generateValueForPreview(null, item, processMap))); + } + yield arrayNode; + } + default -> NullNode.getInstance(); + }; - case PropertyConstant.OBJECT -> generateJson(propertyNode, processMap); + } + + private static JsonNode generateValue(String propertyName, JsonNode propertyNode, Map processMap) { + // 获取属性类型 + if (propertyNode instanceof NullNode) { + return NullNode.getInstance(); + } + String type = getPropertyTextValue(propertyNode, PropertyConstant.TYPE); + String value = getPropertyTextValue(propertyNode, PropertyConstant.EXAMPLE); + return switch (type) { + case PropertyConstant.STRING -> { + if (StringUtils.isBlank(value)) { + JsonNode enumValues = propertyNode.get(PropertyConstant.ENUM_VALUES); + JsonNode defaultValue = propertyNode.get(PropertyConstant.DEFAULT_VALUE); + JsonNode pattern = propertyNode.get(PropertyConstant.PATTERN); + JsonNode maxLength = propertyNode.get(PropertyConstant.MAX_LENGTH); + JsonNode minLength = propertyNode.get(PropertyConstant.MIN_LENGTH); + int max = isTextNotBlank(maxLength) ? maxLength.asInt() : 20; + int min = isTextNotBlank(minLength) ? minLength.asInt() : 1; + if (enumValues != null && enumValues instanceof ArrayNode) { + value = enumValues.get(new Random().nextInt(enumValues.size())).asText(); + } else if (isTextNotBlank(defaultValue)) { + value = defaultValue.asText(); + } else if (isTextNotBlank(pattern)) { + Xeger generator = new Xeger(pattern.asText()); + value = generator.generate(); + } else { + value = RandomStringGenerator.builder().withinRange('0', 'z').build().generate(new Random().nextInt(max - min + 1) + min); + } + } + yield new TextNode(value); + } + case PropertyConstant.INTEGER -> { + if (StringUtils.isBlank(value)) { + JsonNode enumValues = propertyNode.get(PropertyConstant.ENUM_VALUES); + JsonNode defaultValue = propertyNode.get(PropertyConstant.DEFAULT_VALUE); + if (enumValues != null && enumValues instanceof ArrayNode) { + value = enumValues.get(new Random().nextInt(enumValues.size())).asText(); + } else if (isTextNotBlank(defaultValue)) { + value = defaultValue.asText(); + } else { + JsonNode maximum = propertyNode.get(PropertyConstant.MAXIMUM); + JsonNode minimum = propertyNode.get(PropertyConstant.MINIMUM); + int max = isTextNotBlank(maximum) ? maximum.asInt() : Integer.MAX_VALUE; + int min = isTextNotBlank(minimum) ? minimum.asInt() : Integer.MIN_VALUE; + // 这里减去负数可能超过整型最大值,使用 Long 类型 + value = new Random().nextLong(Long.valueOf(max) - Long.valueOf(min)) + min + StringUtils.EMPTY; + } + } else { + if (isVariable(value)) { + yield getJsonNodes(propertyName, processMap, value); + } + } + try { + yield new IntNode(Integer.valueOf(value)); + } catch (Exception e) { + yield new IntNode(propertyNode.get(PropertyConstant.EXAMPLE).asInt()); + } + } + case PropertyConstant.NUMBER -> { + if (StringUtils.isBlank(value)) { + JsonNode enumValues = propertyNode.get(PropertyConstant.ENUM_VALUES); + JsonNode defaultValue = propertyNode.get(PropertyConstant.DEFAULT_VALUE); + if (enumValues != null && enumValues instanceof ArrayNode) { + value = enumValues.get(new Random().nextInt(enumValues.size())).asText(); + } else if (isTextNotBlank(defaultValue)) { + value = defaultValue.asText(); + } else { + JsonNode maximum = propertyNode.get(PropertyConstant.MAXIMUM); + JsonNode minimum = propertyNode.get(PropertyConstant.MINIMUM); + BigDecimal max = isTextNotBlank(maximum) ? new BigDecimal(maximum.asText()) : new BigDecimal(String.valueOf(Float.MAX_VALUE)); + BigDecimal min = isTextNotBlank(minimum) ? new BigDecimal(minimum.asText()) : new BigDecimal(String.valueOf(Float.MIN_VALUE)); + BigDecimal randomBigDecimal = min.add(new BigDecimal(String.valueOf(Math.random())).multiply(max.subtract(min))); + yield new DecimalNode(randomBigDecimal.setScale(2, BigDecimal.ROUND_HALF_UP)); + } + } else { + if (isVariable(value)) { + yield getJsonNodes(propertyName, processMap, value); + } + } + try { + yield new DecimalNode(new BigDecimal(value)); + } catch (Exception e) { + yield new DecimalNode(propertyNode.get(PropertyConstant.EXAMPLE).decimalValue()); + } + } + case PropertyConstant.BOOLEAN -> { + if (isVariable(value)) { + yield getJsonNodes(propertyName, processMap, value); + } else { + yield BooleanNode.valueOf(propertyNode.get(PropertyConstant.EXAMPLE).asBoolean()); + } + } + case PropertyConstant.OBJECT -> generateJson(propertyNode, processMap, true); case PropertyConstant.ARRAY -> { ArrayNode arrayNode = new ArrayNode(JsonNodeFactory.instance); JsonNode items = propertyNode.get(PropertyConstant.ITEMS); @@ -112,6 +221,14 @@ public class JsonSchemaBuilder { } + private static boolean isTextNotBlank(JsonNode jsonNode) { + return jsonNode != null && !(jsonNode instanceof NullNode) && StringUtils.isNotBlank(jsonNode.asText()); + } + + private static String getPropertyTextValue(JsonNode propertyNode, String key) { + return propertyNode.get(key) == null ? StringUtils.EMPTY : propertyNode.get(key).asText(); + } + private static boolean isVariable(String value) { return !StringUtils.equals(value, PropertyConstant.NULL) && (value.startsWith("@") || value.startsWith("${")); } @@ -125,25 +242,10 @@ public class JsonSchemaBuilder { } public static String preview(String jsonSchema) { - String jsonString = jsonSchemaToJson(jsonSchema); - //需要匹配到mock函数 然后换成mock数据 - if (StringUtils.isNotBlank(jsonString)) { - String pattern = "@[a-zA-Z\\\\(|,'-\\\\d ]*[a-zA-Z)-9),\\\\\"]"; - Pattern regex = Pattern.compile(pattern); - Matcher matcher = regex.matcher(jsonString); - while (matcher.find()) { - //取出group的最后一个字符 主要是防止 @string|number 和 @string 这种情况 - String group = matcher.group(); - String lastChar = null; - if (group.endsWith(",") || group.endsWith("\"")) { - lastChar = group.substring(group.length() - 1); - group = group.substring(0, group.length() - 1); - } - jsonString = jsonString.replace(matcher.group(), - StringUtils.join(Mock.calculate(group), lastChar)); - } - } - return jsonString; + return jsonSchemaToJson(jsonSchema, true); + } + public static String jsonSchemaAutoGenerate(String jsonString) { + return jsonSchemaToJson(jsonString, false); } } diff --git a/backend/services/api-test/src/test/java/io/metersphere/api/controller/ApiDefinitionControllerTests.java b/backend/services/api-test/src/test/java/io/metersphere/api/controller/ApiDefinitionControllerTests.java index 6f0fe1a27c..4907f813c0 100644 --- a/backend/services/api-test/src/test/java/io/metersphere/api/controller/ApiDefinitionControllerTests.java +++ b/backend/services/api-test/src/test/java/io/metersphere/api/controller/ApiDefinitionControllerTests.java @@ -117,6 +117,8 @@ public class ApiDefinitionControllerTests extends BaseTest { private static final String ALL_API = "api_definition_module.api.all"; private static final String UNPLANNED_API = "api_unplanned_request"; + private static final String JSON_SCHEMA_PREVIEW = "json-schema/preview"; + private static final String JSON_SCHEMA_AUTO_GENERATE = "json-schema/auto-generate"; private static final String EXPORT = "/export/"; private static ApiDefinition apiDefinition; @@ -1718,12 +1720,12 @@ public class ApiDefinitionControllerTests extends BaseTest { {"example":null,"id":null,"title":null,"type":"object","description":null,"items":null,"properties":{"id":{"example":10,"id":null,"title":null,"type":"integer","description":"","items":null,"properties":null,"additionalProperties":null,"required":null,"pattern":null,"maxLength":null,"minLength":null,"minimum":null,"maximum":null,"schema":null,"format":"int64","enumString":null,"enumInteger":null,"enumNumber":null,"extensions":null},"name":{"example":"@string","id":null,"title":null,"type":"string","description":"","items":null,"properties":null,"additionalProperties":null,"required":null,"pattern":null,"maxLength":null,"minLength":null,"minimum":null,"maximum":null,"schema":null,"format":"","enumString":null,"enumInteger":null,"enumNumber":null,"extensions":null},"category":{"example":null,"id":null,"title":null,"type":"object","description":null,"items":null,"properties":{"id":{"example":"@integer","id":null,"title":null,"type":"integer","description":"","items":null,"properties":null,"additionalProperties":null,"required":null,"pattern":null,"maxLength":null,"minLength":null,"minimum":null,"maximum":null,"schema":null,"format":"int64","enumString":null,"enumInteger":null,"enumNumber":null,"extensions":null},"name":{"example":"Dogs","id":null,"title":null,"type":"string","description":"","items":null,"properties":null,"additionalProperties":null,"required":null,"pattern":null,"maxLength":null,"minLength":null,"minimum":null,"maximum":null,"schema":null,"format":"","enumString":null,"enumInteger":null,"enumNumber":null,"extensions":null}},"additionalProperties":null,"required":null,"pattern":null,"maxLength":null,"minLength":null,"minimum":null,"maximum":null,"schema":null,"format":null,"enumString":null,"enumInteger":null,"enumNumber":null,"extensions":null},"photoUrls":{"example":null,"id":null,"title":null,"type":"array","description":null,"items":[{"example":null,"id":null,"title":null,"type":"string","description":"","items":null,"properties":null,"additionalProperties":null,"required":null,"pattern":null,"maxLength":null,"minLength":null,"minimum":null,"maximum":null,"schema":null,"format":"","enumString":null,"enumInteger":null,"enumNumber":null,"extensions":null}],"properties":null,"additionalProperties":null,"required":null,"pattern":null,"maxLength":null,"minLength":null,"minimum":null,"maximum":null,"schema":null,"format":null,"enumString":null,"enumInteger":null,"enumNumber":null,"extensions":null},"tags":{"example":null,"id":null,"title":null,"type":"array","description":null,"items":[{"example":null,"id":null,"title":null,"type":"object","description":null,"items":null,"properties":{"id":{"example":null,"id":null,"title":null,"type":"integer","description":"","items":null,"properties":null,"additionalProperties":null,"required":null,"pattern":null,"maxLength":null,"minLength":null,"minimum":null,"maximum":null,"schema":null,"format":"int64","enumString":null,"enumInteger":null,"enumNumber":null,"extensions":null},"name":{"example":null,"id":null,"title":null,"type":"string","description":"","items":null,"properties":null,"additionalProperties":null,"required":null,"pattern":null,"maxLength":null,"minLength":null,"mainimum":null,"maximum":null,"schema":null,"format":"","enumString":null,"enumInteger":null,"enumNumber":null,"extensions":null}},"additionalProperties":null,"required":null,"pattern":null,"maxLength":null,"minLength":null,"minimum":null,"maximum":null,"schema":null,"format":null,"enumString":null,"enumInteger":null,"enumNumber":null,"extensions":null}],"properties":null,"additionalProperties":null,"required":null,"pattern":null,"maxLength":null,"minLength":null,"minimum":null,"maximum":null,"schema":null,"format":null,"enumString":null,"enumInteger":null,"enumNumber":null,"extensions":null},"status":{"example":"available","id":null,"title":null,"type":"string","description":"pet status in the store","items":null,"properties":null,"additionalProperties":null,"required":null,"pattern":null,"maxLength":null,"minLength":null,"minimum":null,"maximum":null,"schema":null,"format":"","enumString":["available","pending","sold"],"enumInteger":null,"enumNumber":null,"extensions":null},"testnumber":{"example":1.23139183198000000283719387,"id":null,"title":null,"type":"number","description":"pet status in the store","items":null,"properties":null,"additionalProperties":null,"required":null,"pattern":null,"maxLength":null,"minLength":null,"minimum":null,"maximum":null,"schema":null,"format":"","enumString":["available","pending","sold"],"enumInteger":null,"enumNumber":null,"extensions":null},"testnumber11":{"example":"@number","id":null,"title":null,"type":"number","description":"pet status in the store","items":null,"properties":null,"additionalProperties":null,"required":null,"pattern":null,"maxLength":null,"minLength":null,"minimum":null,"maximum":null,"schema":null,"format":"","enumString":["available","pending","sold"],"enumInteger":null,"enumNumber":null,"extensions":null},"testfalse":{"example":"@boolean","id":null,"title":null,"type":"boolean","description":"pet status in the store","items":null,"properties":null,"additionalProperties":null,"required":null,"pattern":null,"maxLength":null,"minLength":null,"minimum":null,"maximum":null,"schema":null,"format":"","enumString":["available","pending","sold"],"enumInteger":null,"enumNumber":null,"extensions":null},"testfalse":{"example":false,"id":null,"title":null,"type":"boolean","description":"pet status in the store","items":null,"properties":null,"additionalProperties":null,"required":null,"pattern":null,"maxLength":null,"minLength":null,"minimum":null,"maximum":null,"schema":null,"format":"","enumString":["available","pending","sold"],"enumInteger":null,"enumNumber":null,"extensions":null},"testnull":{"example":null,"id":null,"title":null,"type":"null","description":"pet status in the store","items":null,"properties":null,"additionalProperties":null,"required":null,"pattern":null,"maxLength":null,"minLength":null,"minimum":null,"maximum":null,"schema":null,"format":"","enumInteger":null,"enumNumber":null,"extensions":null},"testass": null},"additionalProperties":null,"required":["name","photoUrls"],"pattern":null,"maxLength":null,"minLength":null,"minimum":null,"maximum":null,"schema":null,"format":null,"enumString":null,"enumInteger":null,"enumNumber":null,"extensions":null} """; //正常数据; - requestPost("preview", JSON.parseObject(jsonString, JsonSchemaItem.class)).andExpect(status().isOk()); + requestPostWithOk(JSON_SCHEMA_PREVIEW, JSON.parseObject(jsonString, JsonSchemaItem.class)); //正常array数据 String jsonArray = """ {"example":null,"id":null,"title":null,"type":"array","description":null,"items":{"example":null,"id":null,"title":null,"type":"object","description":null,"items":null,"properties":{"id":{"example":"@integer","id":null,"title":null,"type":"integer","description":"","items":null,"properties":null,"additionalProperties":null,"required":null,"pattern":null,"maxLength":null,"minLength":null,"minimum":null,"maximum":null,"schema":null,"format":"int64","enumString":null,"enumInteger":null,"enumNumber":null,"extensions":null},"name":{"example":null,"id":null,"title":null,"type":"string","description":"","items":null,"properties":null,"additionalProperties":null,"required":null,"pattern":null,"maxLength":null,"minLength":null,"mainimum":null,"maximum":null,"schema":null,"format":"","enumString":null,"enumInteger":null,"enumNumber":null,"extensions":null}},"additionalProperties":null,"required":null,"pattern":null,"maxLength":null,"minLength":null,"minimum":null,"maximum":null,"schema":null,"format":null,"enumString":null,"enumInteger":null,"enumNumber":null,"extensions":null},"properties":null,"additionalProperties":null,"required":null,"pattern":null,"maxLength":null,"minLength":null,"minimum":null,"maximum":null,"schema":null,"format":null,"enumString":null,"enumInteger":null,"enumNumber":null,"extensions":null} """; - requestPost("preview", JSON.parseObject(jsonArray, JsonSchemaItem.class)).andExpect(status().isOk()); + requestPostWithOk(JSON_SCHEMA_PREVIEW, JSON.parseObject(jsonArray, JsonSchemaItem.class)); // 校验转换是否正确 String schema = """ @@ -1739,7 +1741,7 @@ public class ApiDefinitionControllerTests extends BaseTest { "null" : null } """; - MvcResult mvcResult = requestPostWithOkAndReturn("preview", JSON.parseObject(schema, JsonSchemaItem.class)); + MvcResult mvcResult = requestPostWithOkAndReturn(JSON_SCHEMA_PREVIEW, JSON.parseObject(schema, JsonSchemaItem.class)); String resultData = getResultData(mvcResult, String.class); Assertions.assertEquals(JSON.parseObject(jsonResult), JSON.parseObject(resultData)); @@ -1760,7 +1762,7 @@ public class ApiDefinitionControllerTests extends BaseTest { null ] """; - mvcResult = requestPostWithOkAndReturn("preview", JSON.parseObject(schema, JsonSchemaItem.class)); + mvcResult = requestPostWithOkAndReturn(JSON_SCHEMA_PREVIEW, JSON.parseObject(schema, JsonSchemaItem.class)); resultData = getResultData(mvcResult, String.class); Assertions.assertEquals(JSON.parseObject(jsonResult), JSON.parseObject(resultData)); @@ -1768,18 +1770,39 @@ public class ApiDefinitionControllerTests extends BaseTest { schema = """ {"type":"object","enable":false} """; - mvcResult = requestPostWithOkAndReturn("preview", JSON.parseObject(schema, JsonSchemaItem.class)); + mvcResult = requestPostWithOkAndReturn(JSON_SCHEMA_PREVIEW, JSON.parseObject(schema, JsonSchemaItem.class)); Assertions.assertEquals(getResultData(mvcResult, String.class), "{}"); // 校验禁用 schema = """ {"type":"object","enable":true,"properties":{"array":{"type":"array","enable":false,"items":[{"type":"string","example":"1","enable":false},{"type":"number","example":"2","enable":false}]},"string":{"type":"string","example":"stringValue","enable":false},"int":{"type":"integer","example":"1","enable":false},"num":{"type":"number","example":"1.00","enable":false},"boolean":{"type":"boolean","example":"booleanValue","enable":false},"null":{"type":"null","enable":false}}} """; - mvcResult = requestPostWithOkAndReturn("preview", JSON.parseObject(schema, JsonSchemaItem.class)); + mvcResult = requestPostWithOkAndReturn(JSON_SCHEMA_PREVIEW, JSON.parseObject(schema, JsonSchemaItem.class)); resultData = getResultData(mvcResult, String.class); Assertions.assertEquals(JSON.parseObject("{}"), JSON.parseObject(resultData)); } + @Test + @Order(104) + public void testJsonSchemaAutoGenerate() throws Exception { + String jsonString = """ + {"id":null,"title":null,"example":null,"type":"object","description":null,"items":null,"properties":{"array":{"id":null,"title":null,"example":null,"type":"array","description":null,"items":[{"id":null,"title":null,"example":"","type":"string","description":"","items":null,"properties":null,"additionalProperties":null,"required":null,"defaultValue":"","pattern":null,"maxLength":null,"minLength":null,"minimum":null,"maximum":null,"maxItems":null,"minItems":null,"format":null,"enumValues":null,"enable":true},{"id":null,"title":null,"example":"","type":"number","description":"","items":null,"properties":null,"additionalProperties":null,"required":null,"defaultValue":"","pattern":null,"maxLength":null,"minLength":null,"minimum":null,"maximum":null,"maxItems":null,"minItems":null,"format":null,"enumValues":null,"enable":true}],"properties":null,"additionalProperties":null,"required":null,"defaultValue":null,"pattern":null,"maxLength":null,"minLength":null,"minimum":null,"maximum":null,"maxItems":null,"minItems":null,"format":null,"enumValues":null,"enable":true},"string":{"id":null,"title":null,"example":"","type":"string","description":"","items":null,"properties":null,"additionalProperties":null,"required":null,"defaultValue":"","pattern":null,"maxLength":null,"minLength":null,"minimum":null,"maximum":null,"maxItems":null,"minItems":null,"format":null,"enumValues":null,"enable":true},"int":{"id":null,"title":null,"example":"","type":"integer","description":"","items":null,"properties":null,"additionalProperties":null,"required":null,"defaultValue":"","pattern":null,"maxLength":null,"minLength":null,"minimum":null,"maximum":null,"maxItems":null,"minItems":null,"format":null,"enumValues":null,"enable":true},"num":{"id":null,"title":null,"example":"","type":"number","description":"","items":null,"properties":null,"additionalProperties":null,"required":null,"defaultValue":"","pattern":null,"maxLength":null,"minLength":null,"minimum":null,"maximum":null,"maxItems":null,"minItems":null,"format":null,"enumValues":null,"enable":true},"boolean":{"id":null,"title":null,"example":"","type":"boolean","description":"","items":null,"properties":null,"additionalProperties":null,"required":null,"defaultValue":"","pattern":null,"maxLength":null,"minLength":null,"minimum":null,"maximum":null,"maxItems":null,"minItems":null,"format":null,"enumValues":null,"enable":true},"null":{"id":null,"title":null,"example":null,"type":"null","description":null,"items":null,"properties":null,"additionalProperties":null,"required":null,"defaultValue":null,"pattern":null,"maxLength":null,"minLength":null,"minimum":null,"maximum":null,"maxItems":null,"minItems":null,"format":null,"enumValues":null,"enable":true}},"additionalProperties":null,"required":null,"defaultValue":null,"pattern":null,"maxLength":null,"minLength":null,"minimum":null,"maximum":null,"maxItems":null,"minItems":null,"format":null,"enumValues":null,"enable":true} + """; + // 默认生成 + requestPost(JSON_SCHEMA_AUTO_GENERATE, JSON.parseObject(jsonString, JsonSchemaItem.class)); + // 带枚举值 + jsonString = """ + {"type":"object","enable":true,"properties":{"array":{"type":"array","enable":true,"items":[{"type":"string","example":"","description":"","additionalProperties":null,"defaultValue":"","pattern":null,"maxLength":4,"minLength":0,"minimum":null,"maximum":null,"maxItems":null,"minItems":null,"format":null,"enable":true,"regex":"^[A-Z]"},{"type":"number","example":"","description":"","additionalProperties":null,"defaultValue":"","pattern":null,"maxLength":null,"minLength":null,"minimum":null,"maximum":null,"maxItems":null,"minItems":null,"format":null,"enable":true}]},"string":{"type":"string","example":"","description":"","additionalProperties":null,"defaultValue":"","pattern":null,"maxLength":5,"minLength":1,"minimum":null,"maximum":null,"maxItems":null,"minItems":null,"format":null,"enumValues":["11111","22222"],"enable":true},"int":{"type":"integer","example":"","description":"","additionalProperties":null,"defaultValue":"","pattern":null,"maxLength":null,"minLength":null,"minimum":0,"maximum":4,"maxItems":null,"minItems":null,"format":null,"enumValues":["111","2333","444"],"enable":true},"num":{"type":"number","example":"","description":"","additionalProperties":null,"defaultValue":"","pattern":null,"maxLength":null,"minLength":null,"minimum":0,"maximum":5,"maxItems":null,"minItems":null,"format":null,"enumValues":["111","222","3333"],"enable":true},"boolean":{"type":"boolean","example":"","description":"","additionalProperties":null,"defaultValue":"","pattern":null,"maxLength":null,"minLength":null,"minimum":null,"maximum":null,"maxItems":null,"minItems":null,"format":null,"enable":true},"null":{"type":"null","enable":true}}} + """; + requestPostWithOk(JSON_SCHEMA_AUTO_GENERATE, JSON.parseObject(jsonString, JsonSchemaItem.class)); + + // 默认值和正则 + jsonString = """ + {"id":null,"title":null,"example":null,"type":"object","description":null,"items":null,"properties":{"array":{"id":null,"title":null,"example":null,"type":"array","description":null,"items":[{"id":null,"title":null,"example":"","type":"string","description":"","items":null,"properties":null,"additionalProperties":null,"required":null,"defaultValue":"默认值","pattern":null,"maxLength":4,"minLength":0,"minimum":null,"maximum":null,"maxItems":null,"minItems":null,"format":null,"enumValues":null,"enable":true},{"id":null,"title":null,"example":"","type":"number","description":"","items":null,"properties":null,"additionalProperties":null,"required":null,"defaultValue":"","pattern":null,"maxLength":null,"minLength":null,"minimum":null,"maximum":null,"maxItems":null,"minItems":null,"format":null,"enumValues":null,"enable":true}],"properties":null,"additionalProperties":null,"required":null,"defaultValue":null,"pattern":null,"maxLength":null,"minLength":null,"minimum":null,"maximum":null,"maxItems":null,"minItems":null,"format":null,"enumValues":null,"enable":true},"string":{"id":null,"title":null,"example":"","type":"string","description":"","items":null,"properties":null,"additionalProperties":null,"required":null,"defaultValue":"","pattern":"[A-Z0-9_]+","maxLength":5,"minLength":1,"minimum":null,"maximum":null,"maxItems":null,"minItems":null,"format":null,"enumValues":null,"enable":true},"int":{"id":null,"title":null,"example":"","type":"integer","description":"","items":null,"properties":null,"additionalProperties":null,"required":null,"defaultValue":3,"pattern":null,"maxLength":null,"minLength":null,"minimum":0,"maximum":4,"maxItems":null,"minItems":null,"format":null,"enumValues":null,"enable":true},"num":{"id":null,"title":null,"example":"","type":"number","description":"","items":null,"properties":null,"additionalProperties":null,"required":null,"defaultValue":2,"pattern":null,"maxLength":null,"minLength":null,"minimum":0,"maximum":5,"maxItems":null,"minItems":null,"format":null,"enumValues":null,"enable":true},"boolean":{"id":null,"title":null,"example":"","type":"boolean","description":"","items":null,"properties":null,"additionalProperties":null,"required":null,"defaultValue":"true","pattern":null,"maxLength":null,"minLength":null,"minimum":null,"maximum":null,"maxItems":null,"minItems":null,"format":null,"enumValues":null,"enable":true},"null":{"id":null,"title":null,"example":null,"type":"null","description":null,"items":null,"properties":null,"additionalProperties":null,"required":null,"defaultValue":null,"pattern":null,"maxLength":null,"minLength":null,"minimum":null,"maximum":null,"maxItems":null,"minItems":null,"format":null,"enumValues":null,"enable":true}},"additionalProperties":null,"required":null,"defaultValue":null,"pattern":null,"maxLength":null,"minLength":null,"minimum":null,"maximum":null,"maxItems":null,"minItems":null,"format":null,"enumValues":null,"enable":true} + """; + requestPostWithOk(JSON_SCHEMA_AUTO_GENERATE, JSON.parseObject(jsonString, JsonSchemaItem.class)); + } + @Test @Order(20) public void testGetRef() throws Exception { diff --git a/backend/services/project-management/src/main/java/io/metersphere/project/constants/PropertyConstant.java b/backend/services/project-management/src/main/java/io/metersphere/project/constants/PropertyConstant.java index 4050012ef6..048d88aa60 100644 --- a/backend/services/project-management/src/main/java/io/metersphere/project/constants/PropertyConstant.java +++ b/backend/services/project-management/src/main/java/io/metersphere/project/constants/PropertyConstant.java @@ -22,5 +22,12 @@ public class PropertyConstant { public final static String MOCK = "mock"; public final static String BODY_TYPE = "bodyType"; public final static String PARAM_TYPE = "paramType"; + public final static String ENUM_VALUES = "enumValues"; + public final static String DEFAULT_VALUE = "defaultValue"; + public final static String PATTERN = "pattern"; + public final static String MAX_LENGTH = "maxLength"; + public final static String MIN_LENGTH = "minLength"; + public final static String MINIMUM = "minimum"; + public final static String MAXIMUM = "maximum"; } diff --git a/frontend/src/api/modules/api-test/management.ts b/frontend/src/api/modules/api-test/management.ts index c2af6bde7e..1b87d6b20c 100644 --- a/frontend/src/api/modules/api-test/management.ts +++ b/frontend/src/api/modules/api-test/management.ts @@ -55,6 +55,7 @@ import { GetTrashModuleCountUrl, GetTrashModuleTreeUrl, ImportDefinitionUrl, + JsonSchemaAutoGenerateUrl, MockDetailUrl, MoveModuleUrl, OperationHistoryUrl, @@ -308,6 +309,11 @@ export function convertJsonSchemaToJson(data: JsonSchema) { return MSR.post({ url: ConvertJsonSchemaToJsonUrl, data }); } +// json-schema 生成测试数据 +export function jsonSchemaAutoGenerate(data: JsonSchema) { + return MSR.post({ url: JsonSchemaAutoGenerateUrl, data }); +} + /** * Mock */ diff --git a/frontend/src/api/requrls/api-test/management.ts b/frontend/src/api/requrls/api-test/management.ts index 70ce2a0795..999d40eedb 100644 --- a/frontend/src/api/requrls/api-test/management.ts +++ b/frontend/src/api/requrls/api-test/management.ts @@ -36,7 +36,8 @@ export const OperationHistoryUrl = '/api/definition/operation-history'; // 接 export const SaveOperationHistoryUrl = '/api/definition/operation-history/save'; // 接口定义-另存变更历史为指定版本 export const RecoverOperationHistoryUrl = '/api/definition/operation-history/recover'; // 接口定义-变更历史恢复 export const DefinitionReferenceUrl = '/api/definition/get-reference'; // 获取接口引用关系 -export const ConvertJsonSchemaToJsonUrl = '/api/definition/preview'; // 将json-schema转换为 json 数据 +export const ConvertJsonSchemaToJsonUrl = '/api/definition/json-schema/preview'; // 将json-schema转换为 json 数据 +export const JsonSchemaAutoGenerateUrl = '/api/definition/json-schema/auto-generate'; // 将json-schema转换为 json 数据 /** * Mock diff --git a/frontend/src/components/pure/ms-json-schema/index.vue b/frontend/src/components/pure/ms-json-schema/index.vue index e9d9f20472..7ffabca299 100644 --- a/frontend/src/components/pure/ms-json-schema/index.vue +++ b/frontend/src/components/pure/ms-json-schema/index.vue @@ -427,7 +427,7 @@