feat(接口测试): 接口调试增删改查接口实现

This commit is contained in:
AgAngle 2023-11-07 20:44:04 +08:00 committed by Craftsman
parent 1e7070bce7
commit 516559506d
57 changed files with 1522 additions and 39 deletions

View File

@ -8,8 +8,9 @@ CREATE TABLE api_debug(
`protocol` VARCHAR(20) NOT NULL COMMENT '接口协议' , `protocol` VARCHAR(20) NOT NULL COMMENT '接口协议' ,
`method` VARCHAR(20) COMMENT 'http协议类型post/get/其它协议则是协议名(mqtt)' , `method` VARCHAR(20) COMMENT 'http协议类型post/get/其它协议则是协议名(mqtt)' ,
`path` VARCHAR(500) COMMENT 'http协议路径/其它协议则为空' , `path` VARCHAR(500) COMMENT 'http协议路径/其它协议则为空' ,
`pos` BIGINT NOT NULL DEFAULT 0 COMMENT '自定义排序' ,
`project_id` VARCHAR(50) NOT NULL COMMENT '项目fk' , `project_id` VARCHAR(50) NOT NULL COMMENT '项目fk' ,
`module_id` VARCHAR(50) NOT NULL DEFAULT 'root' COMMENT '模块fk' , `module_id` VARCHAR(50) NOT NULL DEFAULT 'root' COMMENT '模块fk' ,
`create_time` BIGINT NOT NULL COMMENT '创建时间' , `create_time` BIGINT NOT NULL COMMENT '创建时间' ,
`create_user` VARCHAR(50) NOT NULL COMMENT '创建人' , `create_user` VARCHAR(50) NOT NULL COMMENT '创建人' ,
`update_time` BIGINT NOT NULL COMMENT '修改时间' , `update_time` BIGINT NOT NULL COMMENT '修改时间' ,
@ -86,7 +87,9 @@ CREATE TABLE api_definition
`delete_time` BIGINT COMMENT '删除时间', `delete_time` BIGINT COMMENT '删除时间',
`deleted` BIT(1) NOT NULL DEFAULT 0 COMMENT '删除状态', `deleted` BIT(1) NOT NULL DEFAULT 0 COMMENT '删除状态',
PRIMARY KEY (id) PRIMARY KEY (id)
) COMMENT = '接口定义'; ) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4
COLLATE = utf8mb4_general_ci COMMENT = '接口定义';
CREATE INDEX idx_project_id ON api_definition(project_id); CREATE INDEX idx_project_id ON api_definition(project_id);

View File

@ -2,5 +2,8 @@ package io.metersphere.plugin.api.spi;
import io.metersphere.plugin.sdk.spi.AbstractMsPlugin; import io.metersphere.plugin.sdk.spi.AbstractMsPlugin;
/**
* 接口插件抽象类
*/
public abstract class AbstractApiPlugin extends AbstractMsPlugin { public abstract class AbstractApiPlugin extends AbstractMsPlugin {
} }

View File

@ -0,0 +1,15 @@
package io.metersphere.plugin.api.spi;
/**
* 接口协议插件抽象类
* @Author: jianxing
* @CreateTime: 2023-11-06 11:10
*/
public abstract class AbstractProtocolPlugin extends AbstractApiPlugin {
/**
* 返回协议名称
* @return
*/
abstract public String getProtocol();
}

View File

@ -222,11 +222,11 @@ public class PermissionConstants {
/*------ start: API_DEBUG ------*/ /*------ start: API_DEBUG ------*/
public static final String PROJECT_API_DEBUG_READ = "PROJECT_API_DEBUG:READ"; public static final String PROJECT_API_DEBUG_READ = "PROJECT_API_DEBUG:READ";
public static final String PROJECT_API_DEBUG_READ_ADD = "PROJECT_API_DEBUG:READ+ADD"; public static final String PROJECT_API_DEBUG_ADD = "PROJECT_API_DEBUG:READ+ADD";
public static final String PROJECT_API_DEBUG_READ_UPDATE = "PROJECT_API_DEBUG:READ+UPDATE"; public static final String PROJECT_API_DEBUG_UPDATE = "PROJECT_API_DEBUG:READ+UPDATE";
public static final String PROJECT_API_DEBUG_READ_DELETE = "PROJECT_API_DEBUG:READ+DELETE"; public static final String PROJECT_API_DEBUG_DELETE = "PROJECT_API_DEBUG:READ+DELETE";
public static final String PROJECT_API_DEBUG_READ_IMPORT = "PROJECT_API_DEBUG:READ+IMPORT"; public static final String PROJECT_API_DEBUG_IMPORT = "PROJECT_API_DEBUG:READ+IMPORT";
public static final String PROJECT_API_DEBUG_READ_EXECUTE = "PROJECT_API_DEBUG:READ+EXECUTE"; public static final String PROJECT_API_DEBUG_EXECUTE = "PROJECT_API_DEBUG:READ+EXECUTE";
/*------ end: API_DEBUG ------*/ /*------ end: API_DEBUG ------*/
/*------ start: BUG ------*/ /*------ start: BUG ------*/

View File

@ -0,0 +1,12 @@
package io.metersphere.sdk.dto.api.request.http;
import lombok.Data;
/**
* @Author: jianxing
* @CreateTime: 2023-11-06 16:59
*/
@Data
public class Header extends KeyValueParam {
}

View File

@ -0,0 +1,27 @@
package io.metersphere.sdk.dto.api.request.http;
import lombok.Data;
/**
* @Author: jianxing
* @CreateTime: 2023-11-06 17:27
*/
@Data
public class KeyValueParam {
/**
*
*/
private String key;
/**
*
*/
private String value;
/**
* 是否启用
*/
private Boolean enable = true;
/**
* 描述
*/
private String description;
}

View File

@ -0,0 +1,16 @@
package io.metersphere.sdk.dto.api.request.http;
import lombok.Data;
/**
* @Author: jianxing
* @CreateTime: 2023-11-07 10:47
*/
@Data
public class MsHTTPConfig {
private Long connectTimeout;
private Long responseTimeout;
private String certificateAlias;
private Boolean followRedirects = true;
private Boolean autoRedirects = false;
}

View File

@ -1,11 +1,62 @@
package io.metersphere.sdk.dto.api.request.http; package io.metersphere.sdk.dto.api.request.http;
import io.metersphere.plugin.api.spi.AbstractMsTestElement; import io.metersphere.plugin.api.spi.AbstractMsTestElement;
import io.metersphere.sdk.dto.api.request.http.auth.HTTPAuth;
import io.metersphere.sdk.dto.api.request.http.body.Body;
import io.metersphere.sdk.dto.api.request.processors.MsProcessor;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import java.util.List;
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class MsHTTPElement extends AbstractMsTestElement { public class MsHTTPElement extends AbstractMsTestElement {
private String domain; // todo 完善字段校验
/**
* 完整请求地址
*/
private String url;
/**
* 接口定义和用例的请求路径
*/
private String path;
/**
* 请求方法
*/
private String method;
/**
* 请求体
*/
private Body body;
/**
* 请求头
*/
private List<Header> headers;
/**
* rest参数
*/
private List<RestParam> rest;
/**
* query参数
*/
private List<QueryParam> query;
/**
* 其他配置
*/
private MsHTTPConfig otherConfig;
/**
* 认证配置
*/
private HTTPAuth authConfig;
/**
* 前置处理器
*/
private List<MsProcessor> preProcessors;
/**
* 后置处理器
*/
private List<MsProcessor> postProcessors;
// todo 断言和提取 待设计
} }

View File

@ -0,0 +1,22 @@
package io.metersphere.sdk.dto.api.request.http;
import lombok.Data;
/**
* @Author: jianxing
* @CreateTime: 2023-11-06 16:59
*/
@Data
public class QueryParam extends KeyValueParam {
/**
* 参数类型
* 默认string可选integernumberarray
* todo
*/
private String paramType;
/**
* 是否必填
*/
private Boolean required = false;
}

View File

@ -0,0 +1,21 @@
package io.metersphere.sdk.dto.api.request.http;
import lombok.Data;
/**
* @Author: jianxing
* @CreateTime: 2023-11-06 16:59
*/
@Data
public class RestParam extends KeyValueParam {
/**
* 参数类型
* 默认string可选integernumberarray
* todo
*/
private String paramType;
/**
* 是否必填
*/
private Boolean required = false;
}

View File

@ -0,0 +1,15 @@
package io.metersphere.sdk.dto.api.request.http.auth;
import com.fasterxml.jackson.annotation.JsonTypeName;
import lombok.Data;
/**
* @Author: jianxing
* @CreateTime: 2023-11-07 11:00
*/
@Data
@JsonTypeName("BASIC")
public class BasicAuth extends HTTPAuth {
private String userName;
private String password;
}

View File

@ -0,0 +1,15 @@
package io.metersphere.sdk.dto.api.request.http.auth;
import com.fasterxml.jackson.annotation.JsonTypeName;
import lombok.Data;
/**
* @Author: jianxing
* @CreateTime: 2023-11-07 11:00
*/
@Data
@JsonTypeName("DIGEST")
public class DigestAuth extends HTTPAuth {
private String userName;
private String password;
}

View File

@ -0,0 +1,19 @@
package io.metersphere.sdk.dto.api.request.http.auth;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import lombok.Data;
/**
* @Author: jianxing
* @CreateTime: 2023-11-07 11:00
*/
@Data
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "authType")
@JsonSubTypes({
@JsonSubTypes.Type(value = NoAuth.class),
@JsonSubTypes.Type(value = BasicAuth.class),
@JsonSubTypes.Type(value = DigestAuth.class),
})
public abstract class HTTPAuth {
}

