feat(接口测试): 获取插件表单下拉框选项接口

This commit is contained in:
AgAngle 2024-01-25 11:51:56 +08:00 committed by Craftsman
parent 785bfa10b0
commit d6f633062b
16 changed files with 241 additions and 12 deletions

View File

@ -0,0 +1,26 @@
package io.metersphere.plugin.api.dto;
import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
/**
* @author wx
*/
@Setter
@Getter
public class ApiPluginOptionsRequest implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 方法所需的查询参数
* 例如
* 查询的过滤条件
* 级联的情况需要表单的其他参数值等
*/
private Object queryParam;
/**
* 需要调用的插件方法
*/
private String optionMethod;
}

View File

@ -0,0 +1,19 @@
package io.metersphere.plugin.api.dto;
import lombok.Data;
/**
* @Author: jianxing
* @CreateTime: 2024-01-24 17:47
*/
@Data
public class ApiPluginSelectOption {
/**
* 选项名称
*/
private String text;
/**
* 选项值
*/
private String value;
}

View File

@ -1,9 +1,27 @@
package io.metersphere.plugin.api.spi;
import io.metersphere.plugin.api.dto.ApiPluginOptionsRequest;
import io.metersphere.plugin.api.dto.ApiPluginSelectOption;
import io.metersphere.plugin.sdk.spi.AbstractMsPlugin;
import io.metersphere.plugin.sdk.util.MSPluginException;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
/**
* 接口插件抽象类
*/
public abstract class AbstractApiPlugin extends AbstractMsPlugin {
public List<ApiPluginSelectOption> getPluginOptions(ApiPluginOptionsRequest request) {
String method = request.getOptionMethod();
try {
// 这里反射调用插件方法获取下拉框选项
return (List<ApiPluginSelectOption>) this.getClass().getMethod(method, request.getClass()).invoke(this, request);
} catch (InvocationTargetException e) {
throw new MSPluginException(e.getTargetException());
} catch (Exception e) {
throw new MSPluginException(e);
}
}
}

View File

@ -379,4 +379,5 @@ api_scenario_exist=场景已存在
schedule_not_exist=定时任务不存在
api_case_report_not_exist=用例报告不存在
api_scenario_report_not_exist=场景报告不存在
permission.api_plugin.name=接口插件

View File

@ -390,4 +390,5 @@ api_swagger_url_error=Swagger url unable to connect
api_scenario_exist=The scenario already exists
schedule_not_exist=The scheduled task does not exist
api_case_report_not_exist=Api report does not exist
api_scenario_report_not_exist=Scenario report does not exist
api_scenario_report_not_exist=Scenario report does not exist
permission.api_plugin.name=Api Plugin

View File

@ -360,3 +360,4 @@ api_scenario_exist=场景已存在
schedule_not_exist=定时任务不存在
api_case_report_not_exist=用例报告不存在
api_scenario_report_not_exist=场景报告不存在
permission.api_plugin.name=接口插件

View File

@ -359,4 +359,5 @@ api_swagger_url_error=Swagger url無法調解
api_scenario_exist=場景已存在
schedule_not_exist=定時任務不存在
api_case_report_not_exist=用例報告不存在
api_scenario_report_not_exist=場景報告不存在
api_scenario_report_not_exist=場景報告不存在
permission.api_plugin.name=接口插件

View File

@ -1,14 +1,17 @@
package io.metersphere.api.controller;
import io.metersphere.api.dto.ApiTestPluginOptionRequest;
import io.metersphere.api.service.ApiExecuteService;
import io.metersphere.api.service.ApiTestService;
import io.metersphere.jmeter.mock.Mock;
import io.metersphere.plugin.api.dto.ApiPluginSelectOption;
import io.metersphere.project.dto.customfunction.request.CustomFunctionRunRequest;
import io.metersphere.sdk.constants.PermissionConstants;
import io.metersphere.system.dto.ProtocolDTO;
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.Logical;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
@ -48,4 +51,16 @@ public class ApiTestController {
public String run(@Validated @RequestBody CustomFunctionRunRequest runRequest) {
return apiExecuteService.runScript(runRequest);
}
@PostMapping("/plugin/form/option")
@Operation(summary = "接口测试-获取插件表单选项")
@RequiresPermissions(value = {
PermissionConstants.PROJECT_API_DEFINITION_READ,
PermissionConstants.PROJECT_API_DEFINITION_CASE_READ,
PermissionConstants.PROJECT_API_DEBUG_READ,
PermissionConstants.PROJECT_API_SCENARIO_READ
}, logical = Logical.OR)
public List<ApiPluginSelectOption> getFormOptions(@Validated @RequestBody ApiTestPluginOptionRequest request) {
return apiTestService.getFormOptions(request);
}
}

View File

@ -0,0 +1,37 @@
package io.metersphere.api.dto;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Data;
@Data
public class ApiTestPluginOptionRequest {
/**
* 组织ID
*/
@NotBlank
@Size(max = 50)
private String orgId;
/**
* 插件ID
*/
@NotBlank
@Size(max = 50)
private String pluginId;
/**
* 需要调用的插件方法
*/
@NotBlank
@Size(max = 100)
private String optionMethod;
/**
* 方法所需的查询参数
* 例如
* 查询的过滤条件
* 级联的情况需要表单的其他参数值等
*/
private Object queryParam;
}

