fix(测试计划): json-schema预览结果有误

This commit is contained in:
AgAngle 2024-07-05 17:49:25 +08:00 committed by jianxing
parent 64d813a5ef
commit 849a108af4
9 changed files with 151 additions and 576 deletions

View File

@ -15,12 +15,10 @@ import io.metersphere.api.service.definition.ApiDefinitionImportService;
import io.metersphere.api.service.definition.ApiDefinitionLogService; import io.metersphere.api.service.definition.ApiDefinitionLogService;
import io.metersphere.api.service.definition.ApiDefinitionNoticeService; import io.metersphere.api.service.definition.ApiDefinitionNoticeService;
import io.metersphere.api.service.definition.ApiDefinitionService; import io.metersphere.api.service.definition.ApiDefinitionService;
import io.metersphere.api.utils.JsonSchemaBuilder;
import io.metersphere.project.service.FileModuleService; import io.metersphere.project.service.FileModuleService;
import io.metersphere.sdk.constants.DefaultRepositoryDir; import io.metersphere.sdk.constants.DefaultRepositoryDir;
import io.metersphere.sdk.constants.PermissionConstants; import io.metersphere.sdk.constants.PermissionConstants;
import io.metersphere.sdk.dto.api.task.TaskRequestDTO; import io.metersphere.sdk.dto.api.task.TaskRequestDTO;
import io.metersphere.sdk.util.JSON;
import io.metersphere.system.dto.OperationHistoryDTO; import io.metersphere.system.dto.OperationHistoryDTO;
import io.metersphere.system.dto.request.OperationHistoryRequest; import io.metersphere.system.dto.request.OperationHistoryRequest;
import io.metersphere.system.dto.request.OperationHistoryVersionRequest; import io.metersphere.system.dto.request.OperationHistoryVersionRequest;
@ -284,7 +282,7 @@ public class ApiDefinitionController {
@Operation(summary = "接口测试-接口管理-接口-json-schema-预览") @Operation(summary = "接口测试-接口管理-接口-json-schema-预览")
@RequiresPermissions(PermissionConstants.PROJECT_API_DEFINITION_READ) @RequiresPermissions(PermissionConstants.PROJECT_API_DEFINITION_READ)
public String preview(@RequestBody JsonSchemaItem jsonSchemaItem) { public String preview(@RequestBody JsonSchemaItem jsonSchemaItem) {
return JsonSchemaBuilder.preview(JSON.toJSONString(jsonSchemaItem)); return apiDefinitionService.preview(jsonSchemaItem);
} }
@PostMapping("/debug") @PostMapping("/debug")

View File

@ -11,6 +11,11 @@ import lombok.Data;
*/ */
@Data @Data
public class JsonBody { public class JsonBody {
/**
* 是否 json-schema
* 默认false
*/
private Boolean enableJsonSchema = false;
/** /**
* json 参数值 * json 参数值
*/ */

View File

@ -11,12 +11,15 @@ import io.metersphere.api.dto.debug.ApiResourceRunRequest;
import io.metersphere.api.dto.definition.*; import io.metersphere.api.dto.definition.*;
import io.metersphere.api.dto.request.ApiEditPosRequest; import io.metersphere.api.dto.request.ApiEditPosRequest;
import io.metersphere.api.dto.request.ApiTransferRequest; import io.metersphere.api.dto.request.ApiTransferRequest;
import io.metersphere.api.dto.schema.JsonSchemaItem;
import io.metersphere.api.mapper.*; import io.metersphere.api.mapper.*;
import io.metersphere.api.service.ApiCommonService; import io.metersphere.api.service.ApiCommonService;
import io.metersphere.api.service.ApiExecuteService; import io.metersphere.api.service.ApiExecuteService;
import io.metersphere.api.service.ApiFileResourceService; import io.metersphere.api.service.ApiFileResourceService;
import io.metersphere.api.utils.ApiDataUtils; import io.metersphere.api.utils.ApiDataUtils;
import io.metersphere.api.utils.JsonSchemaBuilder;
import io.metersphere.plugin.api.spi.AbstractMsTestElement; import io.metersphere.plugin.api.spi.AbstractMsTestElement;
import io.metersphere.project.constants.PropertyConstant;
import io.metersphere.project.domain.FileAssociation; import io.metersphere.project.domain.FileAssociation;
import io.metersphere.project.domain.FileMetadata; import io.metersphere.project.domain.FileMetadata;
import io.metersphere.project.dto.MoveNodeSortDTO; import io.metersphere.project.dto.MoveNodeSortDTO;
@ -51,6 +54,7 @@ import io.metersphere.system.utils.CustomFieldUtils;
import io.metersphere.system.utils.ServiceUtils; import io.metersphere.system.utils.ServiceUtils;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.session.ExecutorType; import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSession;
@ -1184,4 +1188,57 @@ public class ApiDefinitionService extends MoveNodeService {
public List<ReferenceDTO> getReference(ReferenceRequest request) { public List<ReferenceDTO> getReference(ReferenceRequest request) {
return extApiDefinitionMapper.getReference(request); return extApiDefinitionMapper.getReference(request);
} }
public String preview(JsonSchemaItem jsonSchemaItem) {
if (BooleanUtils.isFalse(jsonSchemaItem.getEnable())) {
return "{}";
}
filterDisableItem(jsonSchemaItem);
return JsonSchemaBuilder.preview(JSON.toJSONString(jsonSchemaItem));
}
private void filterDisableItem(JsonSchemaItem jsonSchemaItem) {
if (isObjectItem(jsonSchemaItem)) {
Map<String, JsonSchemaItem> properties = jsonSchemaItem.getProperties();
if (properties != null) {
Iterator<String> iterator = properties.keySet().iterator();
while (iterator.hasNext()) {
String key = iterator.next();
JsonSchemaItem item = properties.get(key);
if (item == null) {
continue;
}
if (BooleanUtils.isFalse(item.getEnable())) {
iterator.remove();
} else if (isObjectItem(jsonSchemaItem) || isArrayItem(jsonSchemaItem)) {
filterDisableItem(item);
}
}
}
} else if (isArrayItem(jsonSchemaItem)) {
List<JsonSchemaItem> items = jsonSchemaItem.getItems();
if (items != null) {
Iterator<JsonSchemaItem> iterator = items.iterator();
while (iterator.hasNext()) {
JsonSchemaItem item = iterator.next();
if (item == null) {
continue;
}
if (BooleanUtils.isFalse(item.getEnable())) {
iterator.remove();
} else if (isObjectItem(jsonSchemaItem) || isArrayItem(jsonSchemaItem)){
filterDisableItem(item);
}
}
}
}
}
private boolean isArrayItem(JsonSchemaItem jsonSchemaItem) {
return StringUtils.equals(jsonSchemaItem.getType(), PropertyConstant.ARRAY);
}
private boolean isObjectItem(JsonSchemaItem jsonSchemaItem) {
return StringUtils.equals(jsonSchemaItem.getType(), PropertyConstant.OBJECT);
}
} }

View File

@ -4,11 +4,11 @@ import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.*; import com.fasterxml.jackson.databind.node.*;
import io.metersphere.jmeter.mock.Mock; import io.metersphere.jmeter.mock.Mock;
import io.metersphere.project.constants.PropertyConstant; import io.metersphere.project.constants.PropertyConstant;
import io.metersphere.sdk.util.LogUtils;
import org.apache.commons.collections4.MapUtils; import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.math.BigDecimal;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.regex.Matcher; import java.util.regex.Matcher;
@ -17,23 +17,18 @@ import java.util.regex.Pattern;
public class JsonSchemaBuilder { public class JsonSchemaBuilder {
public static String jsonSchemaToJson(String jsonSchemaString) { public static String jsonSchemaToJson(String jsonSchemaString) {
try { // 解析 JSON Schema 字符串为 JsonNode
// 解析 JSON Schema 字符串为 JsonNode JsonNode jsonSchemaNode = ApiDataUtils.readTree(jsonSchemaString);
JsonNode jsonSchemaNode = ApiDataUtils.readTree(jsonSchemaString); Map<String, String> processMap = new HashMap<>();
Map<String, String> processMap = new HashMap<>(); // 生成符合 JSON Schema JSON
// 生成符合 JSON Schema JSON JsonNode jsonNode = generateJson(jsonSchemaNode, processMap);
JsonNode jsonNode = generateJson(jsonSchemaNode, processMap); String jsonString = ApiDataUtils.writerWithDefaultPrettyPrinter(jsonNode);
String jsonString = ApiDataUtils.writerWithDefaultPrettyPrinter(jsonNode); if (MapUtils.isNotEmpty(processMap)) {
if (MapUtils.isNotEmpty(processMap)) { for (String str : processMap.keySet()) {
for (String str : processMap.keySet()) { jsonString = jsonString.replace(str, processMap.get(str));
jsonString = jsonString.replace(str, processMap.get(str));
}
} }
return jsonString;
} catch (Exception exception) {
LogUtils.error("jsonSchemaToJson error", exception);
return jsonSchemaString;
} }
return jsonString;
} }
private static JsonNode generateJson(JsonNode jsonSchemaNode, Map<String, String> processMap) { private static JsonNode generateJson(JsonNode jsonSchemaNode, Map<String, String> processMap) {
@ -42,7 +37,7 @@ public class JsonSchemaBuilder {
if (jsonSchemaNode instanceof NullNode) { if (jsonSchemaNode instanceof NullNode) {
return NullNode.getInstance(); return NullNode.getInstance();
} }
String type = jsonSchemaNode.get(PropertyConstant.TYPE).asText(); String type = jsonSchemaNode.get(PropertyConstant.TYPE) == null ? StringUtils.EMPTY : jsonSchemaNode.get(PropertyConstant.TYPE).asText();
if (StringUtils.equals(type, PropertyConstant.OBJECT)) { if (StringUtils.equals(type, PropertyConstant.OBJECT)) {
JsonNode propertiesNode = jsonSchemaNode.get(PropertyConstant.PROPERTIES); JsonNode propertiesNode = jsonSchemaNode.get(PropertyConstant.PROPERTIES);
// 遍历 properties // 遍历 properties
@ -57,10 +52,10 @@ public class JsonSchemaBuilder {
}); });
} }
} else if (StringUtils.equals(type, PropertyConstant.ARRAY)) { } else if (StringUtils.equals(type, PropertyConstant.ARRAY)) {
JsonNode itemsNode = jsonSchemaNode.get(PropertyConstant.ITEMS); JsonNode items = jsonSchemaNode.get(PropertyConstant.ITEMS);
if (itemsNode != null) { if (items != null) {
ArrayNode arrayNode = new ArrayNode(JsonNodeFactory.instance); ArrayNode arrayNode = new ArrayNode(JsonNodeFactory.instance);
arrayNode.add(generateValue(null, itemsNode, processMap)); items.forEach(item -> arrayNode.add(generateValue(null, item, processMap)));
return arrayNode; return arrayNode;
} }
} }
@ -72,8 +67,8 @@ public class JsonSchemaBuilder {
if (propertyNode instanceof NullNode) { if (propertyNode instanceof NullNode) {
return NullNode.getInstance(); return NullNode.getInstance();
} }
String type = propertyNode.get(PropertyConstant.TYPE).asText(); String type = propertyNode.get(PropertyConstant.TYPE) == null ? StringUtils.EMPTY : propertyNode.get(PropertyConstant.TYPE).asText();
String value = propertyNode.get(PropertyConstant.EXAMPLE).asText(); String value = propertyNode.get(PropertyConstant.EXAMPLE) == null ? StringUtils.EMPTY : propertyNode.get(PropertyConstant.EXAMPLE).asText();
return switch (type) { return switch (type) {
case PropertyConstant.STRING -> case PropertyConstant.STRING ->
new TextNode(!StringUtils.equals(value, PropertyConstant.NULL) ? value : "string"); new TextNode(!StringUtils.equals(value, PropertyConstant.NULL) ? value : "string");
@ -88,7 +83,11 @@ public class JsonSchemaBuilder {
if (isVariable(value)) { if (isVariable(value)) {
yield getJsonNodes(propertyName, processMap, value); yield getJsonNodes(propertyName, processMap, value);
} else { } else {
yield new DecimalNode(propertyNode.get(PropertyConstant.EXAMPLE).decimalValue()); try {
yield new DecimalNode(new BigDecimal(propertyNode.get(PropertyConstant.EXAMPLE).asText()));
} catch (Exception e) {
yield new DecimalNode(propertyNode.get(PropertyConstant.EXAMPLE).decimalValue());
}
} }
} }
case PropertyConstant.BOOLEAN -> { case PropertyConstant.BOOLEAN -> {
@ -102,9 +101,9 @@ public class JsonSchemaBuilder {
case PropertyConstant.OBJECT -> generateJson(propertyNode, processMap); case PropertyConstant.OBJECT -> generateJson(propertyNode, processMap);
case PropertyConstant.ARRAY -> { case PropertyConstant.ARRAY -> {
ArrayNode arrayNode = new ArrayNode(JsonNodeFactory.instance); ArrayNode arrayNode = new ArrayNode(JsonNodeFactory.instance);
JsonNode itemsNode = propertyNode.get(PropertyConstant.ITEMS); JsonNode items = propertyNode.get(PropertyConstant.ITEMS);
if (itemsNode != null) { if (items != null) {
arrayNode.add(generateValue(null, itemsNode, processMap)); items.forEach(item -> arrayNode.add(generateValue(null, item, processMap)));
} }
yield arrayNode; yield arrayNode;
} }

View File

@ -18,5 +18,6 @@ public class PropertyConstant {
public final static String TYPE = "type"; public final static String TYPE = "type";
public final static String ITEMS = "items"; public final static String ITEMS = "items";
public final static String PROPERTIES = "properties"; public final static String PROPERTIES = "properties";
public final static String ENABLE = "enable";
} }

View File

@ -181,20 +181,13 @@
<div>{{ t('ms.minders.failStop') }}</div> <div>{{ t('ms.minders.failStop') }}</div>
</div> </div>
</a-form-item> </a-form-item>
<!-- 暂时不上 --> <a-form-item class="hidden-item">
<!-- <a-form-item class="hidden-item">
<div class="flex items-center gap-[8px]"> <div class="flex items-center gap-[8px]">
<a-switch v-model:model-value="configForm.retryOnFail" size="small"></a-switch> <a-switch v-model:model-value="configForm.retryOnFail" size="small"></a-switch>
<div>{{ t('ms.minders.failRetry') }}</div> <div>{{ t('ms.minders.failRetry') }}</div>
</div> </div>
</a-form-item> </a-form-item>
<template v-if="configForm.retryOnFail"> <template v-if="configForm.retryOnFail">
<a-form-item v-if="configForm.type === PlanMinderCollectionType.SCENARIO" class="hidden-item">
<a-radio-group v-model:model-value="configForm.retryType">
<a-radio :value="FailRetry.STEP">{{ t('ms.minders.stepRetry') }}</a-radio>
<a-radio :value="FailRetry.SCENARIO">{{ t('ms.minders.scenarioRetry') }}</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item> <a-form-item>
<template #label> <template #label>
<div class="flex items-center"> <div class="flex items-center">
@ -229,7 +222,7 @@
class="w-[120px]" class="w-[120px]"
></a-input-number> ></a-input-number>
</a-form-item> </a-form-item>
</template> --> </template>
</template> </template>
</a-form> </a-form>
<div v-if="hasEditPermission" class="flex items-center gap-[12px] bg-white pb-[16px]"> <div v-if="hasEditPermission" class="flex items-center gap-[12px] bg-white pb-[16px]">

View File

@ -9,11 +9,6 @@ export enum RunMode {
PARALLEL = 'PARALLEL', // 并行 PARALLEL = 'PARALLEL', // 并行
} }
export enum FailRetry {
STEP = 'STEP',
SCENARIO = 'SCENARIO',
}
// 功能FUNCTIONAL_CASE/接口定义API/接口用例API_CASE/场景SCENARIO_CASE // 功能FUNCTIONAL_CASE/接口定义API/接口用例API_CASE/场景SCENARIO_CASE
export enum PlanMinderAssociateType { export enum PlanMinderAssociateType {
FUNCTIONAL_CASE = 'FUNCTIONAL', FUNCTIONAL_CASE = 'FUNCTIONAL',

View File

@ -6,7 +6,6 @@ import type { TableQueryParams } from '@/models/common';
import { BatchApiParams, DragSortParams } from '@/models/common'; import { BatchApiParams, DragSortParams } from '@/models/common';
import { LastExecuteResults } from '@/enums/caseEnum'; import { LastExecuteResults } from '@/enums/caseEnum';
import { import {
type FailRetry,
type PlanMinderAssociateType, type PlanMinderAssociateType,
type PlanMinderCollectionType, type PlanMinderCollectionType,
type RunMode, type RunMode,
@ -397,7 +396,6 @@ export interface PlanMinderNodeData extends MinderJsonNodeData {
environmentId: string; environmentId: string;
testResourcePoolId: string; testResourcePoolId: string;
retryOnFail: boolean; retryOnFail: boolean;
retryType: FailRetry; // 失败重试类型(步骤/场景)
retryTimes: number; retryTimes: number;
retryInterval: number; retryInterval: number;
stopOnFail: boolean; stopOnFail: boolean;