View File

@ -0,0 +1,13 @@
package io.metersphere.sdk.dto.api.request.http.auth;
import com.fasterxml.jackson.annotation.JsonTypeName;
import lombok.Data;
/**
* @Author: jianxing
* @CreateTime: 2023-11-07 11:00
*/
@Data
@JsonTypeName("NONE")
public class NoAuth extends HTTPAuth {
}

View File

@ -0,0 +1,17 @@
package io.metersphere.sdk.dto.api.request.http.body;
import com.fasterxml.jackson.annotation.JsonTypeName;
import lombok.Data;
/**
* @Author: jianxing
* @CreateTime: 2023-11-06 18:25
*/
@Data
@JsonTypeName("BINARY")
public class BinaryBody extends Body {
// todo 如果fileName能直接定义到文件就不需要filePath
private String filePath;
private String fileName;
private String description;
}

View File

@ -0,0 +1,23 @@
package io.metersphere.sdk.dto.api.request.http.body;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import lombok.Data;
/**
* @Author: jianxing
* @CreateTime: 2023-11-06 16:59
*/
@Data
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "bodyType")
@JsonSubTypes({
@JsonSubTypes.Type(value = NoneBody.class),
@JsonSubTypes.Type(value = FormDataBody.class),
@JsonSubTypes.Type(value = WWWFormBody.class),
@JsonSubTypes.Type(value = JsonBody.class),
@JsonSubTypes.Type(value = XmlBody.class),
@JsonSubTypes.Type(value = RawBody.class),
@JsonSubTypes.Type(value = BinaryBody.class)
})
public abstract class Body {
}

View File

@ -0,0 +1,16 @@
package io.metersphere.sdk.dto.api.request.http.body;
import com.fasterxml.jackson.annotation.JsonTypeName;
import lombok.Data;
import java.util.List;
/**
* @Author: jianxing
* @CreateTime: 2023-11-06 16:59
*/
@Data
@JsonTypeName("FORM_DATA")
public class FormDataBody extends Body {
private List<FormDataKV> fromValues;
}

View File

@ -0,0 +1,18 @@
package io.metersphere.sdk.dto.api.request.http.body;
import io.metersphere.sdk.dto.api.request.http.KeyValueParam;
import lombok.Data;
/**
* @Author: jianxing
* @CreateTime: 2023-11-06 18:11
*/
@Data
public class FormDataKV extends KeyValueParam {
private String paramType;
private Boolean required = false;
private Integer minLength;
private Integer maxLength;
private String contentType;
private Boolean encode = false;
}

View File

@ -0,0 +1,14 @@
package io.metersphere.sdk.dto.api.request.http.body;
import com.fasterxml.jackson.annotation.JsonTypeName;
import lombok.Data;
/**
* @Author: jianxing
* @CreateTime: 2023-11-06 18:25
*/
@Data
@JsonTypeName("JSON")
public class JsonBody extends Body {
private String value;
}

View File

@ -0,0 +1,13 @@
package io.metersphere.sdk.dto.api.request.http.body;
import com.fasterxml.jackson.annotation.JsonTypeName;
import lombok.Data;
/**
* @Author: jianxing
* @CreateTime: 2023-11-06 18:25
*/
@Data
@JsonTypeName("NONE")
public class NoneBody extends Body {
}

View File

@ -0,0 +1,14 @@
package io.metersphere.sdk.dto.api.request.http.body;
import com.fasterxml.jackson.annotation.JsonTypeName;
import lombok.Data;
/**
* @Author: jianxing
* @CreateTime: 2023-11-06 18:25
*/
@Data
@JsonTypeName("RAW")
public class RawBody extends Body {
private String value;
}

View File

@ -0,0 +1,16 @@
package io.metersphere.sdk.dto.api.request.http.body;
import com.fasterxml.jackson.annotation.JsonTypeName;
import lombok.Data;
import java.util.List;
/**
* @Author: jianxing
* @CreateTime: 2023-11-06 16:59
*/
@Data
@JsonTypeName("WWW_FORM")
public class WWWFormBody extends Body {
private List<FormDataKV> fromValues;
}

View File

@ -0,0 +1,14 @@
package io.metersphere.sdk.dto.api.request.http.body;
import com.fasterxml.jackson.annotation.JsonTypeName;
import lombok.Data;
/**
* @Author: jianxing
* @CreateTime: 2023-11-06 18:25
*/
@Data
@JsonTypeName("XML")
public class XmlBody extends Body {
private String value;
}

View File

@ -0,0 +1,19 @@
package io.metersphere.sdk.dto.api.request.processors;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import lombok.Data;
/**
* @Author: jianxing
* @CreateTime: 2023-11-07 10:17
*/
@Data
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "processorType")
@JsonSubTypes({
@JsonSubTypes.Type(value = ScriptProcessor.class),
@JsonSubTypes.Type(value = SQLProcessor.class),
@JsonSubTypes.Type(value = TimeWaitingProcessor.class),
})
public abstract class MsProcessor {
}

View File

@ -0,0 +1,48 @@
package io.metersphere.sdk.dto.api.request.processors;
import com.fasterxml.jackson.annotation.JsonTypeName;
import io.metersphere.sdk.dto.api.request.http.KeyValueParam;
import lombok.Data;
import java.util.List;
/**
* @Author: jianxing
* @CreateTime: 2023-11-06 21:12
*/
@Data
@JsonTypeName("SQL")
public class SQLProcessor extends MsProcessor {
/**
* 脚本内容
*/
private String script;
/**
* 超时时间
*/
private long queryTimeout;
/**
* 存储结果
*/
private String resultVariable;
/**
* 按列存储
*/
private String variableNames;
/**
* 变量列表
*/
private List<KeyValueParam> variables;
/**
* 环境ID
*/
private String environmentId;
/**
* 数据源ID
*/
private String dataSourceId;
/**
* 是否启用
*/
private Boolean enable;
}

View File

@ -0,0 +1,17 @@
package io.metersphere.sdk.dto.api.request.processors;
import com.fasterxml.jackson.annotation.JsonTypeName;
import lombok.Data;
/**
* @Author: jianxing
* @CreateTime: 2023-11-06 21:12
*/
@Data
@JsonTypeName("SCRIPT")
public class ScriptProcessor extends MsProcessor {
private String script;
private String scriptLanguage;
private Boolean jsrEnable;
private Boolean enable;
}

View File

@ -0,0 +1,15 @@
package io.metersphere.sdk.dto.api.request.processors;
import com.fasterxml.jackson.annotation.JsonTypeName;
import lombok.Data;
/**
* @Author: jianxing
* @CreateTime: 2023-11-07 09:59
*/
@Data
@JsonTypeName("TIME_WAITING")
public class TimeWaitingProcessor extends MsProcessor {
private Integer delay;
private Boolean enable;
}

View File

@ -283,3 +283,6 @@ api_environment_config.id.not_blank=ID不能为空
api_environment_config.environment_id.length_range=环境ID长度必须在1-50之间 api_environment_config.environment_id.length_range=环境ID长度必须在1-50之间
api_environment_config.environment_id.not_blank=环境ID不能为空 api_environment_config.environment_id.not_blank=环境ID不能为空
api_module.not.exist=模块不存在 api_module.not.exist=模块不存在
permission.api.name=接口测试
api_debug_exist=接口已存在

View File

