api测试,未完待续

This commit is contained in:
q4speed 2020-04-22 17:29:54 +08:00
parent 8dc3249347
commit 6aa69cee66
32 changed files with 578 additions and 467 deletions

View File

@ -57,6 +57,11 @@
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>

View File

@ -0,0 +1,68 @@
package io.metersphere.api.controller;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import io.metersphere.api.dto.APITestResult;
import io.metersphere.api.dto.DeleteAPITestRequest;
import io.metersphere.api.dto.QueryAPITestRequest;
import io.metersphere.api.dto.SaveAPITestRequest;
import io.metersphere.api.service.ApiTestService;
import io.metersphere.base.domain.ApiTestWithBLOBs;
import io.metersphere.commons.constants.RoleConstants;
import io.metersphere.commons.utils.PageUtils;
import io.metersphere.commons.utils.Pager;
import io.metersphere.service.FileService;
import io.metersphere.user.SessionUtils;
import org.apache.shiro.authz.annotation.Logical;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import javax.annotation.Resource;
@RestController
@RequestMapping(value = "/api")
@RequiresRoles(value = {RoleConstants.TEST_MANAGER, RoleConstants.TEST_USER, RoleConstants.TEST_VIEWER}, logical = Logical.OR)
public class APITestController {
@Resource
private ApiTestService apiTestService;
@Resource
private FileService fileService;
@GetMapping("recent/{count}")
public List<APITestResult> recentTest(@PathVariable int count) {
String currentWorkspaceId = SessionUtils.getCurrentWorkspaceId();
QueryAPITestRequest request = new QueryAPITestRequest();
request.setWorkspaceId(currentWorkspaceId);
PageHelper.startPage(1, count, true);
return apiTestService.recentTest(request);
}
@PostMapping("/list/{goPage}/{pageSize}")
public Pager<List<APITestResult>> list(@PathVariable int goPage, @PathVariable int pageSize, @RequestBody QueryAPITestRequest request) {
Page<Object> page = PageHelper.startPage(goPage, pageSize, true);
request.setWorkspaceId(SessionUtils.getCurrentWorkspaceId());
return PageUtils.setPageInfo(page, apiTestService.list(request));
}
@PostMapping(value = "/save")
public String save(@RequestBody SaveAPITestRequest request) {
return apiTestService.save(request);
}
@GetMapping("/get/{testId}")
public ApiTestWithBLOBs get(@PathVariable String testId) {
return apiTestService.get(testId);
}
@PostMapping("/delete")
public void delete(@RequestBody DeleteAPITestRequest request) {
apiTestService.delete(request);
}
//
// @PostMapping("/run")
// public void run(@RequestBody RunTestPlanRequest request) {
// apiTestService.run(request);
// }
}

View File

@ -0,0 +1,12 @@
package io.metersphere.api.dto;
import io.metersphere.base.domain.ApiTestWithBLOBs;
import lombok.Getter;
import lombok.Setter;
@Setter
@Getter
public class APITestResult extends ApiTestWithBLOBs {
private String projectName;
}

View File

@ -0,0 +1,11 @@
package io.metersphere.api.dto;
import lombok.Getter;
import lombok.Setter;
@Setter
@Getter
public class DeleteAPITestRequest {
private String id;
}

View File

@ -0,0 +1,16 @@
package io.metersphere.api.dto;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class QueryAPITestRequest {
private String id;
private String projectId;
private String name;
private String workspaceId;
private boolean recent = false;
}

View File

@ -0,0 +1,17 @@
package io.metersphere.api.dto;
import lombok.Getter;
import lombok.Setter;
@Setter
@Getter
public class SaveAPITestRequest {
private String id;
private String projectId;
private String name;
private String scenarioDefinition;
}

View File

@ -0,0 +1,100 @@
package io.metersphere.api.service;
import io.metersphere.api.dto.APITestResult;
import io.metersphere.api.dto.DeleteAPITestRequest;
import io.metersphere.api.dto.QueryAPITestRequest;
import io.metersphere.api.dto.SaveAPITestRequest;
import io.metersphere.base.domain.*;
import io.metersphere.base.mapper.*;
import io.metersphere.base.mapper.ext.ExtApiTestMapper;
import io.metersphere.commons.constants.APITestStatus;
import io.metersphere.commons.exception.MSException;
import io.metersphere.i18n.Translator;
import io.metersphere.service.FileService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.UUID;
import javax.annotation.Resource;
@Service
@Transactional(rollbackFor = Exception.class)
public class ApiTestService {
@Resource
private ApiTestMapper apiTestMapper;
@Resource
private ExtApiTestMapper extApiTestMapper;
@Resource
private ProjectMapper projectMapper;
@Resource
private FileMetadataMapper fileMetadataMapper;
@Resource
private FileContentMapper fileContentMapper;
@Resource
private ApiTestFileMapper apiTestFileMapper;
@Resource
private FileService fileService;
public List<APITestResult> list(QueryAPITestRequest request) {
return extApiTestMapper.list(request);
}
public List<APITestResult> recentTest(QueryAPITestRequest request) {
request.setRecent(true);
return extApiTestMapper.list(request);
}
public String save(SaveAPITestRequest request) {
final ApiTestWithBLOBs test;
if (StringUtils.isNotBlank(request.getId())) {
test = updateTest(request);
} else {
test = createTest(request);
}
return test.getId();
}
public ApiTestWithBLOBs get(String id) {
return apiTestMapper.selectByPrimaryKey(id);
}
public void delete(DeleteAPITestRequest request) {
apiTestMapper.deleteByPrimaryKey(request.getId());
}
private ApiTestWithBLOBs updateTest(SaveAPITestRequest request) {
final ApiTestWithBLOBs test = new ApiTestWithBLOBs();
test.setId(request.getId());
test.setName(request.getName());
test.setProjectId(request.getProjectId());
test.setScenarioDefinition(request.getScenarioDefinition());
test.setUpdateTime(System.currentTimeMillis());
test.setStatus(APITestStatus.Saved.name());
apiTestMapper.updateByPrimaryKeySelective(test);
return test;
}
private ApiTestWithBLOBs createTest(SaveAPITestRequest request) {
ApiTestExample example = new ApiTestExample();
example.createCriteria().andNameEqualTo(request.getName()).andProjectIdEqualTo(request.getProjectId());
if (apiTestMapper.countByExample(example) > 0) {
MSException.throwException(Translator.get("load_test_already_exists"));
}
final ApiTestWithBLOBs test = new ApiTestWithBLOBs();
test.setId(UUID.randomUUID().toString());
test.setName(request.getName());
test.setProjectId(request.getProjectId());
test.setScenarioDefinition(request.getScenarioDefinition());
test.setCreateTime(System.currentTimeMillis());
test.setUpdateTime(System.currentTimeMillis());
test.setStatus(APITestStatus.Saved.name());
apiTestMapper.insert(test);
return test;
}
}

