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

View File

@ -11,6 +11,11 @@ import lombok.Data;
*/
@Data
public class JsonBody {
/**
* 是否 json-schema
* 默认false
*/
private Boolean enableJsonSchema = false;
/**
* 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.request.ApiEditPosRequest;
import io.metersphere.api.dto.request.ApiTransferRequest;
import io.metersphere.api.dto.schema.JsonSchemaItem;
import io.metersphere.api.mapper.*;
import io.metersphere.api.service.ApiCommonService;
import io.metersphere.api.service.ApiExecuteService;
import io.metersphere.api.service.ApiFileResourceService;
import io.metersphere.api.utils.ApiDataUtils;
import io.metersphere.api.utils.JsonSchemaBuilder;
import io.metersphere.plugin.api.spi.AbstractMsTestElement;
import io.metersphere.project.constants.PropertyConstant;
import io.metersphere.project.domain.FileAssociation;
import io.metersphere.project.domain.FileMetadata;
import io.metersphere.project.dto.MoveNodeSortDTO;
@ -51,6 +54,7 @@ import io.metersphere.system.utils.CustomFieldUtils;
import io.metersphere.system.utils.ServiceUtils;
import jakarta.annotation.Resource;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession;
@ -1184,4 +1188,57 @@ public class ApiDefinitionService extends MoveNodeService {
public List<ReferenceDTO> getReference(ReferenceRequest 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 io.metersphere.jmeter.mock.Mock;
import io.metersphere.project.constants.PropertyConstant;
import io.metersphere.sdk.util.LogUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
@ -17,23 +17,18 @@ import java.util.regex.Pattern;
public class JsonSchemaBuilder {
public static String jsonSchemaToJson(String jsonSchemaString) {
try {
// 解析 JSON Schema 字符串为 JsonNode
JsonNode jsonSchemaNode = ApiDataUtils.readTree(jsonSchemaString);
Map<String, String> processMap = new HashMap<>();
// 生成符合 JSON Schema JSON
JsonNode jsonNode = generateJson(jsonSchemaNode, processMap);
String jsonString = ApiDataUtils.writerWithDefaultPrettyPrinter(jsonNode);
if (MapUtils.isNotEmpty(processMap)) {
for (String str : processMap.keySet()) {
jsonString = jsonString.replace(str, processMap.get(str));
}
// 解析 JSON Schema 字符串为 JsonNode
JsonNode jsonSchemaNode = ApiDataUtils.readTree(jsonSchemaString);
Map<String, String> processMap = new HashMap<>();
// 生成符合 JSON Schema JSON
JsonNode jsonNode = generateJson(jsonSchemaNode, processMap);
String jsonString = ApiDataUtils.writerWithDefaultPrettyPrinter(jsonNode);
if (MapUtils.isNotEmpty(processMap)) {
for (String str : processMap.keySet()) {
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) {
@ -42,7 +37,7 @@ public class JsonSchemaBuilder {
if (jsonSchemaNode instanceof NullNode) {
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)) {
JsonNode propertiesNode = jsonSchemaNode.get(PropertyConstant.PROPERTIES);
// 遍历 properties
@ -57,10 +52,10 @@ public class JsonSchemaBuilder {
});
}
} else if (StringUtils.equals(type, PropertyConstant.ARRAY)) {
JsonNode itemsNode = jsonSchemaNode.get(PropertyConstant.ITEMS);
if (itemsNode != null) {
JsonNode items = jsonSchemaNode.get(PropertyConstant.ITEMS);
if (items != null) {
ArrayNode arrayNode = new ArrayNode(JsonNodeFactory.instance);
arrayNode.add(generateValue(null, itemsNode, processMap));
items.forEach(item -> arrayNode.add(generateValue(null, item, processMap)));
return arrayNode;
}
}
@ -72,8 +67,8 @@ public class JsonSchemaBuilder {
if (propertyNode instanceof NullNode) {
return NullNode.getInstance();
}
String type = propertyNode.get(PropertyConstant.TYPE).asText();
String value = propertyNode.get(PropertyConstant.EXAMPLE).asText();
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();
return switch (type) {
case PropertyConstant.STRING ->
new TextNode(!StringUtils.equals(value, PropertyConstant.NULL) ? value : "string");
@ -88,7 +83,11 @@ public class JsonSchemaBuilder {
if (isVariable(value)) {
yield getJsonNodes(propertyName, processMap, value);
} 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 -> {
@ -102,9 +101,9 @@ public class JsonSchemaBuilder {
case PropertyConstant.OBJECT -> generateJson(propertyNode, processMap);
case PropertyConstant.ARRAY -> {
ArrayNode arrayNode = new ArrayNode(JsonNodeFactory.instance);
JsonNode itemsNode = propertyNode.get(PropertyConstant.ITEMS);
if (itemsNode != null) {
arrayNode.add(generateValue(null, itemsNode, processMap));
JsonNode items = propertyNode.get(PropertyConstant.ITEMS);
if (items != null) {
items.forEach(item -> arrayNode.add(generateValue(null, item, processMap)));
}
yield arrayNode;
}

View File

@ -18,5 +18,6 @@ public class PropertyConstant {
public final static String TYPE = "type";
public final static String ITEMS = "items";
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>
</a-form-item>
<!-- 暂时不上 -->
<!-- <a-form-item class="hidden-item">
<a-form-item class="hidden-item">
<div class="flex items-center gap-[8px]">
<a-switch v-model:model-value="configForm.retryOnFail" size="small"></a-switch>
<div>{{ t('ms.minders.failRetry') }}</div>
</div>
</a-form-item>
<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>
<template #label>
<div class="flex items-center">
@ -229,7 +222,7 @@
class="w-[120px]"
></a-input-number>
</a-form-item>
</template> -->
</template>
</template>
</a-form>
<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', // 并行
}
export enum FailRetry {
STEP = 'STEP',
SCENARIO = 'SCENARIO',
}
// 功能FUNCTIONAL_CASE/接口定义API/接口用例API_CASE/场景SCENARIO_CASE
export enum PlanMinderAssociateType {
FUNCTIONAL_CASE = 'FUNCTIONAL',

View File

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