@ -267,6 +267,11 @@ api_debug.project_id.not_blank=Project ID cannot be blank
api_debug.project_id.length_range=Project ID length must be between 1-50 api_debug.project_id.length_range=Project ID length must be between 1-50
api_debug.module_id.not_blank=Module ID cannot be blank api_debug.module_id.not_blank=Module ID cannot be blank
api_debug.module_id.length_range=Module ID length must be between 1-50 api_debug.module_id.length_range=Module ID length must be between 1-50
api_debug.create_user.not_blank=The creator cannot be empty
api_debug.create_user.length_range=Creator length must be between {min}-{max}
api_debug.update_user.not_blank=Modifier cannot be blank
api_debug.update_user.length_range=Modifier length must be between {min}-{max}
#module: ApiDebugModule #module: ApiDebugModule
api_debug_module.id.not_blank=ID cannot be blank api_debug_module.id.not_blank=ID cannot be blank
api_debug_module.id.length_range=Module ID length must be between 1-50 api_debug_module.id.length_range=Module ID length must be between 1-50
@ -282,7 +287,10 @@ api_debug_module.unplanned_request=Unplanned request
api_unplanned_request=Unplanned Api api_unplanned_request=Unplanned Api
#module: ApiEnvironmentConfig #module: ApiEnvironmentConfig
api_environment_config.id.not_blank=ID不能为空 api_environment_config.id.not_blank=ID cannot be blank
api_environment_config.environment_id.length_range=环境ID长度必须在1-50之间 api_environment_config.environment_id.length_range=Environment ID length must be between 1-50
api_environment_config.environment_id.not_blank=环境ID不能为空 api_environment_config.environment_id.not_blank=Environment ID cannot be blank
api_module.not.exist=模块不存在 api_module.not.exist=The module does not exist
permission.api.name=API Test
api_debug_exist=The API already exists

View File

@ -267,6 +267,11 @@ api_debug.project_id.not_blank=项目ID不能为空
api_debug.project_id.length_range=项目ID长度必须在1-50之间 api_debug.project_id.length_range=项目ID长度必须在1-50之间
api_debug.module_id.not_blank=模块ID不能为空 api_debug.module_id.not_blank=模块ID不能为空
api_debug.module_id.length_range=模块ID长度必须在1-50之间 api_debug.module_id.length_range=模块ID长度必须在1-50之间
api_debug.create_user.not_blank=创建人不能为空
api_debug.create_user.length_range=创建人长度必须在{min}和{max}之间
api_debug.update_user.not_blank=修改人不能为空
api_debug.update_user.length_range=修改人长度必须在{min}和{max}之间
#module: ApiDebugModule #module: ApiDebugModule
api_debug_module.id.not_blank=ID不能为空 api_debug_module.id.not_blank=ID不能为空
api_debug_module.id.length_range=模块ID长度必须在1-50之间 api_debug_module.id.length_range=模块ID长度必须在1-50之间
@ -285,4 +290,7 @@ api_unplanned_request=未规划接口
api_environment_config.id.not_blank=ID不能为空 api_environment_config.id.not_blank=ID不能为空
api_environment_config.environment_id.length_range=环境ID长度必须在1-50之间 api_environment_config.environment_id.length_range=环境ID长度必须在1-50之间
api_environment_config.environment_id.not_blank=环境ID不能为空 api_environment_config.environment_id.not_blank=环境ID不能为空
api_module.not.exist=模块不存在 api_module.not.exist=模块不存在
permission.api.name=接口测试
api_debug_exist=接口已存在

View File

@ -267,6 +267,11 @@ api_debug.project_id.not_blank=項目ID不能為空
api_debug.project_id.length_range=項目ID長度必須在1-50之間 api_debug.project_id.length_range=項目ID長度必須在1-50之間
api_debug.module_id.not_blank=模塊ID不能為空 api_debug.module_id.not_blank=模塊ID不能為空
api_debug.module_id.length_range=模塊ID長度必須在1-50之間 api_debug.module_id.length_range=模塊ID長度必須在1-50之間
api_debug.create_user.not_blank=創建人不能為空
api_debug.create_user.length_range=創建人長度必須在{min}和{max}之間
api_debug.update_user.not_blank=修改人不能為空
api_debug.update_user.length_range=修改人長度必須在{min}和{max}之間
#module: ApiDebugModule #module: ApiDebugModule
api_debug_module.id.not_blank=ID不能為空 api_debug_module.id.not_blank=ID不能為空
api_debug_module.id.length_range=模塊ID長度必須在1-50之間 api_debug_module.id.length_range=模塊ID長度必須在1-50之間
@ -285,4 +290,7 @@ api_unplanned_request=未规划接口
api_environment_config.id.not_blank=ID不能為空 api_environment_config.id.not_blank=ID不能為空
api_environment_config.environment_id.length_range=環境ID長度必須在1-50之間 api_environment_config.environment_id.length_range=環境ID長度必須在1-50之間
api_environment_config.environment_id.not_blank=環境ID不能為空 api_environment_config.environment_id.not_blank=環境ID不能為空
api_module.not.exist=模塊不存在 api_module.not.exist=模塊不存在
permission.api.name=接口測試
api_debug_exist=接口已存在

View File

@ -439,6 +439,7 @@ permission.delete=删除
permission.import=导入 permission.import=导入
permission.recover=恢复 permission.recover=恢复
permission.export=导出 permission.export=导出
permission.execute=执行
file_name_illegal_error=文件名不合法 file_name_illegal_error=文件名不合法
plugin_enable_error=插件未启用 plugin_enable_error=插件未启用

View File

@ -441,6 +441,7 @@ permission.delete=Delete
permission.import=Import permission.import=Import
permission.recover=Recover permission.recover=Recover
permission.export=Export permission.export=Export
permission.execute=Execute
file_name_illegal_error=File name is illegal file_name_illegal_error=File name is illegal
plugin_enable_error=Plugin is not enabled plugin_enable_error=Plugin is not enabled

View File

@ -439,6 +439,7 @@ permission.delete=删除
permission.import=导入 permission.import=导入
permission.recover=恢复 permission.recover=恢复
permission.export=导出 permission.export=导出
permission.execute=执行
file_name_illegal_error=文件名不合法 file_name_illegal_error=文件名不合法
plugin_enable_error=插件未启用 plugin_enable_error=插件未启用

View File

@ -437,6 +437,7 @@ permission.delete=刪除
permission.import=導入 permission.import=導入
permission.recover=恢復 permission.recover=恢復
permission.export=導出 permission.export=導出
permission.execute=執行
file_name_illegal_error=文件名不合法 file_name_illegal_error=文件名不合法
plugin_enable_error=插件未啟用 plugin_enable_error=插件未啟用

View File

@ -0,0 +1,72 @@
package io.metersphere.api.controller.debug;
import io.metersphere.api.domain.ApiDebug;
import io.metersphere.api.dto.debug.ApiDebugAddRequest;
import io.metersphere.api.dto.debug.ApiDebugDTO;
import io.metersphere.api.dto.debug.ApiDebugSimpleDTO;
import io.metersphere.api.dto.debug.ApiDebugUpdateRequest;
import io.metersphere.api.service.debug.ApiDebugLogService;
import io.metersphere.api.service.debug.ApiDebugService;
import io.metersphere.sdk.constants.PermissionConstants;
import io.metersphere.system.log.annotation.Log;
import io.metersphere.system.log.constants.OperationLogType;
import io.metersphere.system.utils.SessionUtils;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* @author : jianxing
* @date : 2023-11-6
*/
@RestController
@RequestMapping("/api/debug")
@Tag(name = "接口调试")
public class ApiDebugController {
@Resource
private ApiDebugService apiDebugService;
@GetMapping("/list/{protocol}")
@Operation(summary = "获取接口调试列表")
@RequiresPermissions(PermissionConstants.PROJECT_API_DEBUG_READ)
public List<ApiDebugSimpleDTO> list(@PathVariable String protocol) {
return apiDebugService.list(protocol, SessionUtils.getUserId());
}
@GetMapping("/get/{id}")
@Operation(summary = "获取接口调试详情")
@RequiresPermissions(PermissionConstants.PROJECT_API_DEBUG_READ)
public ApiDebugDTO get(@PathVariable String id) {
return apiDebugService.get(id);
}
@PostMapping("/add")
@Operation(summary = "创建接口调试")
@RequiresPermissions(PermissionConstants.PROJECT_API_DEBUG_ADD)
@Log(type = OperationLogType.ADD, expression = "#msClass.addLog(#request)", msClass = ApiDebugLogService.class)
public ApiDebug add(@Validated @RequestBody ApiDebugAddRequest request) {
return apiDebugService.add(request, SessionUtils.getUserId());
}
@PostMapping("/update")
@Operation(summary = "更新接口调试")
@RequiresPermissions(PermissionConstants.PROJECT_API_DEBUG_UPDATE)
@Log(type = OperationLogType.UPDATE, expression = "#msClass.updateLog(#request)", msClass = ApiDebugLogService.class)
public ApiDebug update(@Validated @RequestBody ApiDebugUpdateRequest request) {;
return apiDebugService.update(request, SessionUtils.getUserId());
}
@GetMapping("/delete/{id}")
@Operation(summary = "删除接口调试")
@RequiresPermissions(PermissionConstants.PROJECT_API_DEBUG_DELETE)
@Log(type = OperationLogType.DELETE, expression = "#msClass.deleteLog(#id)", msClass = ApiDebugLogService.class)
public void delete(@PathVariable String id) {
apiDebugService.delete(id);
}
}