View File

@ -11,6 +11,8 @@ public class ApiTest implements Serializable {
private String description;
private String status;
private Long createTime;
private Long updateTime;
@ -49,6 +51,14 @@ public class ApiTest implements Serializable {
this.description = description == null ? null : description.trim();
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status == null ? null : status.trim();
}
public Long getCreateTime() {
return createTime;
}

View File

@ -384,6 +384,76 @@ public class ApiTestExample {
return (Criteria) this;
}
public Criteria andStatusIsNull() {
addCriterion("status is null");
return (Criteria) this;
}
public Criteria andStatusIsNotNull() {
addCriterion("status is not null");
return (Criteria) this;
}
public Criteria andStatusEqualTo(String value) {
addCriterion("status =", value, "status");
return (Criteria) this;
}
public Criteria andStatusNotEqualTo(String value) {
addCriterion("status <>", value, "status");
return (Criteria) this;
}
public Criteria andStatusGreaterThan(String value) {
addCriterion("status >", value, "status");
return (Criteria) this;
}
public Criteria andStatusGreaterThanOrEqualTo(String value) {
addCriterion("status >=", value, "status");
return (Criteria) this;
}
public Criteria andStatusLessThan(String value) {
addCriterion("status <", value, "status");
return (Criteria) this;
}
public Criteria andStatusLessThanOrEqualTo(String value) {
addCriterion("status <=", value, "status");
return (Criteria) this;
}
public Criteria andStatusLike(String value) {
addCriterion("status like", value, "status");
return (Criteria) this;
}
public Criteria andStatusNotLike(String value) {
addCriterion("status not like", value, "status");
return (Criteria) this;
}
public Criteria andStatusIn(List<String> values) {
addCriterion("status in", values, "status");
return (Criteria) this;
}
public Criteria andStatusNotIn(List<String> values) {
addCriterion("status not in", values, "status");
return (Criteria) this;
}
public Criteria andStatusBetween(String value1, String value2) {
addCriterion("status between", value1, value2, "status");
return (Criteria) this;
}
public Criteria andStatusNotBetween(String value1, String value2) {
addCriterion("status not between", value1, value2, "status");
return (Criteria) this;
}
public Criteria andCreateTimeIsNull() {
addCriterion("create_time is null");
return (Criteria) this;

View File

@ -3,18 +3,18 @@ package io.metersphere.base.domain;
import java.io.Serializable;
public class ApiTestWithBLOBs extends ApiTest implements Serializable {
private String runtimeConfiguration;
private String scenarioDefinition;
private String schedule;
private static final long serialVersionUID = 1L;
public String getRuntimeConfiguration() {
return runtimeConfiguration;
public String getScenarioDefinition() {
return scenarioDefinition;
}
public void setRuntimeConfiguration(String runtimeConfiguration) {
this.runtimeConfiguration = runtimeConfiguration == null ? null : runtimeConfiguration.trim();
public void setScenarioDefinition(String scenarioDefinition) {
this.scenarioDefinition = scenarioDefinition == null ? null : scenarioDefinition.trim();
}
public String getSchedule() {

View File

@ -6,11 +6,12 @@
<result column="project_id" jdbcType="VARCHAR" property="projectId" />
<result column="name" jdbcType="VARCHAR" property="name" />
<result column="description" jdbcType="VARCHAR" property="description" />
<result column="status" jdbcType="VARCHAR" property="status" />
<result column="create_time" jdbcType="BIGINT" property="createTime" />
<result column="update_time" jdbcType="BIGINT" property="updateTime" />
</resultMap>
<resultMap extends="BaseResultMap" id="ResultMapWithBLOBs" type="io.metersphere.base.domain.ApiTestWithBLOBs">
<result column="runtime_configuration" jdbcType="LONGVARCHAR" property="runtimeConfiguration" />
<result column="scenario_definition" jdbcType="LONGVARCHAR" property="scenarioDefinition" />
<result column="schedule" jdbcType="LONGVARCHAR" property="schedule" />
</resultMap>
<sql id="Example_Where_Clause">
@ -72,10 +73,10 @@
</where>
</sql>
<sql id="Base_Column_List">
id, project_id, name, description, create_time, update_time
id, project_id, name, description, status, create_time, update_time
</sql>
<sql id="Blob_Column_List">
runtime_configuration, schedule
scenario_definition, schedule
</sql>
<select id="selectByExampleWithBLOBs" parameterType="io.metersphere.base.domain.ApiTestExample" resultMap="ResultMapWithBLOBs">
select
@ -127,11 +128,13 @@
</delete>
<insert id="insert" parameterType="io.metersphere.base.domain.ApiTestWithBLOBs">
insert into api_test (id, project_id, name,
description, create_time, update_time,
runtime_configuration, schedule)
description, status, create_time,
update_time, scenario_definition, schedule
)
values (#{id,jdbcType=VARCHAR}, #{projectId,jdbcType=VARCHAR}, #{name,jdbcType=VARCHAR},
#{description,jdbcType=VARCHAR}, #{createTime,jdbcType=BIGINT}, #{updateTime,jdbcType=BIGINT},
#{runtimeConfiguration,jdbcType=LONGVARCHAR}, #{schedule,jdbcType=LONGVARCHAR})
#{description,jdbcType=VARCHAR}, #{status,jdbcType=VARCHAR}, #{createTime,jdbcType=BIGINT},
#{updateTime,jdbcType=BIGINT}, #{scenarioDefinition,jdbcType=LONGVARCHAR}, #{schedule,jdbcType=LONGVARCHAR}
)
</insert>
<insert id="insertSelective" parameterType="io.metersphere.base.domain.ApiTestWithBLOBs">
insert into api_test
@ -148,14 +151,17 @@
<if test="description != null">
description,
</if>
<if test="status != null">
status,
</if>
<if test="createTime != null">
create_time,
</if>
<if test="updateTime != null">
update_time,
</if>
<if test="runtimeConfiguration != null">
runtime_configuration,
<if test="scenarioDefinition != null">
scenario_definition,
</if>
<if test="schedule != null">
schedule,
@ -174,14 +180,17 @@
<if test="description != null">
#{description,jdbcType=VARCHAR},
</if>
<if test="status != null">
#{status,jdbcType=VARCHAR},
</if>
<if test="createTime != null">
#{createTime,jdbcType=BIGINT},
</if>
<if test="updateTime != null">
#{updateTime,jdbcType=BIGINT},
</if>
<if test="runtimeConfiguration != null">
#{runtimeConfiguration,jdbcType=LONGVARCHAR},
<if test="scenarioDefinition != null">
#{scenarioDefinition,jdbcType=LONGVARCHAR},
</if>
<if test="schedule != null">
#{schedule,jdbcType=LONGVARCHAR},
@ -209,14 +218,17 @@
<if test="record.description != null">
description = #{record.description,jdbcType=VARCHAR},
</if>
<if test="record.status != null">
status = #{record.status,jdbcType=VARCHAR},
</if>
<if test="record.createTime != null">
create_time = #{record.createTime,jdbcType=BIGINT},
</if>
<if test="record.updateTime != null">
update_time = #{record.updateTime,jdbcType=BIGINT},
</if>
<if test="record.runtimeConfiguration != null">
runtime_configuration = #{record.runtimeConfiguration,jdbcType=LONGVARCHAR},
<if test="record.scenarioDefinition != null">
scenario_definition = #{record.scenarioDefinition,jdbcType=LONGVARCHAR},
</if>
<if test="record.schedule != null">
schedule = #{record.schedule,jdbcType=LONGVARCHAR},
@ -232,9 +244,10 @@
project_id = #{record.projectId,jdbcType=VARCHAR},
name = #{record.name,jdbcType=VARCHAR},
description = #{record.description,jdbcType=VARCHAR},
status = #{record.status,jdbcType=VARCHAR},
create_time = #{record.createTime,jdbcType=BIGINT},
update_time = #{record.updateTime,jdbcType=BIGINT},
runtime_configuration = #{record.runtimeConfiguration,jdbcType=LONGVARCHAR},
scenario_definition = #{record.scenarioDefinition,jdbcType=LONGVARCHAR},
schedule = #{record.schedule,jdbcType=LONGVARCHAR}
<if test="_parameter != null">
<include refid="Update_By_Example_Where_Clause" />
@ -246,6 +259,7 @@
project_id = #{record.projectId,jdbcType=VARCHAR},
name = #{record.name,jdbcType=VARCHAR},
description = #{record.description,jdbcType=VARCHAR},
status = #{record.status,jdbcType=VARCHAR},
create_time = #{record.createTime,jdbcType=BIGINT},
update_time = #{record.updateTime,jdbcType=BIGINT}
<if test="_parameter != null">
@ -264,14 +278,17 @@
<if test="description != null">
description = #{description,jdbcType=VARCHAR},
</if>
<if test="status != null">
status = #{status,jdbcType=VARCHAR},
</if>
<if test="createTime != null">
create_time = #{createTime,jdbcType=BIGINT},
</if>
<if test="updateTime != null">
update_time = #{updateTime,jdbcType=BIGINT},
</if>
<if test="runtimeConfiguration != null">
runtime_configuration = #{runtimeConfiguration,jdbcType=LONGVARCHAR},
<if test="scenarioDefinition != null">
scenario_definition = #{scenarioDefinition,jdbcType=LONGVARCHAR},
</if>
<if test="schedule != null">
schedule = #{schedule,jdbcType=LONGVARCHAR},
@ -284,9 +301,10 @@
set project_id = #{projectId,jdbcType=VARCHAR},
name = #{name,jdbcType=VARCHAR},
description = #{description,jdbcType=VARCHAR},
status = #{status,jdbcType=VARCHAR},
create_time = #{createTime,jdbcType=BIGINT},
update_time = #{updateTime,jdbcType=BIGINT},
runtime_configuration = #{runtimeConfiguration,jdbcType=LONGVARCHAR},
scenario_definition = #{scenarioDefinition,jdbcType=LONGVARCHAR},
schedule = #{schedule,jdbcType=LONGVARCHAR}
where id = #{id,jdbcType=VARCHAR}
</update>
@ -295,6 +313,7 @@
set project_id = #{projectId,jdbcType=VARCHAR},
name = #{name,jdbcType=VARCHAR},
description = #{description,jdbcType=VARCHAR},
status = #{status,jdbcType=VARCHAR},
create_time = #{createTime,jdbcType=BIGINT},
update_time = #{updateTime,jdbcType=BIGINT}
where id = #{id,jdbcType=VARCHAR}

View File

@ -1,11 +1,11 @@
package io.metersphere.base.mapper.ext;
import io.metersphere.controller.request.testplan.QueryTestPlanRequest;
import io.metersphere.dto.ApiTestDTO;
import io.metersphere.api.dto.APITestResult;
import io.metersphere.api.dto.QueryAPITestRequest;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface ExtApiTestMapper {
List<ApiTestDTO> list(@Param("request") QueryTestPlanRequest params);
List<APITestResult> list(@Param("request") QueryAPITestRequest request);
}

View File

@ -2,12 +2,12 @@
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="io.metersphere.base.mapper.ext.ExtApiTestMapper">
<resultMap id="BaseResultMap" type="io.metersphere.dto.ApiTestDTO"
<resultMap id="BaseResultMap" type="io.metersphere.api.dto.APITestResult"
extends="io.metersphere.base.mapper.ApiTestMapper.BaseResultMap">
<result column="project_name" property="projectName"/>
</resultMap>
<select id="list" resultMap="BaseResultMap" parameterType="io.metersphere.controller.request.testplan.QueryTestPlanRequest">
<select id="list" resultMap="BaseResultMap" parameterType="io.metersphere.api.dto.APITestResult">
select api_test.*, project.name as project_name
from api_test
left join project on api_test.project_id = project.id

View File

@ -0,0 +1,5 @@
package io.metersphere.commons.constants;
public enum APITestStatus {
Saved, Starting, Running, Completed, Error
}

View File

@ -1,99 +0,0 @@
package io.metersphere.controller;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import io.metersphere.base.domain.FileMetadata;
import io.metersphere.commons.constants.RoleConstants;
import io.metersphere.commons.utils.PageUtils;
import io.metersphere.commons.utils.Pager;
import io.metersphere.controller.request.testplan.*;
import io.metersphere.dto.ApiTestDTO;
import io.metersphere.service.FileService;
import io.metersphere.service.ApiTestService;
import io.metersphere.user.SessionUtils;
import org.apache.shiro.authz.annotation.Logical;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.util.List;
@RestController
@RequestMapping(value = "/api")
@RequiresRoles(value = {RoleConstants.TEST_MANAGER, RoleConstants.TEST_USER, RoleConstants.TEST_VIEWER}, logical = Logical.OR)
public class ApiTestController {
@Resource
private ApiTestService apiTestService;
@Resource
private FileService fileService;
@GetMapping("recent/{count}")
public List<ApiTestDTO> recentTestPlans(@PathVariable int count) {
String currentWorkspaceId = SessionUtils.getCurrentWorkspaceId();
QueryTestPlanRequest request = new QueryTestPlanRequest();
request.setWorkspaceId(currentWorkspaceId);
PageHelper.startPage(1, count, true);
return apiTestService.recentTestPlans(request);
}
@PostMapping("/list/{goPage}/{pageSize}")
public Pager<List<ApiTestDTO>> list(@PathVariable int goPage, @PathVariable int pageSize, @RequestBody QueryTestPlanRequest request) {
Page<Object> page = PageHelper.startPage(goPage, pageSize, true);
request.setWorkspaceId(SessionUtils.getCurrentWorkspaceId());
return PageUtils.setPageInfo(page, apiTestService.list(request));
}
@PostMapping(value = "/save", consumes = {"multipart/form-data"})
public String save(
@RequestPart("request") SaveTestPlanRequest request,
@RequestPart(value = "file") MultipartFile file
) {
return apiTestService.save(request, file);
}
@PostMapping(value = "/edit", consumes = {"multipart/form-data"})
public String edit(
@RequestPart("request") EditTestPlanRequest request,
@RequestPart(value = "file", required = false) MultipartFile file
) {
return apiTestService.edit(request, file);
}
@GetMapping("/get/{testId}")
public ApiTestDTO get(@PathVariable String testId) {
return apiTestService.get(testId);
}
@GetMapping("/get-runtime-config/{testId}")
public String getAdvancedConfiguration(@PathVariable String testId) {
return apiTestService.getRuntimeConfiguration(testId);
}
@PostMapping("/delete")
public void delete(@RequestBody DeleteTestPlanRequest request) {
apiTestService.delete(request);
}
@PostMapping("/run")
public void run(@RequestBody RunTestPlanRequest request) {
apiTestService.run(request);
}
@GetMapping("/file/metadata/{testId}")
public FileMetadata getFileMetadata(@PathVariable String testId) {
return fileService.getApiFileMetadataByTestId(testId);
}
@PostMapping("/file/download")
public ResponseEntity<byte[]> downloadJmx(@RequestBody FileOperationRequest fileOperationRequest) {
byte[] bytes = fileService.loadFileAsBytes(fileOperationRequest.getId());
return ResponseEntity.ok()
.contentType(MediaType.parseMediaType("application/octet-stream"))
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + fileOperationRequest.getName() + "\"")
.body(bytes);
}
}

View File

@ -15,7 +15,7 @@ import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
/**
* 统一处理返回结果集
*/
@RestControllerAdvice(value = {"io.metersphere.controller"})
@RestControllerAdvice(value = {"io.metersphere.controller", "io.metersphere.api.controller"})
public class ResultResponseBodyAdvice implements ResponseBodyAdvice<Object> {
@Override

View File

@ -1,15 +0,0 @@
package io.metersphere.dto;
import io.metersphere.base.domain.LoadTest;
public class ApiTestDTO extends LoadTest {
private String projectName;
public String getProjectName() {
return projectName;
}
public void setProjectName(String projectName) {
this.projectName = projectName;
}
}

View File

@ -1,194 +0,0 @@
package io.metersphere.service;
import io.metersphere.base.domain.*;
import io.metersphere.base.mapper.*;
import io.metersphere.base.mapper.ext.ExtApiTestMapper;
import io.metersphere.commons.exception.MSException;
import io.metersphere.controller.request.testplan.*;
import io.metersphere.dto.ApiTestDTO;
import io.metersphere.i18n.Translator;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.io.IOException;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
@Service
@Transactional(rollbackFor = Exception.class)
public class ApiTestService {
@Resource
private ApiTestMapper ApiTestMapper;
@Resource
private ExtApiTestMapper extApiTestMapper;
@Resource
private ProjectMapper projectMapper;
@Resource
private FileMetadataMapper fileMetadataMapper;
@Resource
private FileContentMapper fileContentMapper;
@Resource
private ApiTestFileMapper ApiTestFileMapper;
@Resource
private FileService fileService;
public List<ApiTestDTO> list(QueryTestPlanRequest request) {
return extApiTestMapper.list(request);
}
public void delete(DeleteTestPlanRequest request) {
ApiTestMapper.deleteByPrimaryKey(request.getId());
fileService.deleteFileByTestId(request.getId());
}
public String save(SaveTestPlanRequest request, MultipartFile file) {
if (file == null) {
throw new IllegalArgumentException("文件不能为空!");
}
final FileMetadata fileMetadata = saveFile(file);
final ApiTestWithBLOBs ApiTest = saveApiTest(request);
ApiTestFile ApiTestFile = new ApiTestFile();
ApiTestFile.setTestId(ApiTest.getId());
ApiTestFile.setFileId(fileMetadata.getId());
ApiTestFileMapper.insert(ApiTestFile);
return ApiTest.getId();
}
private ApiTestWithBLOBs saveApiTest(SaveTestPlanRequest request) {
ApiTestExample example = new ApiTestExample();
example.createCriteria().andNameEqualTo(request.getName()).andProjectIdEqualTo(request.getProjectId());
if (ApiTestMapper.countByExample(example) > 0) {
MSException.throwException(Translator.get("function_test_already_exists"));
}
final ApiTestWithBLOBs ApiTes = new ApiTestWithBLOBs();
ApiTes.setId(UUID.randomUUID().toString());
ApiTes.setName(request.getName());
ApiTes.setProjectId(request.getProjectId());
ApiTes.setCreateTime(System.currentTimeMillis());
ApiTes.setUpdateTime(System.currentTimeMillis());
ApiTes.setDescription("todo");
ApiTes.setRuntimeConfiguration(request.getRuntimeConfiguration());
ApiTestMapper.insert(ApiTes);
return ApiTes;
}
private FileMetadata saveFile(MultipartFile file) {
final FileMetadata fileMetadata = new FileMetadata();
fileMetadata.setId(UUID.randomUUID().toString());
fileMetadata.setName(file.getOriginalFilename());
fileMetadata.setSize(file.getSize());
fileMetadata.setCreateTime(System.currentTimeMillis());
fileMetadata.setUpdateTime(System.currentTimeMillis());
fileMetadata.setType("jmx");
fileMetadataMapper.insert(fileMetadata);
FileContent fileContent = new FileContent();
fileContent.setFileId(fileMetadata.getId());
try {
fileContent.setFile(file.getBytes());
} catch (IOException e) {
MSException.throwException(e);
}
fileContentMapper.insert(fileContent);
return fileMetadata;
}
public String edit(EditTestPlanRequest request, MultipartFile file) {
// 新选择了一个文件删除原来的文件
if (file != null) {
fileService.deleteFileByTestId(request.getId());
final FileMetadata fileMetadata = saveFile(file);
ApiTestFile ApiTestFile = new ApiTestFile();
ApiTestFile.setTestId(request.getId());
ApiTestFile.setFileId(fileMetadata.getId());
ApiTestFileMapper.insert(ApiTestFile);
}
final ApiTestWithBLOBs ApiTest = ApiTestMapper.selectByPrimaryKey(request.getId());
if (ApiTest == null) {
MSException.throwException("无法编辑测试,未找到测试:" + request.getId());
} else {
ApiTest.setName(request.getName());
ApiTest.setProjectId(request.getProjectId());
ApiTest.setUpdateTime(System.currentTimeMillis());
ApiTest.setDescription("todo");
ApiTest.setRuntimeConfiguration(request.getRuntimeConfiguration());
ApiTestMapper.updateByPrimaryKeySelective(ApiTest);
}
return request.getId();
}
public void run(RunTestPlanRequest request) {
final ApiTestWithBLOBs ApiTest = ApiTestMapper.selectByPrimaryKey(request.getId());
// if (ApiTest == null) {
// MSException.throwException("无法运行测试,未找到测试:" + request.getId());
// }
//
// final List<FileMetadata> fileMetadataList = fileService.getFileMetadataByTestId(request.getId());
// if (fileMetadataList == null) {
// MSException.throwException("无法运行测试无法获取测试文件元信息测试ID" + request.getId());
// }
//
// final FileContent fileContent = fileService.getFileContent(fileMetadata.getId());
// if (fileContent == null) {
// MSException.throwException("无法运行测试无法获取测试文件内容测试ID" + request.getId());
// }
//
// System.out.println("开始运行:" + ApiTest.getName());
// final Engine engine = EngineFactory.createEngine(fileMetadata.getType());
// if (engine == null) {
// MSException.throwException(String.format("无法运行测试未识别测试文件类型测试ID%s文件类型%s",
// request.getId(),
// fileMetadata.getType()));
// }
//
// boolean init = true;
// try {
//// init = engine.init(EngineFactory.createContext(ApiTest, fileMetadata, fileContent));
// } catch (Exception e) {
// MSException.throwException(e);
// }
// if (!init) {
// MSException.throwException(String.format("无法运行测试初始化运行环境失败测试ID%s", request.getId()));
// }
//
//// engine.start();
/// todo通过调用stop方法能够停止正在运行的engine但是如果部署了多个backend实例页面发送的停止请求如何定位到具体的engine
}
public List<ApiTestDTO> recentTestPlans(QueryTestPlanRequest request) {
// 查询最近的测试计划
request.setRecent(true);
return extApiTestMapper.list(request);
}
public ApiTestDTO get(String testId) {
QueryTestPlanRequest request = new QueryTestPlanRequest();
request.setId(testId);
List<ApiTestDTO> testDTOS = extApiTestMapper.list(request);
if (!CollectionUtils.isEmpty(testDTOS)) {
return testDTOS.get(0);
}
return null;
}
public String getRuntimeConfiguration(String testId) {
ApiTestWithBLOBs ApiTestWithBLOBs = ApiTestMapper.selectByPrimaryKey(testId);
return Optional.ofNullable(ApiTestWithBLOBs).orElse(new ApiTestWithBLOBs()).getRuntimeConfiguration();
}
}

View File

@ -194,19 +194,17 @@ CREATE TABLE IF NOT EXISTS `workspace` (
-- api start
CREATE TABLE IF NOT EXISTS `api_test` (
`id` varchar(50) NOT NULL COMMENT 'Test ID',
`project_id` varchar(50) NOT NULL COMMENT 'Project ID this test belongs to',
`name` varchar(64) NOT NULL COMMENT 'Test name',
`description` varchar(255) DEFAULT NULL COMMENT 'Test description',
`runtime_configuration` longtext COMMENT 'Load configuration (JSON format)',
`schedule` longtext COMMENT 'Test schedule (cron list)',
`create_time` bigint(13) NOT NULL COMMENT 'Create timestamp',
`update_time` bigint(13) NOT NULL COMMENT 'Update timestamp',
`id` varchar(50) COLLATE utf8mb4_bin NOT NULL COMMENT 'Test ID',
`project_id` varchar(50) COLLATE utf8mb4_bin NOT NULL COMMENT 'Project ID this test belongs to',
`name` varchar(64) COLLATE utf8mb4_bin NOT NULL COMMENT 'Test name',
`description` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL COMMENT 'Test description',
`scenario_definition` longtext COLLATE utf8mb4_bin COMMENT 'Scenario definition (JSON format)',
`schedule` longtext COLLATE utf8mb4_bin COMMENT 'Test schedule (cron list)',
`status` varchar(64) COLLATE utf8mb4_bin DEFAULT NULL,
`create_time` bigint(13) NOT NULL COMMENT 'Create timestamp',
`update_time` bigint(13) NOT NULL COMMENT 'Update timestamp',
PRIMARY KEY (`id`)
)
ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4
COLLATE = utf8mb4_bin;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
CREATE TABLE IF NOT EXISTS `api_test_file` (
`test_id` varchar(64) DEFAULT NULL,

View File

@ -1,6 +1,5 @@
<template>
<div id="menu-bar" v-if="isRouterAlive">
<div id="menu-bar">
<el-row type="flex">
<el-col :span="8">
<el-menu class="header-menu" :unique-opened="true" mode="horizontal" router :default-active='$route.path'>
@ -22,10 +21,10 @@
<template v-slot:title>{{$t('commons.test')}}</template>
<ms-recent-list :options="testRecent"/>
<el-divider/>
<ms-show-all :index="'/api/test/all'"/>
<ms-show-all :index="'/api/test/list/all'"/>
<ms-create-button :index="'/api/test/create'" :title="$t('load_test.create')"/>
<el-menu-item :index="testCaseProjectPath" class="blank_item"></el-menu-item>
<el-menu-item :index="testEditPath" class="blank_item"></el-menu-item>
<!-- <el-menu-item :index="testCaseProjectPath" class="blank_item"></el-menu-item>-->
<!-- <el-menu-item :index="testEditPath" class="blank_item"></el-menu-item>-->
</el-submenu>
<el-submenu v-if="isCurrentWorkspaceUser"
@ -34,7 +33,7 @@
<ms-recent-list :options="reportRecent"/>
<el-divider/>
<ms-show-all :index="'/api/report/all'"/>
<el-menu-item :index="reportViewPath" class="blank_item"></el-menu-item>
<!-- <el-menu-item :index="reportViewPath" class="blank_item"></el-menu-item>-->
</el-submenu>
</el-menu>
</el-col>
@ -63,10 +62,10 @@
data() {
return {
isCurrentWorkspaceUser: false,
testCaseProjectPath: '',
testEditPath: '',
reportViewPath: '',
isRouterAlive: true,
// testCaseProjectPath: '',
// testEditPath: '',
// reportViewPath: '',
// isRouterAlive: true,
projectRecent: {
title: this.$t('project.recent'),
url: "/project/recent/5",
@ -82,6 +81,9 @@
url: "/api/recent/5",
index: function (item) {
return '/api/test/edit/' + item.id;
},
router: function (item) {
return {path: '/api/test/edit', query: {id: item.id}}
}
},
reportRecent: {
@ -93,35 +95,35 @@
}
}
},
watch: {
'$route'(to, from) {
let path = to.path;
//
if (path.indexOf("/api/test/") >= 0) {
this.testCaseProjectPath = '/api/test/' + this.$route.params.projectId;
this.reload();
}
if (path.indexOf("/api/test/edit/") >= 0) {
this.testEditPath = '/api/test/edit/' + this.$route.params.testId;
this.reload();
}
if (path.indexOf("/api/report/view/") >= 0) {
this.reportViewPath = '/api/report/view/' + this.$route.params.reportId;
this.reload();
}
}
},
// watch: {
// '$route'(to, from) {
// let path = to.path;
// //
// if (path.indexOf("/api/test/") >= 0) {
// this.testCaseProjectPath = '/api/test/' + this.$route.params.projectId;
// this.reload();
// }
// if (path.indexOf("/api/test/edit/") >= 0) {
// this.testEditPath = '/api/test/edit/' + this.$route.params.testId;
// this.reload();
// }
// if (path.indexOf("/api/report/view/") >= 0) {
// this.reportViewPath = '/api/report/view/' + this.$route.params.reportId;
// this.reload();
// }
// }
// },
mounted() {
this.isCurrentWorkspaceUser = checkoutCurrentWorkspace();
},
methods: {
reload() {
this.isRouterAlive = false;
this.$nextTick(function () {
this.isRouterAlive = true;
})
}
}
// methods: {
// reload() {
// this.isRouterAlive = false;
// this.$nextTick(function () {
// this.isRouterAlive = true;
// })
// }
// }
}
</script>

View File

@ -14,7 +14,7 @@
<el-button type="primary" plain :disabled="isDisabled" @click="saveTest">保存</el-button>
</el-row>
</el-header>
<ms-api-scenario-config :scenarios="test.scenarioDefinition"/>
<ms-api-scenario-config :scenarios="test.scenarioDefinition" ref="config"/>
</el-container>
</el-card>
</div>
@ -23,26 +23,34 @@
<script>
import MsApiScenarioConfig from "./components/ApiScenarioConfig";
import {Test} from "./model/ScenarioModel"
export default {
name: "MsApiTestConfig",
components: {MsApiScenarioConfig},
props: ["id"],
data() {
return {
result: {},
projects: [],
change: false,
test: {
id: null,
projectId: null,
name: null,
scenarioDefinition: []
}
test: new Test()
}
},
beforeRouteUpdate(to, from, next) {
if (to.params.type === "edit") {
this.getTest(to.query.id);
} else {
this.test = new Test();
this.$refs.config.reset();
}
next();
},
watch: {
test: {
handler: function () {
@ -53,6 +61,19 @@
},
methods: {
getTest: function (id) {
this.result = this.$get("/api/get/" + id, response => {
let item = response.data;
this.test.reset({
id: item.id,
projectId: item.projectId,
name: item.name,
scenarioDefinition: JSON.parse(item.scenarioDefinition),
});
this.$refs.config.reset();
});
},
saveTest: function () {
this.change = false;
@ -83,6 +104,9 @@
this.result = this.$get("/project/listAll", response => {
this.projects = response.data;
})
if (this.id) {
this.getTest(this.id);
}
}
}
</script>

View File

@ -71,7 +71,6 @@
data() {
return {
result: {},
deletePath: "/api/delete",
condition: "",
projectId: null,
tableData: [],
@ -83,16 +82,18 @@
testId: null,
}
},
watch: {
'$route'(to) {
this.projectId = to.params.projectId;
this.search();
}
beforeRouteUpdate(to, from, next) {
this.projectId = to.params.projectId;
this.search();
next();
},
created: function () {
this.projectId = this.$route.params.projectId;
this.search();
},
methods: {
search() {
let param = {
@ -113,34 +114,27 @@
handleSelectionChange(val) {
this.multipleSelection = val;
},
handleEdit(testPlan) {
handleEdit(test) {
this.$router.push({
path: '/api/test/edit/' + testPlan.id,
path: '/api/test/edit?id=' + test.id,
})
},
handleDelete(testPlan) {
this.$alert(this.$t('load_test.delete_confirm') + testPlan.name + "", '', {
handleDelete(test) {
this.$alert(this.$t('load_test.delete_confirm') + test.name + "", '', {
confirmButtonText: this.$t('commons.confirm'),
callback: (action) => {
if (action === 'confirm') {
this._handleDelete(testPlan);
this.result = this.$post("/api/delete", {id: test.id}, () => {
this.$message({
message: this.$t('commons.delete_success'),
type: 'success'
});
this.initTableData();
});
}
}
});
},
_handleDelete(testPlan) {
let data = {
id: testPlan.id
};
this.result = this.$post(this.deletePath, data, () => {
this.$message({
message: this.$t('commons.delete_success'),
type: 'success'
});
this.initTableData();
});
},
}
}
}
</script>
@ -149,6 +143,4 @@
.test-content {
width: 100%;
}
</style>

View File

@ -41,14 +41,7 @@
options: ASSERTION_TYPE,
type: "",
}
},
methods: {
createRegex: function () {
return new Regex();
}
}
}
</script>

View File

@ -9,7 +9,7 @@
</div>
</div>
<div class="assertion-item-editing response-time" v-if="assertions.responseTime.isValid()">
<div class="assertion-item-editing response-time" v-if="isShow">
<div>
{{$t("api_test.request.assertions.response_time")}}
</div>
@ -31,6 +31,13 @@
props: {
assertions: Assertions
},
computed: {
isShow() {
let rt = this.assertions.responseTime;
return rt.responseInTime !== null && rt.responseInTime > 0;
}
}
}
</script>

View File

@ -80,10 +80,7 @@
},
created() {
if (this.requests.length === 0) {
this.createRequest();
this.select(this.requests[0]);
}
this.select(this.requests[0]);
}
}
</script>

View File

@ -43,12 +43,13 @@
import MsApiKeyValue from "./ApiKeyValue";
import MsApiBody from "./ApiBody";
import MsApiAssertions from "./ApiAssertions";
import {Request} from "../model/ScenarioModel";
export default {
name: "MsApiRequestForm",
components: {MsApiAssertions, MsApiBody, MsApiKeyValue},
props: {
request: Object
request: Request
},
data() {

View File

@ -6,7 +6,12 @@
<ms-api-collapse-item v-for="(scenario, index) in scenarios" :key="index"
:title="scenario.name" :name="index">
<template slot="title">
<div class="scenario-name">{{scenario.name}}</div>
<div class="scenario-name">
{{scenario.name}}
<span id="hint" v-if="!scenario.name">
{{$t('api_test.scenario.config')}}
</span>
</div>
<el-dropdown trigger="click" @command="handleCommand">
<span class="el-dropdown-link el-icon-more scenario-btn"/>
<el-dropdown-menu slot="dropdown">
@ -57,14 +62,13 @@
data() {
return {
activeName: 0,
selected: Object
selected: [Scenario, Request]
}
},
methods: {
createScenario: function () {
let scenario = new Scenario({name: "Scenario"});
this.scenarios.push(scenario);
this.scenarios.push(new Scenario());
},
deleteScenario: function (index) {
this.scenarios.splice(index, 1);
@ -85,6 +89,12 @@
},
select: function (obj) {
this.selected = obj;
},
reset: function () {
this.$nextTick(function () {
this.activeName = 0;
this.select(this.scenarios[0]);
});
}
},
@ -98,10 +108,7 @@
},
created() {
if (this.scenarios.length === 0) {
this.createScenario();
this.select(this.scenarios[0]);
}
this.select(this.scenarios[0]);
}
}
</script>
@ -131,6 +138,10 @@
width: 100%;
}
.scenario-name > #hint {
color: #8a8b8d;
}
.scenario-btn {
text-align: center;
padding: 13px;

View File

@ -21,12 +21,13 @@
<script>
import MsApiKeyValue from "./ApiKeyValue";
import {Scenario} from "../model/ScenarioModel";
export default {
name: "MsApiScenarioForm",
components: {MsApiKeyValue},
props: {
scenario: Object
scenario: Scenario
},
data() {

View File

@ -1,9 +1,23 @@
import {generateId} from "element-ui/src/utils/util";
const assign = function (obj, options) {
for (let name in options) {
if (options.hasOwnProperty(name)) {
obj[name] = options[name];
if (options) {
for (let name in options) {
if (options.hasOwnProperty(name)) {
if (!(obj[name] instanceof Array)) {
obj[name] = options[name];
}
}
}
}
}
const assigns = function (target, source, type) {
if (target instanceof Array && source instanceof Array) {
if (source && source.length > 0) {
source.forEach((options) => {
target.push(new type(options));
})
}
}
}
@ -19,8 +33,32 @@ export const ASSERTION_TYPE = {
RESPONSE_TIME: "RESPONSE_TIME"
}
export class Test {
constructor(options) {
this.reset(options);
}
reset(options) {
options = this.getDefaultOptions(options);
this.id = null;
this.name = null;
this.projectId = null;
this.scenarioDefinition = [];
assign(this, options);
assigns(this.scenarioDefinition, options.scenarioDefinition, Scenario);
}
getDefaultOptions(options) {
options = options || {};
options.scenarioDefinition = options.scenarioDefinition || [new Scenario()];
return options;
}
}
export class Scenario {
constructor(options) {
options = this.getDefaultOptions(options);
this.name = null;
this.url = null;
this.variables = [];
@ -28,32 +66,55 @@ export class Scenario {
this.requests = [];
assign(this, options);
assigns(this.variables, options.variables, KeyValue);
assigns(this.headers, options.headers, KeyValue);
assigns(this.requests, options.requests, Request);
}
getDefaultOptions(options) {
options = options || {};
options.requests = options.requests || [new Request()];
return options;
}
}
export class Request {
constructor(options) {
options = this.getDefaultOptions(options);
this.randomId = generateId();
this.name = null;
this.url = null;
this.method = null;
this.parameters = [];
this.headers = [];
this.body = new Body();
this.assertions = new Assertions();
this.body = null;
this.assertions = null;
this.extract = [];
assign(this, options);
assigns(this.parameters, options.parameters, KeyValue);
assigns(this.headers, options.headers, KeyValue);
// TODO assigns extract
}
getDefaultOptions(options) {
options = options || {};
options.method = "GET";
options.body = new Body(options.body);
options.assertions = new Assertions(options.assertions);
return options;
}
}
export class Body {
constructor(options) {
options = options || {};
this.type = null;
this.text = null;
this.kvs = [];
assign(this, options);
assigns(this.kvs, options.kvs, KeyValue);
}
isKV() {
@ -63,6 +124,7 @@ export class Body {
export class KeyValue {
constructor(options) {
options = options || {};
this.key = null;
this.value = null;
@ -72,11 +134,20 @@ export class KeyValue {
export class Assertions {
constructor(options) {
options = this.getDefaultOptions(options);
this.text = [];
this.regex = [];
this.responseTime = new ResponseTime();
this.responseTime = null;
assign(this, options);
assigns(this.text, options.text, KeyValue);
assigns(this.regex, options.regex, KeyValue);
}
getDefaultOptions(options) {
options = options || {};
options.responseTime = new ResponseTime(options.responseTime);
return options;
}
}
@ -88,6 +159,7 @@ class AssertionType {
export class Text extends AssertionType {
constructor(options) {
options = options || {};
super(ASSERTION_TYPE.TEXT);
this.subject = null;
this.condition = null;
@ -99,6 +171,7 @@ export class Text extends AssertionType {
export class Regex extends AssertionType {
constructor(options) {
options = options || {};
super(ASSERTION_TYPE.REGEX);
this.subject = null;
this.expression = null;
@ -110,14 +183,11 @@ export class Regex extends AssertionType {
export class ResponseTime extends AssertionType {
constructor(options) {
options = options || {};
super(ASSERTION_TYPE.RESPONSE_TIME);
this.responseInTime = null;
assign(this, options);
}
isValid() {
return this.responseInTime !== null && this.responseInTime > 0;
}
}

View File

@ -9,7 +9,7 @@
name: "MsCreateTest",
props: {
show: Boolean,
to: String,
to: [String, Object],
title: {
type: String,
default: function () {

View File

@ -95,25 +95,14 @@ const router = new VueRouter({
component: ApiTestHome,
},
{
path: 'test/create',
name: "createAPITest",
path: "test/:type",
name: "ApiTestConfig",
component: ApiTestConfig,
props: (route) => ({id: route.query.id})
},
{
path: "test/edit/:testId",
name: "editAPITest",
component: ApiTestConfig,
props: {
content: (route) => {
return {
...route.params
}
}
}
},
{
path: "test/:projectId",
name: "fucPlan",
path: "test/list/:projectId",
name: "ApiTestList",
component: ApiTestList
},
{

View File

@ -173,6 +173,7 @@ export default {
input_name: "请输入测试名称",
select_project: "请选择项目",
scenario: {
config: "场景配置",
input_name: "请输入场景名称",
name: "场景名称",
base_url: "基础URL",