View File

@ -1,9 +1,17 @@
package io.metersphere.api.service;
import io.metersphere.api.dto.ApiTestPluginOptionRequest;
import io.metersphere.api.dto.request.http.MsHTTPElement;
import io.metersphere.plugin.api.dto.ApiPluginOptionsRequest;
import io.metersphere.plugin.api.dto.ApiPluginSelectOption;
import io.metersphere.plugin.api.spi.AbstractApiPlugin;
import io.metersphere.sdk.util.BeanUtils;
import io.metersphere.system.dto.ProtocolDTO;
import io.metersphere.system.service.ApiPluginService;
import io.metersphere.system.utils.ServiceUtils;
import jakarta.annotation.Resource;
import org.apache.commons.lang3.StringUtils;
import org.pf4j.PluginWrapper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@ -31,4 +39,18 @@ public class ApiTestService {
return protocols;
}
public List<ApiPluginSelectOption> getFormOptions(ApiTestPluginOptionRequest request) {
// 获取组织下有权限的接口插件
List<PluginWrapper> pluginWrappers = apiPluginService.getOrgApiPluginWrappers(request.getOrgId());
PluginWrapper pluginWrapper = pluginWrappers.stream()
.filter(item -> StringUtils.equals(item.getPluginId(), request.getPluginId()))
.findFirst()
.orElse(null);
if (pluginWrapper == null || !(pluginWrapper.getPlugin() instanceof AbstractApiPlugin)) {
// 插件不存在或者非接口插件抛出异常
ServiceUtils.checkResourceExist(null, "permission.api_plugin.name");
}
ApiPluginOptionsRequest optionsRequest = BeanUtils.copyBean(new ApiPluginOptionsRequest(), request);
return ((AbstractApiPlugin) pluginWrapper.getPlugin()).getPluginOptions(optionsRequest);
}
}

View File

@ -1,21 +1,37 @@
package io.metersphere.api.controller;
import io.metersphere.api.dto.ApiTestPluginOptionRequest;
import io.metersphere.api.service.BaseResourcePoolTestService;
import io.metersphere.plugin.api.dto.ApiPluginSelectOption;
import io.metersphere.project.constants.ScriptLanguageType;
import io.metersphere.project.dto.customfunction.request.CustomFunctionRunRequest;
import io.metersphere.project.dto.environment.KeyValueParam;
import io.metersphere.sdk.constants.PermissionConstants;
import io.metersphere.system.base.BasePluginTestService;
import io.metersphere.system.base.BaseTest;
import io.metersphere.system.domain.Plugin;
import io.metersphere.system.dto.request.PluginUpdateRequest;
import io.metersphere.system.service.PluginService;
import io.metersphere.system.uid.IDGenerator;
import jakarta.annotation.Resource;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.test.web.servlet.MvcResult;
import java.io.File;
import java.io.FileInputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import static io.metersphere.sdk.constants.InternalUserRole.ADMIN;
import static io.metersphere.system.controller.handler.result.MsHttpResultCode.NOT_FOUND;
/**
* @Author: jianxing
* @CreateTime: 2023-11-07 17:07
@ -29,9 +45,14 @@ public class ApiTestControllerTests extends BaseTest {
protected static final String PROTOCOL_LIST = "protocol/{0}";
protected static final String MOCK = "mock/{0}";
protected static final String CUSTOM_FUNC_RUN = "custom/func/run";
protected static final String PLUGIN_FORM_OPTION = "plugin/form/option";
@Resource
private BaseResourcePoolTestService baseResourcePoolTestService;
@Resource
private PluginService pluginService;
@Resource
private BasePluginTestService basePluginTestService;
@Override
protected String getBasePath() {
return BASE_PATH;
@ -75,4 +96,50 @@ public class ApiTestControllerTests extends BaseTest {
// @@校验权限
requestPostPermissionTest(PermissionConstants.PROJECT_CUSTOM_FUNCTION_EXECUTE, CUSTOM_FUNC_RUN, request);
}
@Test
public void getFormOptions() throws Exception {
ApiTestPluginOptionRequest request = new ApiTestPluginOptionRequest();
request.setOrgId(DEFAULT_ORGANIZATION_ID);
request.setQueryParam(new HashMap<>());
request.setPluginId("aaa");
request.setOptionMethod("testGetApiPluginSelectOption");
assertErrorCode(this.requestPost(PLUGIN_FORM_OPTION, request), NOT_FOUND);
Plugin jiraPlugin = basePluginTestService.addJiraPlugin();
request.setPluginId(jiraPlugin.getId());
assertErrorCode(this.requestPost(PLUGIN_FORM_OPTION, request), NOT_FOUND);
// @@请求成功
Plugin plugin = addOptionTestPlugin();
request.setPluginId(plugin.getId());
MvcResult mvcResult = this.requestPostWithOkAndReturn(PLUGIN_FORM_OPTION, request);
List<ApiPluginSelectOption> options = getResultDataArray(mvcResult, ApiPluginSelectOption.class);
Assertions.assertEquals(options.size(), 1);
basePluginTestService.deleteJiraPlugin();
pluginService.delete(plugin.getId());
// @@校验权限
requestPostPermissionsTest(new ArrayList<>() {{
add(PermissionConstants.PROJECT_API_DEFINITION_READ);
add(PermissionConstants.PROJECT_API_DEFINITION_CASE_READ);
add(PermissionConstants.PROJECT_API_DEBUG_READ);
add(PermissionConstants.PROJECT_API_SCENARIO_READ);
}}, PLUGIN_FORM_OPTION, request);
}
public Plugin addOptionTestPlugin() throws Exception {
PluginUpdateRequest request = new PluginUpdateRequest();
File jarFile = new File(
this.getClass().getClassLoader().getResource("file/dubbo-option-test-sampler-v3.x.jar")
.getPath()
);
FileInputStream inputStream = new FileInputStream(jarFile);
MockMultipartFile mockMultipartFile = new MockMultipartFile(jarFile.getName(), jarFile.getName(), "jar", inputStream);
request.setName("测试获取选项插件");
request.setGlobal(true);
request.setEnable(true);
request.setCreateUser(ADMIN.name());
return pluginService.add(request, mockMultipartFile);
}
}

View File

@ -40,6 +40,7 @@ public class ApiTestServiceTest extends BaseTest {
httpProtocol.setPolymorphicName(MsHTTPElement.class.getSimpleName());
ProtocolDTO jdbcProtocol = new ProtocolDTO();
jdbcProtocol.setProtocol("JDBC");
jdbcProtocol.setPluginId("jdbc");
jdbcProtocol.setPolymorphicName("MsJDBCElement");
expected.add(httpProtocol);
expected.add(jdbcProtocol);

View File

@ -17,4 +17,8 @@ public class ProtocolDTO {
* 例如 Http 对应 MsHTTPElement
*/
private String polymorphicName;
/**
* 插件ID
*/
private String pluginId;
}