View File

@ -35,14 +35,14 @@ public class ApiDebugModuleController {
@PostMapping("/add") @PostMapping("/add")
@Operation(summary = "接口测试-接口调试-模块-添加模块") @Operation(summary = "接口测试-接口调试-模块-添加模块")
@RequiresPermissions(PermissionConstants.PROJECT_API_DEBUG_READ_ADD) @RequiresPermissions(PermissionConstants.PROJECT_API_DEBUG_ADD)
public String add(@RequestBody @Validated ModuleCreateRequest request) { public String add(@RequestBody @Validated ModuleCreateRequest request) {
return apiDebugModuleService.add(request, SessionUtils.getUserId()); return apiDebugModuleService.add(request, SessionUtils.getUserId());
} }
@PostMapping("/update") @PostMapping("/update")
@Operation(summary = "接口测试-接口调试-模块-修改模块") @Operation(summary = "接口测试-接口调试-模块-修改模块")
@RequiresPermissions(PermissionConstants.PROJECT_API_DEBUG_READ_UPDATE) @RequiresPermissions(PermissionConstants.PROJECT_API_DEBUG_UPDATE)
public boolean list(@RequestBody @Validated ModuleUpdateRequest request) { public boolean list(@RequestBody @Validated ModuleUpdateRequest request) {
apiDebugModuleService.update(request, SessionUtils.getUserId(), SessionUtils.getCurrentProjectId()); apiDebugModuleService.update(request, SessionUtils.getUserId(), SessionUtils.getCurrentProjectId());
return true; return true;
@ -50,14 +50,14 @@ public class ApiDebugModuleController {
@GetMapping("/delete/{deleteId}") @GetMapping("/delete/{deleteId}")
@Operation(summary = "接口测试-接口调试-模块-删除模块") @Operation(summary = "接口测试-接口调试-模块-删除模块")
@RequiresPermissions(PermissionConstants.PROJECT_API_DEBUG_READ_DELETE) @RequiresPermissions(PermissionConstants.PROJECT_API_DEBUG_DELETE)
public void deleteNode(@PathVariable String deleteId) { public void deleteNode(@PathVariable String deleteId) {
apiDebugModuleService.deleteModule(deleteId, SessionUtils.getUserId()); apiDebugModuleService.deleteModule(deleteId, SessionUtils.getUserId());
} }
@PostMapping("/move") @PostMapping("/move")
@Operation(summary = "接口测试-接口调试-模块-移动模块") @Operation(summary = "接口测试-接口调试-模块-移动模块")
@RequiresPermissions(PermissionConstants.PROJECT_API_DEBUG_READ_UPDATE) @RequiresPermissions(PermissionConstants.PROJECT_API_DEBUG_UPDATE)
public void moveNode(@Validated @RequestBody NodeMoveRequest request) { public void moveNode(@Validated @RequestBody NodeMoveRequest request) {
apiDebugModuleService.moveNode(request, SessionUtils.getUserId()); apiDebugModuleService.moveNode(request, SessionUtils.getUserId());
} }

View File

@ -0,0 +1,30 @@
package io.metersphere.api.controller.result;
import io.metersphere.sdk.exception.IResultCode;
/**
* @author jianxing
*/
public enum ApiResultCode implements IResultCode {
API_DEBUG_EXIST(104001, "api_debug_exist");
private final int code;
private final String message;
ApiResultCode(int code, String message) {
this.code = code;
this.message = message;
}
@Override
public int getCode() {
return code;
}
@Override
public String getMessage() {
return getTranslationMessage(this.message);
}
}

View File

@ -0,0 +1,48 @@
package io.metersphere.api.dto.debug;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Data;
import java.io.Serializable;
/**
* @Author: jianxing
* @CreateTime: 2023-11-06 10:22
*/
@Data
public class ApiDebugAddRequest implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "接口名称", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{api_debug.name.not_blank}")
@Size(min = 1, max = 255, message = "{api_debug.name.length_range}")
private String name;
@Schema(description = "接口协议", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{api_debug.protocol.not_blank}")
@Size(min = 1, max = 20, message = "{api_debug.protocol.length_range}")
private String protocol;
@Schema(description = "http协议类型post/get/其它协议则是协议名(mqtt)")
private String method;
@Schema(description = "http协议url/其它协议则为空")
private String path;
@Schema(description = "项目fk", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{api_debug.project_id.not_blank}")
@Size(min = 1, max = 50, message = "{api_debug.project_id.length_range}")
private String projectId;
@Schema(description = "模块fk", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{api_debug.module_id.not_blank}")
@Size(min = 1, max = 50, message = "{api_debug.module_id.length_range}")
private String moduleId;
@Schema(description = "请求内容")
@NotBlank
private String request;
}

View File

@ -0,0 +1,15 @@
package io.metersphere.api.dto.debug;
import io.metersphere.api.domain.ApiDebug;
import io.metersphere.plugin.api.spi.AbstractMsTestElement;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
public class ApiDebugDTO extends ApiDebug {
@Schema(description = "请求内容")
private AbstractMsTestElement request;
@Schema(description = "响应内容")
private String response;
}

View File

@ -0,0 +1,19 @@
package io.metersphere.api.dto.debug;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
public class ApiDebugSimpleDTO {
@Schema(description = "接口ID")
private String id;
@Schema(description = "接口名称")
private String name;
@Schema(description = "http协议类型post/get/其它协议则是协议名(mqtt)")
private String method;
@Schema(description = "模块fk")
private String moduleId;
}

View File

@ -0,0 +1,45 @@
package io.metersphere.api.dto.debug;
import io.metersphere.validation.groups.Created;
import io.metersphere.validation.groups.Updated;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Data;
import java.io.Serializable;
/**
* @Author: jianxing
* @CreateTime: 2023-11-06 10:22
*/
@Data
public class ApiDebugUpdateRequest implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "接口pk", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{api_debug.id.not_blank}", groups = {Updated.class})
@Size(min = 1, max = 50, message = "{api_debug.id.length_range}", groups = {Created.class, Updated.class})
private String id;
@Schema(description = "接口名称", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{api_debug.name.not_blank}", groups = {Created.class})
@Size(min = 1, max = 255, message = "{api_debug.name.length_range}", groups = {Created.class, Updated.class})
private String name;
@Schema(description = "http协议类型post/get/其它协议则是协议名(mqtt)")
private String method;
@Schema(description = "http协议路径/其它协议则为空")
private String path;
@Schema(description = "模块fk", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{api_debug.module_id.not_blank}", groups = {Created.class})
@Size(min = 1, max = 50, message = "{api_debug.module_id.length_range}", groups = {Created.class, Updated.class})
private String moduleId;
@Schema(description = "请求内容")
@NotBlank
private String request;
}

View File

@ -0,0 +1,17 @@
package io.metersphere.api.mapper;
import io.metersphere.api.dto.debug.ApiDebugSimpleDTO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* @author jianxing
* @date : 2023-11-6
*/
@Mapper
public interface ExtApiDebugMapper {
List<ApiDebugSimpleDTO> list(@Param("protocol") String protocol, @Param("userId") String userId);
}

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="io.metersphere.api.mapper.ExtApiDebugMapper">
<select id="list" resultType="io.metersphere.api.dto.debug.ApiDebugSimpleDTO">
select id, name, method, module_id
from api_debug
where protocol = #{protocol} and create_user = #{userId}
ORDER BY pos
</select>
</mapper>

View File

@ -0,0 +1,68 @@
package io.metersphere.api.service.debug;
import io.metersphere.api.domain.ApiDebug;
import io.metersphere.api.dto.debug.ApiDebugAddRequest;
import io.metersphere.api.dto.debug.ApiDebugUpdateRequest;
import io.metersphere.sdk.constants.OperationLogConstants;
import io.metersphere.sdk.util.JSON;
import io.metersphere.system.log.constants.OperationLogModule;
import io.metersphere.system.log.constants.OperationLogType;
import io.metersphere.system.log.dto.LogDTO;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* @author jianxing
* @date : 2023-11-6
*/
@Service
@Transactional(rollbackFor = Exception.class)
public class ApiDebugLogService {
@Resource
private ApiDebugService apiDebugService;
public LogDTO addLog(ApiDebugAddRequest request) {
LogDTO dto = new LogDTO(
request.getProjectId(),
null,
null,
null,
OperationLogType.ADD.name(),
OperationLogModule.API_DEBUG,
request.getName());
dto.setOriginalValue(JSON.toJSONBytes(request));
return dto;
}
public LogDTO updateLog(ApiDebugUpdateRequest request) {
ApiDebug apiDebug = apiDebugService.get(request.getId());
LogDTO dto = null;
if (apiDebug != null) {
dto = new LogDTO(
apiDebug.getProjectId(),
null,
apiDebug.getId(),
null,
OperationLogType.UPDATE.name(),
OperationLogModule.API_DEBUG,
apiDebug.getName());
dto.setOriginalValue(JSON.toJSONBytes(apiDebug));
}
return dto;
}
public LogDTO deleteLog(String id) {
ApiDebug apiDebug = apiDebugService.get(id);
LogDTO dto = new LogDTO(
OperationLogConstants.SYSTEM,
OperationLogConstants.SYSTEM,
apiDebug.getId(),
null,
OperationLogType.DELETE.name(),
OperationLogModule.API_DEBUG,
apiDebug.getName());
dto.setOriginalValue(JSON.toJSONBytes(apiDebug));
return dto;
}
}

View File

@ -0,0 +1,133 @@
package io.metersphere.api.service.debug;
import io.metersphere.api.domain.ApiDebug;
import io.metersphere.api.domain.ApiDebugBlob;
import io.metersphere.api.domain.ApiDebugExample;
import io.metersphere.api.dto.debug.ApiDebugAddRequest;
import io.metersphere.api.dto.debug.ApiDebugDTO;
import io.metersphere.api.dto.debug.ApiDebugSimpleDTO;
import io.metersphere.api.dto.debug.ApiDebugUpdateRequest;
import io.metersphere.api.mapper.ApiDebugBlobMapper;
import io.metersphere.api.mapper.ApiDebugMapper;
import io.metersphere.api.mapper.ExtApiDebugMapper;
import io.metersphere.api.util.ApiDataUtils;
import io.metersphere.plugin.api.spi.AbstractMsTestElement;
import io.metersphere.project.service.ProjectService;
import io.metersphere.sdk.exception.MSException;
import io.metersphere.sdk.util.BeanUtils;
import io.metersphere.system.uid.IDGenerator;
import io.metersphere.system.utils.ServiceUtils;
import jakarta.annotation.Resource;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import static io.metersphere.api.controller.result.ApiResultCode.API_DEBUG_EXIST;
/**
* @author jianxing
* @date : 2023-11-6
*/
@Service
@Transactional(rollbackFor = Exception.class)
public class ApiDebugService {
@Resource
private ApiDebugMapper apiDebugMapper;
@Resource
private ApiDebugBlobMapper apiDebugBlobMapper;
@Resource
private ExtApiDebugMapper extApiDebugMapper;
public List<ApiDebugSimpleDTO> list(String protocol, String userId) {
return extApiDebugMapper.list(protocol, userId);
}
public ApiDebugDTO get(String id) {
checkResourceExist(id);
ApiDebug apiDebug = apiDebugMapper.selectByPrimaryKey(id);
ApiDebugBlob apiDebugBlob = apiDebugBlobMapper.selectByPrimaryKey(id);
ApiDebugDTO apiDebugDTO = new ApiDebugDTO();
BeanUtils.copyBean(apiDebugDTO, apiDebug);
apiDebugDTO.setRequest(ApiDataUtils.parseObject(new String(apiDebugBlob.getRequest()), AbstractMsTestElement.class));
apiDebugDTO.setResponse(apiDebugDTO.getResponse());
return apiDebugDTO;
}
public ApiDebug add(ApiDebugAddRequest request, String createUser) {
ProjectService.checkResourceExist(request.getProjectId());
ApiDebug apiDebug = new ApiDebug();
BeanUtils.copyBean(apiDebug, request);
apiDebug.setCreateUser(createUser);
checkAddExist(apiDebug);
apiDebug.setId(IDGenerator.nextStr());
apiDebug.setCreateTime(System.currentTimeMillis());
apiDebug.setUpdateTime(System.currentTimeMillis());
apiDebug.setUpdateUser(apiDebug.getCreateUser());
apiDebugMapper.insert(apiDebug);
// todo 处理 body 文件
// todo 校验 moduleId
ApiDebugBlob apiDebugBlob = new ApiDebugBlob();
apiDebugBlob.setId(apiDebug.getId());
apiDebugBlob.setRequest(request.getRequest().getBytes());
apiDebugBlobMapper.insert(apiDebugBlob);
return apiDebug;
}
public ApiDebug update(ApiDebugUpdateRequest request, String updateUser) {
checkResourceExist(request.getId());
ApiDebug apiDebug = BeanUtils.copyBean(new ApiDebug(), request);
ApiDebug originApiDebug = apiDebugMapper.selectByPrimaryKey(request.getId());
checkUpdateExist(apiDebug, originApiDebug);
apiDebug.setUpdateUser(updateUser);
apiDebug.setUpdateTime(System.currentTimeMillis());
apiDebugMapper.updateByPrimaryKeySelective(apiDebug);
// todo 处理 body 文件
// todo 校验 moduleId
ApiDebugBlob apiDebugBlob = new ApiDebugBlob();
apiDebugBlob.setId(request.getId());
apiDebugBlob.setRequest(request.getRequest().getBytes());
apiDebugBlobMapper.updateByPrimaryKeySelective(apiDebugBlob);
return apiDebug;
}
public void delete(String id) {
checkResourceExist(id);
apiDebugMapper.deleteByPrimaryKey(id);
apiDebugBlobMapper.deleteByPrimaryKey(id);
}
private void checkAddExist(ApiDebug apiDebug) {
ApiDebugExample example = new ApiDebugExample();
example.createCriteria()
.andNameEqualTo(apiDebug.getName())
.andModuleIdEqualTo(apiDebug.getModuleId());
if (CollectionUtils.isNotEmpty(apiDebugMapper.selectByExample(example))) {
throw new MSException(API_DEBUG_EXIST);
}
}
private void checkUpdateExist(ApiDebug apiDebug, ApiDebug originApiDebug) {
if (StringUtils.isBlank(apiDebug.getName())) {
return;
}
ApiDebugExample example = new ApiDebugExample();
example.createCriteria()
.andIdNotEqualTo(apiDebug.getId())
.andModuleIdEqualTo(apiDebug.getModuleId() == null ? originApiDebug.getModuleId() : apiDebug.getModuleId())
.andNameEqualTo(apiDebug.getName());
if (CollectionUtils.isNotEmpty(apiDebugMapper.selectByExample(example))) {
throw new MSException(API_DEBUG_EXIST);
}
}
private ApiDebug checkResourceExist(String id) {
return ServiceUtils.checkResourceExist(apiDebugMapper.selectByPrimaryKey(id), "permission.system_api_debug.name");
}
}

View File

@ -21,12 +21,10 @@
"id": "PROJECT_API_DEBUG:READ+DELETE" "id": "PROJECT_API_DEBUG:READ+DELETE"
}, },
{ {
"id": "PROJECT_API_DEBUG:READ+IMPORT", "id": "PROJECT_API_DEBUG:READ+IMPORT"
"name": "permission.api_definition.import"
}, },
{ {
"id": "PROJECT_API_DEBUG:READ+EXECUTE", "id": "PROJECT_API_DEBUG:READ+EXECUTE"
"name": "permission.api_definition.execute"
} }
] ]
}, },
@ -47,16 +45,13 @@
"id": "PROJECT_API_DEFINITION:READ+DELETE" "id": "PROJECT_API_DEFINITION:READ+DELETE"
}, },
{ {
"id": "PROJECT_API_DEFINITION:READ+IMPORT", "id": "PROJECT_API_DEFINITION:READ+IMPORT"
"name": "permission.api_definition.import"
}, },
{ {
"id": "PROJECT_API_DEFINITION:READ+EXECUTE", "id": "PROJECT_API_DEFINITION:READ+EXECUTE"
"name": "permission.api_definition.execute"
}, },
{ {
"id": "PROJECT_API_DEFINITION:READ+EXPORT", "id": "PROJECT_API_DEFINITION:READ+EXPORT"
"name": "permission.api_definition.export"
} }
] ]
} }

