fix(测试计划): json-schema预览结果有误
This commit is contained in:
parent
64d813a5ef
commit
849a108af4
|
@ -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")
|
||||||
|
|
|
@ -11,6 +11,11 @@ import lombok.Data;
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
public class JsonBody {
|
public class JsonBody {
|
||||||
|
/**
|
||||||
|
* 是否 json-schema
|
||||||
|
* 默认false
|
||||||
|
*/
|
||||||
|
private Boolean enableJsonSchema = false;
|
||||||
/**
|
/**
|
||||||
* json 参数值
|
* json 参数值
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -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";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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]">
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in New Issue