View File

@ -1,5 +1,6 @@
package io.metersphere.system.service;
import io.metersphere.plugin.api.spi.AbstractApiPlugin;
import io.metersphere.plugin.api.spi.AbstractProtocolPlugin;
import io.metersphere.plugin.api.spi.MsTestElement;
import io.metersphere.sdk.constants.PluginScenarioType;
@ -29,20 +30,12 @@ public class ApiPluginService {
/**
* 获取协议插件的的协议列表
*
* @param orgId
* @return
*/
public List<ProtocolDTO> 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();
List<PluginWrapper> pluginWrappers = plugins.stream()
.filter(plugin -> pluginIds.contains(plugin.getPluginId()) && plugin.getPlugin() instanceof AbstractProtocolPlugin).toList();
List<PluginWrapper> pluginWrappers = getOrgProtocolPluginWrappers(orgId);
List protocols = new ArrayList<ProtocolDTO>();
pluginWrappers.forEach(pluginWrapper -> {
@ -54,6 +47,7 @@ public class ApiPluginService {
AbstractProtocolPlugin protocolPlugin = ((AbstractProtocolPlugin) pluginWrapper.getPlugin());
ProtocolDTO protocolDTO = new ProtocolDTO();
protocolDTO.setProtocol(protocolPlugin.getProtocol());
protocolDTO.setPluginId(pluginWrapper.getPluginId());
if (CollectionUtils.isNotEmpty(extensionClasses)) {
protocolDTO.setPolymorphicName(extensionClasses.get(0).getSimpleName());
}
@ -66,4 +60,25 @@ public class ApiPluginService {
});
return protocols;
}
private List<PluginWrapper> getOrgProtocolPluginWrappers(String orgId) {
return getOrgApiPluginWrappers(orgId).stream()
.filter(plugin -> plugin.getPlugin() instanceof AbstractProtocolPlugin)
.toList();
}
public List<PluginWrapper> getOrgApiPluginWrappers(String orgId) {
// 查询组织下有权限的插件
Set<String> pluginIds = basePluginService.getOrgEnabledPlugins(orgId, PluginScenarioType.API_PROTOCOL)
.stream()
.map(Plugin::getId)
.collect(Collectors.toSet());
// 过滤协议插件
List<PluginWrapper> plugins = pluginLoadService.getMsPluginManager().getPlugins();
List<PluginWrapper> pluginWrappers = plugins.stream()
.filter(plugin -> pluginIds.contains(plugin.getPluginId()) && plugin.getPlugin() instanceof AbstractApiPlugin)
.toList();
return pluginWrappers;
}
}

View File

@ -37,6 +37,7 @@ public class ApiPluginServiceTests extends BaseTest {
ProtocolDTO jdbcProtocol = new ProtocolDTO();
jdbcProtocol.setProtocol("JDBC");
jdbcProtocol.setPolymorphicName("MsJDBCElement");
jdbcProtocol.setPluginId("jdbc");
expected.add(jdbcProtocol);
Assertions.assertEquals(protocols, expected);
}