View File

@ -0,0 +1,189 @@
package io.metersphere.api.controller;
import io.metersphere.api.controller.param.ApiDebugAddRequestDefinition;
import io.metersphere.api.controller.param.ApiDebugUpdateRequestDefinition;
import io.metersphere.api.domain.ApiDebug;
import io.metersphere.api.domain.ApiDebugBlob;
import io.metersphere.api.dto.debug.ApiDebugAddRequest;
import io.metersphere.api.dto.debug.ApiDebugDTO;
import io.metersphere.api.dto.debug.ApiDebugSimpleDTO;
import io.metersphere.api.dto.debug.ApiDebugUpdateRequest;
import io.metersphere.api.mapper.ApiDebugBlobMapper;
import io.metersphere.api.mapper.ApiDebugMapper;
import io.metersphere.api.util.ApiDataUtils;
import io.metersphere.plugin.api.spi.AbstractMsTestElement;
import io.metersphere.sdk.constants.PermissionConstants;
import io.metersphere.sdk.dto.api.request.http.MsHTTPElement;
import io.metersphere.sdk.util.BeanUtils;
import io.metersphere.sdk.util.JSON;
import io.metersphere.system.base.BaseTest;
import io.metersphere.system.log.constants.OperationLogType;
import jakarta.annotation.Resource;
import org.junit.jupiter.api.*;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MvcResult;
import java.util.List;
import static io.metersphere.api.controller.result.ApiResultCode.API_DEBUG_EXIST;
import static io.metersphere.system.controller.handler.result.MsHttpResultCode.NOT_FOUND;
/**
* @author jianxing
* @date : 2023-11-7
*/
@SpringBootTest(webEnvironment= SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class ApiDebugControllerTests extends BaseTest {
private static final String BASE_PATH = "/api/debug/";
protected static final String DEFAULT_LIST = "list/{0}";
protected static final String HTTP_PROTOCOL = "HTTP";
@Resource
private ApiDebugMapper apiDebugMapper;
@Resource
private ApiDebugBlobMapper apiDebugBlobMapper;
private static ApiDebug addApiDebug;
private static ApiDebug anotherAddApiDebug;
@Override
protected String getBasePath() {
return BASE_PATH;
}
@Test
@Order(0)
public void listEmpty() throws Exception {
// @@校验没有数据的情况
this.requestGetWithOk(DEFAULT_LIST, HTTP_PROTOCOL);
}
@Test
@Order(1)
public void add() throws Exception {
// @@请求成功
ApiDebugAddRequest request = new ApiDebugAddRequest();
request.setPath("http://test.com");
request.setMethod("GET");
request.setName("test");
request.setProtocol(HTTP_PROTOCOL);
request.setModuleId("default");
request.setProjectId(DEFAULT_PROJECT_ID);
MsHTTPElement msHttpElement = MsHTTPElementTest.getMsHttpElement();
request.setRequest(ApiDataUtils.toJSONString(msHttpElement));
MvcResult mvcResult = this.requestPostWithOkAndReturn(DEFAULT_ADD, request);
// 校验请求成功数据
ApiDebug resultData = getResultData(mvcResult, ApiDebug.class);
this.addApiDebug = assertUpdateApiDebug(request, msHttpElement, resultData.getId());
// 再插入一条数据便于修改时重名校验
request.setName("test1");
mvcResult = this.requestPostWithOkAndReturn(DEFAULT_ADD, request);
resultData = getResultData(mvcResult, ApiDebug.class);
this.anotherAddApiDebug = assertUpdateApiDebug(request, msHttpElement, resultData.getId());
// @@重名校验异常
assertErrorCode(this.requestPost(DEFAULT_ADD, request), API_DEBUG_EXIST);
// 校验项目是否存在
request.setProjectId("111");
assertErrorCode(this.requestPost(DEFAULT_ADD, request), NOT_FOUND);
// @@校验日志
checkLog(this.addApiDebug.getId(), OperationLogType.ADD);
// @@异常参数校验
createdGroupParamValidateTest(ApiDebugAddRequestDefinition.class, DEFAULT_ADD);
// @@校验权限
request.setProjectId(DEFAULT_PROJECT_ID);
requestPostPermissionTest(PermissionConstants.PROJECT_API_DEBUG_ADD, DEFAULT_ADD, request);
}
private ApiDebug assertUpdateApiDebug(Object request, MsHTTPElement msHttpElement, String id) {
ApiDebug apiDebug = apiDebugMapper.selectByPrimaryKey(id);
ApiDebugBlob apiDebugBlob = apiDebugBlobMapper.selectByPrimaryKey(id);
ApiDebug copyApiDebug = BeanUtils.copyBean(new ApiDebug(), apiDebug);
copyApiDebug = BeanUtils.copyBean(copyApiDebug, request);
Assertions.assertEquals(apiDebug, copyApiDebug);
ApiDataUtils.setResolver(MsHTTPElement.class);
Assertions.assertEquals(msHttpElement, ApiDataUtils.parseObject(new String(apiDebugBlob.getRequest()), AbstractMsTestElement.class));
return apiDebug;
}
@Test
@Order(2)
public void update() throws Exception {
// @@请求成功
ApiDebugUpdateRequest request = new ApiDebugUpdateRequest();
request.setId(addApiDebug.getId());
request.setPath("http://tesat.com");
request.setName("test1");
request.setMethod("POST");
request.setModuleId("default1");
MsHTTPElement msHttpElement = MsHTTPElementTest.getMsHttpElement();
msHttpElement.setName("test1");
request.setRequest(ApiDataUtils.toJSONString(msHttpElement));
this.requestPostWithOk(DEFAULT_UPDATE, request);
// 校验请求成功数据
assertUpdateApiDebug(request, msHttpElement, request.getId());
// @@重名校验异常
request.setModuleId("default");
assertErrorCode(this.requestPost(DEFAULT_UPDATE, request), API_DEBUG_EXIST);
// @@校验日志
checkLog(request.getId(), OperationLogType.UPDATE);
// @@异常参数校验
updatedGroupParamValidateTest(ApiDebugUpdateRequestDefinition.class, DEFAULT_UPDATE);
// @@校验权限
requestPostPermissionTest(PermissionConstants.PROJECT_API_DEBUG_UPDATE, DEFAULT_UPDATE, request);
}
@Test
@Order(3)
public void list() throws Exception {
// @@请求成功
MvcResult mvcResult = this.requestGetWithOk(DEFAULT_LIST, HTTP_PROTOCOL)
.andReturn();
// 校验数据是否正确
List<ApiDebugSimpleDTO> apiDebugList = getResultDataArray(mvcResult, ApiDebugSimpleDTO.class);
Assertions.assertEquals(apiDebugList.size(), 2);
Assertions.assertEquals(apiDebugList.get(0), BeanUtils.copyBean(new ApiDebugSimpleDTO(),
apiDebugMapper.selectByPrimaryKey(addApiDebug.getId())));
Assertions.assertEquals(apiDebugList.get(1),
BeanUtils.copyBean(new ApiDebugSimpleDTO(), apiDebugMapper.selectByPrimaryKey(anotherAddApiDebug.getId())));
// @@校验权限
requestGetPermissionTest(PermissionConstants.PROJECT_API_DEBUG_READ, DEFAULT_LIST, HTTP_PROTOCOL);
}
@Test
@Order(4)
public void get() throws Exception {
// @@请求成功
MvcResult mvcResult = this.requestGetWithOk(DEFAULT_GET, addApiDebug.getId())
.andReturn();
ApiDataUtils.setResolver(MsHTTPElement.class);
ApiDebugDTO apiDebugDTO = ApiDataUtils.parseObject(JSON.toJSONString(parseResponse(mvcResult).get("data")), ApiDebugDTO.class);
// 校验数据是否正确
ApiDebugDTO copyApiDebugDTO = BeanUtils.copyBean(new ApiDebugDTO(), apiDebugMapper.selectByPrimaryKey(addApiDebug.getId()));
ApiDebugBlob apiDebugBlob = apiDebugBlobMapper.selectByPrimaryKey(addApiDebug.getId());
copyApiDebugDTO.setRequest(ApiDataUtils.parseObject(new String(apiDebugBlob.getRequest()), AbstractMsTestElement.class));
Assertions.assertEquals(apiDebugDTO, copyApiDebugDTO);
// @@校验权限
requestGetPermissionTest(PermissionConstants.PROJECT_API_DEBUG_READ, DEFAULT_GET, apiDebugDTO.getId());
}
@Test
@Order(5)
public void delete() throws Exception {
// @@请求成功
this.requestGetWithOk(DEFAULT_DELETE, addApiDebug.getId());
// 校验请求成功数据
Assertions.assertNull(apiDebugMapper.selectByPrimaryKey(addApiDebug.getId()));
Assertions.assertNull(apiDebugBlobMapper.selectByPrimaryKey(addApiDebug.getId()));
// @@校验日志
checkLog(addApiDebug.getId(), OperationLogType.DELETE);
// @@校验权限
requestGetPermissionTest(PermissionConstants.PROJECT_API_DEBUG_DELETE, DEFAULT_DELETE, addApiDebug.getId());
}
}

View File

@ -273,7 +273,7 @@ public class ApiDebugModuleControllerTests extends BaseTest {
request = new ModuleCreateRequest(); request = new ModuleCreateRequest();
request.setProjectId(DEFAULT_PROJECT_ID); request.setProjectId(DEFAULT_PROJECT_ID);
request.setName("defaultProject"); request.setName("defaultProject");
requestPostPermissionTest(PermissionConstants.PROJECT_API_DEBUG_READ_ADD, URL_MODULE_ADD, request); requestPostPermissionTest(PermissionConstants.PROJECT_API_DEBUG_ADD, URL_MODULE_ADD, request);
} }
@Test @Test
@ -365,7 +365,7 @@ public class ApiDebugModuleControllerTests extends BaseTest {
updateRequest = new ModuleUpdateRequest(); updateRequest = new ModuleUpdateRequest();
updateRequest.setId(apiDebugModules.get(0).getId()); updateRequest.setId(apiDebugModules.get(0).getId());
updateRequest.setName("default-update-Project"); updateRequest.setName("default-update-Project");
requestPostPermissionTest(PermissionConstants.PROJECT_API_DEBUG_READ_UPDATE, URL_MODULE_UPDATE, updateRequest); requestPostPermissionTest(PermissionConstants.PROJECT_API_DEBUG_UPDATE, URL_MODULE_UPDATE, updateRequest);
} }
@Test @Test
@ -638,7 +638,7 @@ public class ApiDebugModuleControllerTests extends BaseTest {
checkLog(a1Node.getId(), OperationLogType.UPDATE, URL_MODULE_MOVE); checkLog(a1Node.getId(), OperationLogType.UPDATE, URL_MODULE_MOVE);
checkLog(a3Node.getId(), OperationLogType.UPDATE, URL_MODULE_MOVE); checkLog(a3Node.getId(), OperationLogType.UPDATE, URL_MODULE_MOVE);
requestPostPermissionTest(PermissionConstants.PROJECT_API_DEBUG_READ_UPDATE, URL_MODULE_MOVE, request); requestPostPermissionTest(PermissionConstants.PROJECT_API_DEBUG_UPDATE, URL_MODULE_MOVE, request);
} }
@Test @Test
@ -750,7 +750,7 @@ public class ApiDebugModuleControllerTests extends BaseTest {
//service层判断测试删除空集合 //service层判断测试删除空集合
apiDebugModuleService.deleteModule(new ArrayList<>(), "admin", DEFAULT_PROJECT_ID); apiDebugModuleService.deleteModule(new ArrayList<>(), "admin", DEFAULT_PROJECT_ID);
//校验权限 //校验权限
requestGetPermissionTest(PermissionConstants.PROJECT_API_DEBUG_READ_DELETE, String.format(URL_MODULE_DELETE, IDGenerator.nextNum())); requestGetPermissionTest(PermissionConstants.PROJECT_API_DEBUG_DELETE, String.format(URL_MODULE_DELETE, IDGenerator.nextNum()));
} }

View File

@ -0,0 +1,190 @@
package io.metersphere.api.controller;
import io.metersphere.api.util.ApiDataUtils;
import io.metersphere.plugin.api.spi.AbstractMsTestElement;
import io.metersphere.sdk.dto.api.request.http.*;
import io.metersphere.sdk.dto.api.request.http.auth.BasicAuth;
import io.metersphere.sdk.dto.api.request.http.auth.DigestAuth;
import io.metersphere.sdk.dto.api.request.http.auth.HTTPAuth;
import io.metersphere.sdk.dto.api.request.http.auth.NoAuth;
import io.metersphere.sdk.dto.api.request.http.body.*;
import io.metersphere.sdk.dto.api.request.processors.SQLProcessor;
import io.metersphere.sdk.dto.api.request.processors.ScriptProcessor;
import io.metersphere.sdk.dto.api.request.processors.TimeWaitingProcessor;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.List;
/**
* @Author: jianxing
* @CreateTime: 2023-11-07 11:17
*/
public class MsHTTPElementTest {
public MsHTTPElementTest() {
ApiDataUtils.setResolver(MsHTTPElement.class);
}
@Test
public void bodyTest() {
MsHTTPElement msHTTPElement = getMsHttpElement();
List bodies = new ArrayList<>();
FormDataBody formDataBody = new FormDataBody();
FormDataKV formDataKV = new FormDataKV();
formDataKV.setEnable(false);
formDataKV.setContentType("text/plain");
formDataKV.setEncode(true);
formDataKV.setMaxLength(10);
formDataKV.setMinLength(8);
formDataKV.setParamType("text");
formDataKV.setDescription("test");
formDataKV.setRequired(true);
formDataKV.setValue("value");
formDataKV.setKey("key");
formDataBody.setFromValues(List.of(formDataKV));
bodies.add(formDataBody);
WWWFormBody wwwFormBody = new WWWFormBody();
wwwFormBody.setFromValues(List.of(formDataKV));
bodies.add(wwwFormBody);
JsonBody jsonBody = new JsonBody();
jsonBody.setValue("{}");
bodies.add(jsonBody);
bodies.add(new NoneBody());
RawBody rawBody = new RawBody();
rawBody.setValue("A");
bodies.add(rawBody);
XmlBody xmlBody = new XmlBody();
xmlBody.setValue("<a/>");
bodies.add(xmlBody);
BinaryBody binaryBody = new BinaryBody();
binaryBody.setFilePath("/test/a.png");
binaryBody.setFileName("a.png");
bodies.add(binaryBody);
for (Object body : bodies) {
msHTTPElement.setBody((Body) body);
String json = ApiDataUtils.toJSONString(msHTTPElement);
Assertions.assertNotNull(json);
Assertions.assertEquals(ApiDataUtils.parseObject(json, AbstractMsTestElement.class), msHTTPElement);
}
}
@Test
public void authConfigTest() {
MsHTTPElement msHTTPElement = getMsHttpElement();
List authConfigs = new ArrayList<>();
authConfigs.add(new NoAuth());
BasicAuth basicAuth = new BasicAuth();
basicAuth.setUserName("test");
basicAuth.setPassword("passwd");
authConfigs.add(basicAuth);
DigestAuth digestAuth = new DigestAuth();
digestAuth.setUserName("test");
digestAuth.setPassword("passwd");
authConfigs.add(digestAuth);
for (Object authConfig : authConfigs) {
msHTTPElement.setAuthConfig((HTTPAuth) authConfig);
String json = ApiDataUtils.toJSONString(msHTTPElement);
Assertions.assertNotNull(json);
Assertions.assertEquals(ApiDataUtils.parseObject(json, AbstractMsTestElement.class), msHTTPElement);
}
}
@Test
public void msProcessorTest() {
MsHTTPElement msHTTPElement = getMsHttpElement();
List processors = new ArrayList<>();
ScriptProcessor scriptProcessor = new ScriptProcessor();
scriptProcessor.setEnable(true);
scriptProcessor.setScript("script");
scriptProcessor.setScriptLanguage("js");
scriptProcessor.setJsrEnable(true);
processors.add(scriptProcessor);
SQLProcessor sqlProcessor = new SQLProcessor();
sqlProcessor.setScript("script");
sqlProcessor.setEnable(true);
sqlProcessor.setDataSourceId("dataSourceId");
KeyValueParam keyValueParam = new KeyValueParam();
keyValueParam.setKey("key");
keyValueParam.setValue("value");
sqlProcessor.setVariables(List.of(keyValueParam));
sqlProcessor.setResultVariable("ddd");
sqlProcessor.setQueryTimeout(1111);
sqlProcessor.setVariableNames("test");
processors.add(sqlProcessor);
TimeWaitingProcessor timeWaitingProcessor = new TimeWaitingProcessor();
timeWaitingProcessor.setDelay(1000);
timeWaitingProcessor.setEnable(true);
processors.add(timeWaitingProcessor);
msHTTPElement.setPreProcessors(processors);
msHTTPElement.setPostProcessors(processors);
String json = ApiDataUtils.toJSONString(msHTTPElement);
Assertions.assertNotNull(json);
Assertions.assertEquals(ApiDataUtils.parseObject(json, AbstractMsTestElement.class), msHTTPElement);
}
public static MsHTTPElement getMsHttpElement() {
MsHTTPElement msHTTPElement = new MsHTTPElement();
msHTTPElement.setUrl("http://www.test.com");
msHTTPElement.setPath("/test");
msHTTPElement.setMethod("GET");
msHTTPElement.setName("name");
msHTTPElement.setEnable(false);
Header header = new Header();
header.setEnable(false);
header.setValue("value");
header.setKey("key");
header.setDescription("desc");
msHTTPElement.setHeaders(List.of(header));
RestParam restParam = new RestParam();
restParam.setKey("key");
restParam.setValue("value");
restParam.setEnable(false);
restParam.setDescription("desc");
restParam.setRequired(true);
msHTTPElement.setRest(List.of(restParam));
QueryParam queryParam = new QueryParam();
queryParam.setKey("key");
queryParam.setValue("value");
queryParam.setEnable(false);
queryParam.setDescription("desc");
queryParam.setRequired(true);
msHTTPElement.setQuery(List.of(queryParam));
MsHTTPConfig msHTTPConfig = new MsHTTPConfig();
msHTTPConfig.setFollowRedirects(true);
msHTTPConfig.setAutoRedirects(true);
msHTTPConfig.setResponseTimeout(1000L);
msHTTPConfig.setConnectTimeout(1000L);
msHTTPConfig.setCertificateAlias("alias");
msHTTPElement.setOtherConfig(msHTTPConfig);
return msHTTPElement;
}
}

View File

@ -0,0 +1,46 @@
package io.metersphere.api.controller.param;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Getter;
import lombok.Setter;
/**
* @Author: jianxing
* @CreateTime: 2023-11-07 14:49
*/
@Getter
@Setter
public class ApiDebugAddRequestDefinition {
@Schema(description = "接口名称", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{api_debug.name.not_blank}")
@Size(min = 1, max = 255, message = "{api_debug.name.length_range}")
private String name;
@Schema(description = "接口协议", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{api_debug.protocol.not_blank}")
@Size(min = 1, max = 20, message = "{api_debug.protocol.length_range}")
private String protocol;
@Schema(description = "http协议类型post/get/其它协议则是协议名(mqtt)")
private String method;
@Schema(description = "http协议路径/其它协议则为空")
private String path;
@Schema(description = "项目fk", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{api_debug.project_id.not_blank}")
@Size(min = 1, max = 50, message = "{api_debug.project_id.length_range}")
private String projectId;
@Schema(description = "模块fk", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{api_debug.module_id.not_blank}")
@Size(min = 1, max = 50, message = "{api_debug.module_id.length_range}")
private String moduleId;
@Schema(description = "请求内容")
@NotBlank
private String request;
}

View File

@ -0,0 +1,41 @@
package io.metersphere.api.controller.param;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Getter;
import lombok.Setter;
/**
* @Author: jianxing
* @CreateTime: 2023-11-07 14:33
*/
@Getter
@Setter
public class ApiDebugUpdateRequestDefinition {
@Schema(description = "接口pk", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{api_debug.id.not_blank}")
@Size(min = 1, max = 50, message = "{api_debug.id.length_range}")
private String id;
@Schema(description = "接口名称", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{api_debug.name.not_blank}")
@Size(min = 1, max = 255, message = "{api_debug.name.length_range}")
private String name;
@Schema(description = "http协议类型post/get/其它协议则是协议名(mqtt)")
private String method;
@Schema(description = "http协议路径/其它协议则为空")
private String path;
@Schema(description = "模块fk", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{api_debug.module_id.not_blank}")
@Size(min = 1, max = 50, message = "{api_debug.module_id.length_range}")
private String moduleId;
@Schema(description = "请求内容")
@NotBlank
private String request;
}

View File

@ -1,10 +1,6 @@
package io.metersphere.project.controller; package io.metersphere.project.controller;
import io.metersphere.sdk.constants.*; import io.metersphere.sdk.constants.*;
import io.metersphere.system.dto.sdk.request.StatusDefinitionUpdateRequest;
import io.metersphere.system.dto.sdk.request.StatusFlowUpdateRequest;
import io.metersphere.system.dto.sdk.request.StatusItemAddRequest;
import io.metersphere.system.dto.sdk.request.StatusItemUpdateRequest;
import io.metersphere.sdk.util.BeanUtils; import io.metersphere.sdk.util.BeanUtils;
import io.metersphere.system.base.BaseTest; import io.metersphere.system.base.BaseTest;
import io.metersphere.system.controller.OrganizationStatusFlowSettingControllerTest; import io.metersphere.system.controller.OrganizationStatusFlowSettingControllerTest;
@ -12,8 +8,13 @@ import io.metersphere.system.controller.param.StatusDefinitionUpdateRequestDefin
import io.metersphere.system.controller.param.StatusFlowUpdateRequestDefinition; import io.metersphere.system.controller.param.StatusFlowUpdateRequestDefinition;
import io.metersphere.system.controller.param.StatusItemAddRequestDefinition; import io.metersphere.system.controller.param.StatusItemAddRequestDefinition;
import io.metersphere.system.controller.param.StatusItemUpdateRequestDefinition; import io.metersphere.system.controller.param.StatusItemUpdateRequestDefinition;
import io.metersphere.system.domain.*; import io.metersphere.system.domain.OrganizationParameter;
import io.metersphere.system.domain.StatusItem;
import io.metersphere.system.dto.StatusItemDTO; import io.metersphere.system.dto.StatusItemDTO;
import io.metersphere.system.dto.sdk.request.StatusDefinitionUpdateRequest;
import io.metersphere.system.dto.sdk.request.StatusFlowUpdateRequest;
import io.metersphere.system.dto.sdk.request.StatusItemAddRequest;
import io.metersphere.system.dto.sdk.request.StatusItemUpdateRequest;
import io.metersphere.system.log.constants.OperationLogType; import io.metersphere.system.log.constants.OperationLogType;
import io.metersphere.system.mapper.OrganizationParameterMapper; import io.metersphere.system.mapper.OrganizationParameterMapper;
import io.metersphere.system.mapper.StatusItemMapper; import io.metersphere.system.mapper.StatusItemMapper;
@ -23,7 +24,6 @@ import io.metersphere.system.service.BaseStatusItemService;
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.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.junit.jupiter.api.*; import org.junit.jupiter.api.*;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;

View File

@ -0,0 +1,13 @@
package io.metersphere.system.dto;
import lombok.Data;
/**
* @Author: jianxing
* @CreateTime: 2023-11-07 18:43
*/
@Data
public class ProtocolDTO {
private String protocol;
private String name;
}

View File

@ -0,0 +1,42 @@
package io.metersphere.system.service;
import io.metersphere.plugin.api.spi.AbstractProtocolPlugin;
import io.metersphere.sdk.constants.PluginScenarioType;
import io.metersphere.system.domain.Plugin;
import jakarta.annotation.Resource;
import org.pf4j.PluginWrapper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
@Service
@Transactional(rollbackFor = Exception.class)
public class ApiPluginService {
@Resource
private PluginLoadService pluginLoadService;
@Resource
private BasePluginService basePluginService;
/**
* 获取协议插件的的协议列表
* @param orgId
* @return
*/
public List<String> getProtocols(String orgId) {
// 查询组织下有权限的插件
Set<String> pluginIds = basePluginService.getOrgEnabledPlugins(orgId, PluginScenarioType.API_PROTOCOL)
.stream()
.map(Plugin::getId)
.collect(Collectors.toSet());
List<PluginWrapper> plugins = pluginLoadService.getMsPluginManager().getPlugins();
return plugins.stream()
.filter(plugin -> pluginIds.contains(plugin.getPluginId()) && plugin.getPlugin() instanceof AbstractProtocolPlugin)
.map(plugin -> ((AbstractProtocolPlugin) plugin.getPlugin()).getProtocol())
.collect(Collectors.toList());
}
}

View File

@ -119,6 +119,7 @@ public class BaseUserRoleService {
put("READ+IMPORT", "permission.import"); put("READ+IMPORT", "permission.import");
put("READ+RECOVER", "permission.recover"); put("READ+RECOVER", "permission.recover");
put("READ+EXPORT", "permission.export"); put("READ+EXPORT", "permission.export");
put("READ+EXECUTE", "permission.execute");
}}; }};
return Translator.get(translationMap.get(permissionKey)); return Translator.get(translationMap.get(permissionKey));
} }

View File

@ -264,7 +264,7 @@ public abstract class BaseTest {
return JSON.parseArray(JSON.toJSONString(data), clazz); return JSON.parseArray(JSON.toJSONString(data), clazz);
} }
private static Map parseResponse(MvcResult mvcResult) throws UnsupportedEncodingException { protected static Map parseResponse(MvcResult mvcResult) throws UnsupportedEncodingException {
return JSON.parseMap(mvcResult.getResponse().getContentAsString(Charset.defaultCharset())); return JSON.parseMap(mvcResult.getResponse().getContentAsString(Charset.defaultCharset()));
} }