Merge remote-tracking branch 'origin/master'

This commit is contained in:
song.tianyang 2021-03-11 16:36:42 +08:00
commit 9f33d88d3a
59 changed files with 2550 additions and 124582 deletions

View File

@ -422,6 +422,12 @@
<version>${jmeter.version}</version>
</dependency>
<!-- 添加jmeter包支持导入的jmx能正常执行 -->
<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId>
<version>2.5.0</version>
</dependency>
</dependencies>
<build>

View File

@ -34,4 +34,14 @@ public class SaveApiPlanRequest {
private String projectId;
/**
* 项目环境对应关系
*/
private Map<String, String> envMap;
/**
* 用例的环境的对应关系
*/
private Map<String, List<String>> mapping;
}

View File

@ -489,6 +489,7 @@ public class MsJmeterParser extends ApiImportAbstractParser<ScenarioImport> {
assertionJsonPath.setDescription(jsonPathAssertion.getName());
assertionJsonPath.setExpression(jsonPathAssertion.getJsonPath());
assertionJsonPath.setExpect(jsonPathAssertion.getExpectedValue());
assertionJsonPath.setOption(jsonPathAssertion.getPropertyAsString("ASS_OPTION"));
assertions.setName(jsonPathAssertion.getName());
assertions.getJsonPath().add(assertionJsonPath);
} else if (key instanceof XPath2Assertion) {

View File

@ -10,6 +10,7 @@ public class MsAssertionJsonPath extends MsAssertionType {
private String expect;
private String expression;
private String description;
private String option = "REGEX";
public MsAssertionJsonPath() {
setType(MsAssertionType.JSON_PATH);

View File

@ -96,7 +96,12 @@ public class MsAssertions extends MsTestElement {
assertion.setJsonValidationBool(true);
assertion.setExpectNull(false);
assertion.setInvert(false);
assertion.setIsRegex(true);
assertion.setProperty("ASS_OPTION",assertionJsonPath.getOption());
if (StringUtils.isEmpty(assertionJsonPath.getOption()) || "REGEX".equals(assertionJsonPath.getOption())) {
assertion.setIsRegex(true);
} else {
assertion.setIsRegex(false);
}
return assertion;
}

View File

@ -40,6 +40,8 @@ public class MsJSR223Processor extends MsTestElement {
if (StringUtils.isNotEmpty(name) && !config.isOperating()) {
processor.setName(this.getName() + "<->" + name);
}
processor.setProperty("MS-ID", this.getId());
processor.setProperty(TestElement.TEST_CLASS, JSR223Sampler.class.getName());
processor.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("TestBeanGUI"));
processor.setProperty("cacheKey", "true");

View File

@ -82,6 +82,7 @@ public class MsDubboSampler extends MsTestElement {
}
sampler.setProperty(TestElement.TEST_CLASS, DubboSample.class.getName());
sampler.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("DubboSampleGui"));
sampler.setProperty("MS-ID", this.getId());
sampler.addTestElement(configCenter(this.getConfigCenter()));
sampler.addTestElement(registryCenter(this.getRegistryCenter()));

View File

@ -106,6 +106,7 @@ public class MsHTTPSamplerProxy extends MsTestElement {
}
sampler.setProperty(TestElement.TEST_CLASS, HTTPSamplerProxy.class.getName());
sampler.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("HttpTestSampleGui"));
sampler.setProperty("MS-ID", this.getId());
sampler.setMethod(this.getMethod());
sampler.setContentEncoding("UTF-8");
sampler.setConnectTimeout(this.getConnectTimeout() == null ? "6000" : this.getConnectTimeout());

View File

@ -119,6 +119,8 @@ public class MsJDBCSampler extends MsTestElement {
}
sampler.setProperty(TestElement.TEST_CLASS, JDBCSampler.class.getName());
sampler.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("TestBeanGUI"));
sampler.setProperty("MS-ID", this.getId());
// request.getDataSource() 是ID需要转换为Name
sampler.setProperty("dataSource", this.dataSource.getName());
sampler.setProperty("query", this.getQuery());

View File

@ -117,6 +117,7 @@ public class MsTCPSampler extends MsTestElement {
if (StringUtils.isNotEmpty(name) && !config.isOperating()) {
tcpSampler.setName(this.getName() + "<->" + name);
}
tcpSampler.setProperty("MS-ID", this.getId());
tcpSampler.setProperty(TestElement.TEST_CLASS, TCPSampler.class.getName());
tcpSampler.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("TCPSamplerGui"));

View File

@ -239,7 +239,7 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl
LogUtil.error(e.getMessage(), e);
}
}
sendTask(report, reportUrl, testResult);
sendTask(report, reportUrl, testResult);
}
private static void sendTask(ApiTestReport report, String reportUrl, TestResult testResult) {
@ -303,6 +303,7 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl
private RequestResult getRequestResult(SampleResult result) {
RequestResult requestResult = new RequestResult();
requestResult.setId(result.getSamplerId());
requestResult.setName(result.getSampleLabel());
requestResult.setUrl(result.getUrlAsString());
requestResult.setMethod(getMethod(result));

View File

@ -7,6 +7,8 @@ import java.util.List;
@Data
public class RequestResult {
// 请求ID
private String id;
private String name;

View File

@ -611,27 +611,38 @@ public class ApiAutomationService {
if (CollectionUtils.isEmpty(request.getPlanIds())) {
MSException.throwException(Translator.get("plan id is null "));
}
List<String> scenarioIds = request.getScenarioIds();
if (request.isSelectAllDate()) {
scenarioIds = this.getAllScenarioIdsByFontedSelect(
request.getModuleIds(), request.getName(), request.getProjectId(), request.getFilters(), request.getUnSelectIds());
}
// List<String> scenarioIds = request.getScenarioIds();
// if (request.isSelectAllDate()) {
// scenarioIds = this.getAllScenarioIdsByFontedSelect(
// request.getModuleIds(), request.getName(), request.getProjectId(), request.getFilters(), request.getUnSelectIds());
// }
Map<String, List<String>> mapping = request.getMapping();
Map<String, String> envMap = request.getEnvMap();
Set<String> set = mapping.keySet();
List<TestPlanDTO> list = extTestPlanMapper.selectByIds(request.getPlanIds());
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
ExtTestPlanScenarioCaseMapper scenarioBatchMapper = sqlSession.getMapper(ExtTestPlanScenarioCaseMapper.class);
ExtTestPlanApiCaseMapper apiCaseBatchMapper = sqlSession.getMapper(ExtTestPlanApiCaseMapper.class);
for (TestPlanDTO testPlan : list) {
if (scenarioIds != null) {
for (String scenarioId : scenarioIds) {
if (!set.isEmpty()) {
set.forEach(id -> {
Map<String, String> newEnvMap = new HashMap<>(16);
if (envMap != null && !envMap.isEmpty()) {
List<String> lt = mapping.get(id);
lt.forEach(l -> {
newEnvMap.put(l, envMap.get(l));
});
}
TestPlanApiScenario testPlanApiScenario = new TestPlanApiScenario();
testPlanApiScenario.setId(UUID.randomUUID().toString());
testPlanApiScenario.setApiScenarioId(scenarioId);
testPlanApiScenario.setApiScenarioId(id);
testPlanApiScenario.setTestPlanId(testPlan.getId());
testPlanApiScenario.setCreateTime(System.currentTimeMillis());
testPlanApiScenario.setUpdateTime(System.currentTimeMillis());
testPlanApiScenario.setEnvironment(JSON.toJSONString(newEnvMap));
scenarioBatchMapper.insertIfNotExists(testPlanApiScenario);
}
});
}
if (request.getApiIds() != null) {
for (String caseId : request.getApiIds()) {

View File

@ -1,8 +1,7 @@
package io.metersphere.base.domain;
import lombok.Data;
import java.io.Serializable;
import lombok.Data;
@Data
public class ApiDefinition implements Serializable {
@ -38,5 +37,7 @@ public class ApiDefinition implements Serializable {
private String tags;
private String originalState;
private static final long serialVersionUID = 1L;
}

View File

@ -1193,6 +1193,76 @@ public class ApiDefinitionExample {
addCriterion("tags not between", value1, value2, "tags");
return (Criteria) this;
}
public Criteria andOriginalStateIsNull() {
addCriterion("original_state is null");
return (Criteria) this;
}
public Criteria andOriginalStateIsNotNull() {
addCriterion("original_state is not null");
return (Criteria) this;
}
public Criteria andOriginalStateEqualTo(String value) {
addCriterion("original_state =", value, "originalState");
return (Criteria) this;
}
public Criteria andOriginalStateNotEqualTo(String value) {
addCriterion("original_state <>", value, "originalState");
return (Criteria) this;
}
public Criteria andOriginalStateGreaterThan(String value) {
addCriterion("original_state >", value, "originalState");
return (Criteria) this;
}
public Criteria andOriginalStateGreaterThanOrEqualTo(String value) {
addCriterion("original_state >=", value, "originalState");
return (Criteria) this;
}
public Criteria andOriginalStateLessThan(String value) {
addCriterion("original_state <", value, "originalState");
return (Criteria) this;
}
public Criteria andOriginalStateLessThanOrEqualTo(String value) {
addCriterion("original_state <=", value, "originalState");
return (Criteria) this;
}
public Criteria andOriginalStateLike(String value) {
addCriterion("original_state like", value, "originalState");
return (Criteria) this;
}
public Criteria andOriginalStateNotLike(String value) {
addCriterion("original_state not like", value, "originalState");
return (Criteria) this;
}
public Criteria andOriginalStateIn(List<String> values) {
addCriterion("original_state in", values, "originalState");
return (Criteria) this;
}
public Criteria andOriginalStateNotIn(List<String> values) {
addCriterion("original_state not in", values, "originalState");
return (Criteria) this;
}
public Criteria andOriginalStateBetween(String value1, String value2) {
addCriterion("original_state between", value1, value2, "originalState");
return (Criteria) this;
}
public Criteria andOriginalStateNotBetween(String value1, String value2) {
addCriterion("original_state not between", value1, value2, "originalState");
return (Criteria) this;
}
}
public static class Criteria extends GeneratedCriteria {

View File

@ -43,5 +43,7 @@ public class ApiScenario implements Serializable {
private Integer num;
private String originalState;
private static final long serialVersionUID = 1L;
}

View File

@ -1393,6 +1393,76 @@ public class ApiScenarioExample {
addCriterion("num not between", value1, value2, "num");
return (Criteria) this;
}
public Criteria andOriginalStateIsNull() {
addCriterion("original_state is null");
return (Criteria) this;
}
public Criteria andOriginalStateIsNotNull() {
addCriterion("original_state is not null");
return (Criteria) this;
}
public Criteria andOriginalStateEqualTo(String value) {
addCriterion("original_state =", value, "originalState");
return (Criteria) this;
}
public Criteria andOriginalStateNotEqualTo(String value) {
addCriterion("original_state <>", value, "originalState");
return (Criteria) this;
}
public Criteria andOriginalStateGreaterThan(String value) {
addCriterion("original_state >", value, "originalState");
return (Criteria) this;
}
public Criteria andOriginalStateGreaterThanOrEqualTo(String value) {
addCriterion("original_state >=", value, "originalState");
return (Criteria) this;
}
public Criteria andOriginalStateLessThan(String value) {
addCriterion("original_state <", value, "originalState");
return (Criteria) this;
}
public Criteria andOriginalStateLessThanOrEqualTo(String value) {
addCriterion("original_state <=", value, "originalState");
return (Criteria) this;
}
public Criteria andOriginalStateLike(String value) {
addCriterion("original_state like", value, "originalState");
return (Criteria) this;
}
public Criteria andOriginalStateNotLike(String value) {
addCriterion("original_state not like", value, "originalState");
return (Criteria) this;
}
public Criteria andOriginalStateIn(List<String> values) {
addCriterion("original_state in", values, "originalState");
return (Criteria) this;
}
public Criteria andOriginalStateNotIn(List<String> values) {
addCriterion("original_state not in", values, "originalState");
return (Criteria) this;
}
public Criteria andOriginalStateBetween(String value1, String value2) {
addCriterion("original_state between", value1, value2, "originalState");
return (Criteria) this;
}
public Criteria andOriginalStateNotBetween(String value1, String value2) {
addCriterion("original_state not between", value1, value2, "originalState");
return (Criteria) this;
}
}
public static class Criteria extends GeneratedCriteria {

View File

@ -18,6 +18,7 @@
<result column="path" jdbcType="VARCHAR" property="path" />
<result column="num" jdbcType="INTEGER" property="num" />
<result column="tags" jdbcType="VARCHAR" property="tags" />
<result column="original_state" jdbcType="VARCHAR" property="originalState" />
</resultMap>
<resultMap extends="BaseResultMap" id="ResultMapWithBLOBs" type="io.metersphere.base.domain.ApiDefinitionWithBLOBs">
<result column="description" jdbcType="LONGVARCHAR" property="description" />
@ -84,7 +85,7 @@
</sql>
<sql id="Base_Column_List">
id, project_id, `name`, `method`, module_path, environment_id, schedule, `status`,
module_id, user_id, create_time, update_time, protocol, `path`, num, tags
module_id, user_id, create_time, update_time, protocol, `path`, num, tags, original_state
</sql>
<sql id="Blob_Column_List">
description, request, response
@ -143,15 +144,15 @@
schedule, `status`, module_id,
user_id, create_time, update_time,
protocol, `path`, num,
tags, description, request,
response)
tags, original_state, description,
request, response)
values (#{id,jdbcType=VARCHAR}, #{projectId,jdbcType=VARCHAR}, #{name,jdbcType=VARCHAR},
#{method,jdbcType=VARCHAR}, #{modulePath,jdbcType=VARCHAR}, #{environmentId,jdbcType=VARCHAR},
#{schedule,jdbcType=VARCHAR}, #{status,jdbcType=VARCHAR}, #{moduleId,jdbcType=VARCHAR},
#{userId,jdbcType=VARCHAR}, #{createTime,jdbcType=BIGINT}, #{updateTime,jdbcType=BIGINT},
#{protocol,jdbcType=VARCHAR}, #{path,jdbcType=VARCHAR}, #{num,jdbcType=INTEGER},
#{tags,jdbcType=VARCHAR}, #{description,jdbcType=LONGVARCHAR}, #{request,jdbcType=LONGVARCHAR},
#{response,jdbcType=LONGVARCHAR})
#{tags,jdbcType=VARCHAR}, #{originalState,jdbcType=VARCHAR}, #{description,jdbcType=LONGVARCHAR},
#{request,jdbcType=LONGVARCHAR}, #{response,jdbcType=LONGVARCHAR})
</insert>
<insert id="insertSelective" parameterType="io.metersphere.base.domain.ApiDefinitionWithBLOBs">
insert into api_definition
@ -204,6 +205,9 @@
<if test="tags != null">
tags,
</if>
<if test="originalState != null">
original_state,
</if>
<if test="description != null">
description,
</if>
@ -263,6 +267,9 @@
<if test="tags != null">
#{tags,jdbcType=VARCHAR},
</if>
<if test="originalState != null">
#{originalState,jdbcType=VARCHAR},
</if>
<if test="description != null">
#{description,jdbcType=LONGVARCHAR},
</if>
@ -331,6 +338,9 @@
<if test="record.tags != null">
tags = #{record.tags,jdbcType=VARCHAR},
</if>
<if test="record.originalState != null">
original_state = #{record.originalState,jdbcType=VARCHAR},
</if>
<if test="record.description != null">
description = #{record.description,jdbcType=LONGVARCHAR},
</if>
@ -363,6 +373,7 @@
`path` = #{record.path,jdbcType=VARCHAR},
num = #{record.num,jdbcType=INTEGER},
tags = #{record.tags,jdbcType=VARCHAR},
original_state = #{record.originalState,jdbcType=VARCHAR},
description = #{record.description,jdbcType=LONGVARCHAR},
request = #{record.request,jdbcType=LONGVARCHAR},
response = #{record.response,jdbcType=LONGVARCHAR}
@ -387,7 +398,8 @@
protocol = #{record.protocol,jdbcType=VARCHAR},
`path` = #{record.path,jdbcType=VARCHAR},
num = #{record.num,jdbcType=INTEGER},
tags = #{record.tags,jdbcType=VARCHAR}
tags = #{record.tags,jdbcType=VARCHAR},
original_state = #{record.originalState,jdbcType=VARCHAR}
<if test="_parameter != null">
<include refid="Update_By_Example_Where_Clause" />
</if>
@ -440,6 +452,9 @@
<if test="tags != null">
tags = #{tags,jdbcType=VARCHAR},
</if>
<if test="originalState != null">
original_state = #{originalState,jdbcType=VARCHAR},
</if>
<if test="description != null">
description = #{description,jdbcType=LONGVARCHAR},
</if>
@ -469,6 +484,7 @@
`path` = #{path,jdbcType=VARCHAR},
num = #{num,jdbcType=INTEGER},
tags = #{tags,jdbcType=VARCHAR},
original_state = #{originalState,jdbcType=VARCHAR},
description = #{description,jdbcType=LONGVARCHAR},
request = #{request,jdbcType=LONGVARCHAR},
response = #{response,jdbcType=LONGVARCHAR}
@ -490,7 +506,8 @@
protocol = #{protocol,jdbcType=VARCHAR},
`path` = #{path,jdbcType=VARCHAR},
num = #{num,jdbcType=INTEGER},
tags = #{tags,jdbcType=VARCHAR}
tags = #{tags,jdbcType=VARCHAR},
original_state = #{originalState,jdbcType=VARCHAR}
where id = #{id,jdbcType=VARCHAR}
</update>
</mapper>

View File

@ -21,6 +21,7 @@
<result column="last_result" jdbcType="VARCHAR" property="lastResult" />
<result column="report_id" jdbcType="VARCHAR" property="reportId" />
<result column="num" jdbcType="INTEGER" property="num" />
<result column="original_state" jdbcType="VARCHAR" property="originalState" />
</resultMap>
<resultMap extends="BaseResultMap" id="ResultMapWithBLOBs" type="io.metersphere.base.domain.ApiScenarioWithBLOBs">
<result column="scenario_definition" jdbcType="LONGVARCHAR" property="scenarioDefinition" />
@ -87,7 +88,7 @@
<sql id="Base_Column_List">
id, project_id, tags, user_id, api_scenario_module_id, module_path, `name`, `level`,
`status`, principal, step_total, follow_people, schedule, create_time, update_time,
pass_rate, last_result, report_id, num
pass_rate, last_result, report_id, num, original_state
</sql>
<sql id="Blob_Column_List">
scenario_definition, description
@ -147,16 +148,16 @@
principal, step_total, follow_people,
schedule, create_time, update_time,
pass_rate, last_result, report_id,
num, scenario_definition, description
)
num, original_state, scenario_definition,
description)
values (#{id,jdbcType=VARCHAR}, #{projectId,jdbcType=VARCHAR}, #{tags,jdbcType=VARCHAR},
#{userId,jdbcType=VARCHAR}, #{apiScenarioModuleId,jdbcType=VARCHAR}, #{modulePath,jdbcType=VARCHAR},
#{name,jdbcType=VARCHAR}, #{level,jdbcType=VARCHAR}, #{status,jdbcType=VARCHAR},
#{principal,jdbcType=VARCHAR}, #{stepTotal,jdbcType=INTEGER}, #{followPeople,jdbcType=VARCHAR},
#{schedule,jdbcType=VARCHAR}, #{createTime,jdbcType=BIGINT}, #{updateTime,jdbcType=BIGINT},
#{passRate,jdbcType=VARCHAR}, #{lastResult,jdbcType=VARCHAR}, #{reportId,jdbcType=VARCHAR},
#{num,jdbcType=INTEGER}, #{scenarioDefinition,jdbcType=LONGVARCHAR}, #{description,jdbcType=LONGVARCHAR}
)
#{num,jdbcType=INTEGER}, #{originalState,jdbcType=VARCHAR}, #{scenarioDefinition,jdbcType=LONGVARCHAR},
#{description,jdbcType=LONGVARCHAR})
</insert>
<insert id="insertSelective" parameterType="io.metersphere.base.domain.ApiScenarioWithBLOBs">
insert into api_scenario
@ -218,6 +219,9 @@
<if test="num != null">
num,
</if>
<if test="originalState != null">
original_state,
</if>
<if test="scenarioDefinition != null">
scenario_definition,
</if>
@ -283,6 +287,9 @@
<if test="num != null">
#{num,jdbcType=INTEGER},
</if>
<if test="originalState != null">
#{originalState,jdbcType=VARCHAR},
</if>
<if test="scenarioDefinition != null">
#{scenarioDefinition,jdbcType=LONGVARCHAR},
</if>
@ -357,6 +364,9 @@
<if test="record.num != null">
num = #{record.num,jdbcType=INTEGER},
</if>
<if test="record.originalState != null">
original_state = #{record.originalState,jdbcType=VARCHAR},
</if>
<if test="record.scenarioDefinition != null">
scenario_definition = #{record.scenarioDefinition,jdbcType=LONGVARCHAR},
</if>
@ -389,6 +399,7 @@
last_result = #{record.lastResult,jdbcType=VARCHAR},
report_id = #{record.reportId,jdbcType=VARCHAR},
num = #{record.num,jdbcType=INTEGER},
original_state = #{record.originalState,jdbcType=VARCHAR},
scenario_definition = #{record.scenarioDefinition,jdbcType=LONGVARCHAR},
description = #{record.description,jdbcType=LONGVARCHAR}
<if test="_parameter != null">
@ -415,7 +426,8 @@
pass_rate = #{record.passRate,jdbcType=VARCHAR},
last_result = #{record.lastResult,jdbcType=VARCHAR},
report_id = #{record.reportId,jdbcType=VARCHAR},
num = #{record.num,jdbcType=INTEGER}
num = #{record.num,jdbcType=INTEGER},
original_state = #{record.originalState,jdbcType=VARCHAR}
<if test="_parameter != null">
<include refid="Update_By_Example_Where_Clause" />
</if>
@ -477,6 +489,9 @@
<if test="num != null">
num = #{num,jdbcType=INTEGER},
</if>
<if test="originalState != null">
original_state = #{originalState,jdbcType=VARCHAR},
</if>
<if test="scenarioDefinition != null">
scenario_definition = #{scenarioDefinition,jdbcType=LONGVARCHAR},
</if>
@ -506,6 +521,7 @@
last_result = #{lastResult,jdbcType=VARCHAR},
report_id = #{reportId,jdbcType=VARCHAR},
num = #{num,jdbcType=INTEGER},
original_state = #{originalState,jdbcType=VARCHAR},
scenario_definition = #{scenarioDefinition,jdbcType=LONGVARCHAR},
description = #{description,jdbcType=LONGVARCHAR}
where id = #{id,jdbcType=VARCHAR}
@ -529,7 +545,8 @@
pass_rate = #{passRate,jdbcType=VARCHAR},
last_result = #{lastResult,jdbcType=VARCHAR},
report_id = #{reportId,jdbcType=VARCHAR},
num = #{num,jdbcType=INTEGER}
num = #{num,jdbcType=INTEGER},
original_state = #{originalState,jdbcType=VARCHAR}
where id = #{id,jdbcType=VARCHAR}
</update>
</mapper>

View File

@ -235,7 +235,7 @@
<update id="removeToGc">
update api_definition
set
set original_state=status,
status = 'Trash'
where id in
<foreach collection="ids" item="v" separator="," open="(" close=")">
@ -245,7 +245,7 @@
<update id="removeToGcByExample" parameterType="io.metersphere.base.domain.ApiDefinitionExample">
update api_definition
set
set original_state=status,
status = 'Trash', module_path = null, module_id = null
<if test="_parameter != null">
<include refid="Example_Where_Clause" />
@ -255,7 +255,7 @@
<update id="reduction">
update api_definition
set
status = 'Underway'
status = original_state
where id in
<foreach collection="ids" item="v" separator="," open="(" close=")">
#{v}

View File

@ -275,7 +275,7 @@
<update id="removeToGc">
update api_scenario
set
set original_state=status,
status = 'Trash'
where id in
<foreach collection="ids" item="v" separator="," open="(" close=")">
@ -285,7 +285,7 @@
<update id="removeToGcByExample" parameterType="io.metersphere.base.domain.ApiScenarioExample">
update api_scenario
set
set original_state=status,
status = 'Trash', module_path = null, api_scenario_module_id = null
<if test="_parameter != null">
<include refid="Example_Where_Clause" />
@ -295,7 +295,7 @@
<update id="reduction">
update api_scenario
set
status = 'Underway'
status = original_state
where id in
<foreach collection="ids" item="v" separator="," open="(" close=")">
#{v}

View File

@ -5,8 +5,8 @@
<insert id="insertIfNotExists" parameterType="io.metersphere.base.domain.TestPlanApiScenario">
-- 查询没有数据再插入
INSERT INTO test_plan_api_scenario(id, test_plan_id, api_scenario_id, create_time, update_time)
SELECT #{request.id}, #{request.testPlanId}, #{request.apiScenarioId}, #{request.createTime}, #{request.updateTime}
INSERT INTO test_plan_api_scenario(id, test_plan_id, api_scenario_id, create_time, update_time, environment)
SELECT #{request.id}, #{request.testPlanId}, #{request.apiScenarioId}, #{request.createTime}, #{request.updateTime}, #{request.environment}
FROM DUAL
WHERE NOT EXISTS(
SELECT id FROM

View File

@ -1,18 +1,35 @@
package io.metersphere.commons.user;
import io.metersphere.commons.utils.CodingUtil;
import io.metersphere.dto.UserDTO;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import java.io.Serializable;
import java.util.Arrays;
import java.util.List;
@Setter
@Getter
public class SessionUser extends UserDTO implements Serializable {
public static final String secret = "9a9rdqPlTqhpZzkq";
public static final String iv = "1Av7hf9PgHusUHRm";
private static final long serialVersionUID = -7149638440406959033L;
private String csrfToken;
private SessionUser() {
}
public static SessionUser fromUser(UserDTO user) {
SessionUser sessionUser = new SessionUser();
BeanUtils.copyProperties(user, sessionUser);
List<String> infos = Arrays.asList(user.getId(), RandomStringUtils.random(6), "" + System.currentTimeMillis());
sessionUser.csrfToken = CodingUtil.aesEncrypt(StringUtils.join(infos, "|"), secret, iv);
return sessionUser;
}

View File

@ -62,14 +62,14 @@ public class SessionUtils {
}
public static String getCurrentWorkspaceId() {
return Optional.ofNullable(getUser()).orElse(new SessionUser()).getLastWorkspaceId();
return getUser().getLastWorkspaceId();
}
public static String getCurrentOrganizationId() {
return Optional.ofNullable(getUser()).orElse(new SessionUser()).getLastOrganizationId();
return getUser().getLastOrganizationId();
}
public static String getCurrentProjectId() {
return Optional.ofNullable(getUser()).orElse(new SessionUser()).getLastProjectId();
return getUser().getLastProjectId();
}
}

View File

@ -44,6 +44,10 @@ public class ShiroUtils {
// filterChainDefinitionMap.put("/document/**", "anon");
}
public static void ignoreCsrfFilter(Map<String, String> filterChainDefinitionMap) {
filterChainDefinitionMap.put("/", "apikey, authc"); // 跳转到 / 不用校验 csrf
}
public static Cookie getSessionIdCookie(){
SimpleCookie sessionIdCookie = new SimpleCookie();
sessionIdCookie.setPath("/");

View File

@ -2,6 +2,7 @@ package io.metersphere.config;
import io.metersphere.commons.utils.ShiroUtils;
import io.metersphere.security.ApiKeyFilter;
import io.metersphere.security.CsrfFilter;
import io.metersphere.security.UserModularRealmAuthenticator;
import io.metersphere.security.realm.LdapRealm;
import io.metersphere.security.realm.ShiroDBRealm;
@ -44,10 +45,14 @@ public class ShiroConfig implements EnvironmentAware {
shiroFilterFactoryBean.setSuccessUrl("/");
shiroFilterFactoryBean.getFilters().put("apikey", new ApiKeyFilter());
shiroFilterFactoryBean.getFilters().put("csrf", new CsrfFilter());
Map<String, String> filterChainDefinitionMap = shiroFilterFactoryBean.getFilterChainDefinitionMap();
ShiroUtils.loadBaseFilterChain(filterChainDefinitionMap);
filterChainDefinitionMap.put("/**", "apikey, authc");
ShiroUtils.ignoreCsrfFilter(filterChainDefinitionMap);
filterChainDefinitionMap.put("/**", "apikey, csrf, authc");
return shiroFilterFactoryBean;
}

View File

@ -1,6 +1,5 @@
package io.metersphere.controller;
import io.metersphere.commons.constants.SsoMode;
import io.metersphere.commons.constants.UserSource;
import io.metersphere.commons.user.SessionUser;
import io.metersphere.commons.utils.SessionUtils;
@ -10,7 +9,6 @@ import io.metersphere.service.UserService;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.core.env.Environment;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@ -24,8 +22,6 @@ public class LoginController {
@Resource
private UserService userService;
@Resource
private Environment env;
@Resource
private BaseDisplayService baseDisplayService;
@GetMapping(value = "/isLogin")
@ -37,10 +33,6 @@ public class LoginController {
}
return ResultHolder.success(user);
}
String ssoMode = env.getProperty("sso.mode");
if (ssoMode != null && StringUtils.equalsIgnoreCase(SsoMode.CAS.name(), ssoMode)) {
return ResultHolder.error("sso");
}
return ResultHolder.error("");
}

View File

@ -1,11 +1,8 @@
package io.metersphere.controller;
import com.alibaba.fastjson.JSONObject;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import io.metersphere.base.domain.Organization;
import io.metersphere.base.domain.User;
import io.metersphere.base.domain.Workspace;
import io.metersphere.commons.constants.RoleConstants;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.user.SessionUser;
@ -29,7 +26,6 @@ import io.metersphere.service.WorkspaceService;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authz.annotation.Logical;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.checkerframework.checker.units.qual.C;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

View File

@ -14,8 +14,7 @@ public class EngineContext {
private String reportId;
private Integer resourceIndex;
private Map<String, Object> properties = new HashMap<>();
private Map<String, String> testData = new HashMap<>();
private Map<String, byte[]> testJars = new HashMap<>();
private Map<String, byte[]> testResourceFiles = new HashMap<>();
public String getTestId() {
return testId;
@ -69,14 +68,6 @@ public class EngineContext {
this.fileType = fileType;
}
public Map<String, String> getTestData() {
return testData;
}
public void setTestData(Map<String, String> testData) {
this.testData = testData;
}
public String getResourcePoolId() {
return resourcePoolId;
}
@ -111,11 +102,11 @@ public class EngineContext {
}
public Map<String, byte[]> getTestJars() {
return testJars;
public Map<String, byte[]> getTestResourceFiles() {
return testResourceFiles;
}
public void setTestJars(Map<String, byte[]> testJars) {
this.testJars = testJars;
public void setTestResourceFiles(Map<String, byte[]> testResourceFiles) {
this.testResourceFiles = testResourceFiles;
}
}

View File

@ -19,7 +19,8 @@ import io.metersphere.service.FileService;
import io.metersphere.service.KubernetesTestEngine;
import io.metersphere.service.TestResourcePoolService;
import org.apache.commons.beanutils.ConstructorUtils;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.ListUtils;
import org.apache.commons.lang3.StringUtils;
import org.reflections8.Reflections;
import org.springframework.stereotype.Service;
@ -92,8 +93,7 @@ public class EngineFactory {
}
List<FileMetadata> jmxFiles = fileMetadataList.stream().filter(f -> StringUtils.equalsIgnoreCase(f.getType(), FileType.JMX.name())).collect(Collectors.toList());
List<FileMetadata> csvFiles = fileMetadataList.stream().filter(f -> StringUtils.equalsIgnoreCase(f.getType(), FileType.CSV.name())).collect(Collectors.toList());
List<FileMetadata> jarFiles = fileMetadataList.stream().filter(f -> StringUtils.equalsIgnoreCase(f.getType(), FileType.JAR.name())).collect(Collectors.toList());
List<FileMetadata> resourceFiles = ListUtils.subtract(fileMetadataList, jmxFiles);
// 合并上传的jmx
byte[] jmxBytes = mergeJmx(jmxFiles);
final EngineContext engineContext = new EngineContext();
@ -156,22 +156,13 @@ public class EngineFactory {
MSException.throwException(e);
}
if (CollectionUtils.isNotEmpty(csvFiles)) {
Map<String, String> data = new HashMap<>();
csvFiles.forEach(cf -> {
FileContent csvContent = fileService.getFileContent(cf.getId());
data.put(cf.getName(), new String(csvContent.getFile()));
});
engineContext.setTestData(data);
}
if (CollectionUtils.isNotEmpty(jarFiles)) {
if (CollectionUtils.isNotEmpty(resourceFiles)) {
Map<String, byte[]> data = new HashMap<>();
jarFiles.forEach(jf -> {
FileContent content = fileService.getFileContent(jf.getId());
data.put(jf.getName(), content.getFile());
resourceFiles.forEach(cf -> {
FileContent csvContent = fileService.getFileContent(cf.getId());
data.put(cf.getName(), csvContent.getFile());
});
engineContext.setTestJars(data);
engineContext.setTestResourceFiles(data);
}
return engineContext;

View File

@ -51,17 +51,9 @@ public class JmeterFileService {
// 每个测试生成一个文件夹
files.put(fileName, context.getContent().getBytes(StandardCharsets.UTF_8));
// 保存测试数据文件
Map<String, String> testData = context.getTestData();
if (!CollectionUtils.isEmpty(testData)) {
for (String k : testData.keySet()) {
String v = testData.get(k);
files.put(k, v.getBytes(StandardCharsets.UTF_8));
}
}
// 保存 byte[] jar
Map<String, byte[]> jarFiles = context.getTestJars();
// 保存 byte[]
Map<String, byte[]> jarFiles = context.getTestResourceFiles();
if (!CollectionUtils.isEmpty(jarFiles)) {
for (String k : jarFiles.keySet()) {
byte[] v = jarFiles.get(k);

View File

@ -0,0 +1,93 @@
package io.metersphere.security;
import io.metersphere.commons.user.SessionUser;
import io.metersphere.commons.utils.CodingUtil;
import io.metersphere.commons.utils.CommonBeanFactory;
import io.metersphere.commons.utils.SessionUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.web.filter.authc.AnonymousFilter;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.core.env.Environment;
import org.springframework.http.HttpHeaders;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class CsrfFilter extends AnonymousFilter {
private static final String TOKEN_NAME = "CSRF-TOKEN";
@Override
protected boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) {
HttpServletRequest httpServletRequest = WebUtils.toHttp(request);
if (!SecurityUtils.getSubject().isAuthenticated()) {
((HttpServletResponse) response).setHeader("Authentication-Status", "invalid");
return true;
}
// api 过来的请求
if (ApiKeyHandler.isApiKeyCall(WebUtils.toHttp(request))) {
return true;
}
// websocket 不需要csrf
String websocketKey = httpServletRequest.getHeader("Sec-WebSocket-Key");
if (StringUtils.isNotBlank(websocketKey)) {
return true;
}
// 请求头取出的token value
String csrfToken = httpServletRequest.getHeader(TOKEN_NAME);
// 校验 token
validateToken(csrfToken);
// 校验 referer
validateReferer(httpServletRequest);
return true;
}
private void validateReferer(HttpServletRequest request) {
Environment env = CommonBeanFactory.getBean(Environment.class);
String domains = env.getProperty("referer.urls");
if (StringUtils.isBlank(domains)) {
// 没有配置不校验
return;
}
String[] split = StringUtils.split(domains, ",");
String referer = request.getHeader(HttpHeaders.REFERER);
if (split != null) {
if (!ArrayUtils.contains(split, referer)) {
throw new RuntimeException("csrf error");
}
}
}
private void validateToken(String csrfToken) {
if (StringUtils.isBlank(csrfToken)) {
throw new RuntimeException("csrf token is empty");
}
csrfToken = CodingUtil.aesDecrypt(csrfToken, SessionUser.secret, SessionUser.iv);
String[] signatureArray = StringUtils.split(StringUtils.trimToNull(csrfToken), "|");
if (signatureArray.length != 3) {
throw new RuntimeException("invalid token");
}
long signatureTime;
try {
signatureTime = Long.parseLong(signatureArray[2]);
} catch (Exception e) {
throw new RuntimeException(e);
}
Environment env = CommonBeanFactory.getBean(Environment.class);
long timeout = env.getProperty("session.timeout", Long.class, 43200L);
if (Math.abs(System.currentTimeMillis() - signatureTime) > timeout * 1000) {
throw new RuntimeException("expired token");
}
if (!StringUtils.equals(SessionUtils.getUserId(), signatureArray[0])) {
throw new RuntimeException("Please check csrf token.");
}
}
}

View File

@ -136,8 +136,8 @@ public class XmindCaseParser {
data.setNodePath(nodePath);
if (data.getName().length() > 50) {
process.add(Translator.get("test_case") + Translator.get("test_track.length_less_than") + "50", nodePath + data.getName());
if (data.getName().length() > 200) {
process.add(Translator.get("test_case") + Translator.get("test_track.length_less_than") + "200", nodePath + data.getName());
}
if (!StringUtils.isEmpty(nodePath)) {

View File

@ -0,0 +1,252 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.apache.jmeter.assertions;
import java.io.Serializable;
import java.text.DecimalFormat;
import java.util.Map;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.jayway.jsonpath.JsonPath;
import org.apache.commons.lang3.StringUtils;
import org.apache.jmeter.samplers.SampleResult;
import org.apache.jmeter.testelement.AbstractTestElement;
import org.apache.jmeter.testelement.ThreadListener;
import org.apache.jmeter.util.JMeterUtils;
import org.apache.oro.text.regex.Pattern;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This is main class for JSONPath Assertion which verifies assertion on
* previous sample result using JSON path expression
*
* @since 4.0
*/
public class JSONPathAssertion extends AbstractTestElement implements Serializable, Assertion, ThreadListener {
private static final Logger log = LoggerFactory.getLogger(JSONPathAssertion.class);
private static final long serialVersionUID = 2L;
public static final String JSONPATH = "JSON_PATH";
public static final String EXPECTEDVALUE = "EXPECTED_VALUE";
public static final String JSONVALIDATION = "JSONVALIDATION";
public static final String EXPECT_NULL = "EXPECT_NULL";
public static final String INVERT = "INVERT";
public static final String ISREGEX = "ISREGEX";
private static ThreadLocal<DecimalFormat> decimalFormatter =
ThreadLocal.withInitial(JSONPathAssertion::createDecimalFormat);
public String getOption() {
return getPropertyAsString("ASS_OPTION");
}
private static DecimalFormat createDecimalFormat() {
DecimalFormat decimalFormatter = new DecimalFormat("#.#");
decimalFormatter.setMaximumFractionDigits(340); // java.text.DecimalFormat.DOUBLE_FRACTION_DIGITS == 340
decimalFormatter.setMinimumFractionDigits(1);
return decimalFormatter;
}
public String getJsonPath() {
return getPropertyAsString(JSONPATH);
}
public void setJsonPath(String jsonPath) {
setProperty(JSONPATH, jsonPath);
}
public String getExpectedValue() {
return getPropertyAsString(EXPECTEDVALUE);
}
public void setExpectedValue(String expectedValue) {
setProperty(EXPECTEDVALUE, expectedValue);
}
public void setJsonValidationBool(boolean jsonValidation) {
setProperty(JSONVALIDATION, jsonValidation);
}
public void setExpectNull(boolean val) {
setProperty(EXPECT_NULL, val);
}
public boolean isExpectNull() {
return getPropertyAsBoolean(EXPECT_NULL);
}
public boolean isJsonValidationBool() {
return getPropertyAsBoolean(JSONVALIDATION);
}
public void setInvert(boolean invert) {
setProperty(INVERT, invert);
}
public boolean isInvert() {
return getPropertyAsBoolean(INVERT);
}
public void setIsRegex(boolean flag) {
setProperty(ISREGEX, flag);
}
public boolean isUseRegex() {
return getPropertyAsBoolean(ISREGEX, true);
}
private void doAssert(String jsonString) {
Object value = JsonPath.read(jsonString, getJsonPath());
if (!isJsonValidationBool()) {
return;
}
if (value instanceof JSONArray) {
if (arrayMatched((JSONArray) value)) {
return;
}
} else {
if ((isExpectNull() && value == null)
|| isEquals(value)) {
return;
}
}
if (isExpectNull()) {
throw new IllegalStateException(String.format("Value expected to be null, but found '%s'", value));
} else {
String msg;
if (isUseRegex()) {
msg = "Value expected to match regexp '%s', but it did not match: '%s'";
} else {
msg = "Value expected to be '%s', but found '%s'";
}
throw new IllegalStateException(String.format(msg, getExpectedValue(), objectToString(value)));
}
}
private boolean arrayMatched(JSONArray value) {
if (value.isEmpty() && "[]".equals(getExpectedValue())) {
return true;
}
for (Object subj : value.toArray()) {
if ((subj == null && isExpectNull())
|| isEquals(subj)) {
return true;
}
}
return isEquals(value);
}
private boolean isEquals(Object subj) {
String str = objectToString(subj);
if (isUseRegex()) {
Pattern pattern = JMeterUtils.getPatternCache().getPattern(getExpectedValue());
return JMeterUtils.getMatcher().matches(str, pattern);
} else {
if (StringUtils.isNotEmpty(getOption())) {
boolean refFlag = false;
switch (getOption()) {
case "CONTAINS":
refFlag = str.contains(getExpectedValue());
break;
case "NOT_CONTAINS":
refFlag = !str.contains(getExpectedValue());
break;
case "EQUALS":
refFlag = str.equals(getExpectedValue());
break;
case "NOT_EQUALS":
refFlag = !str.contains(getExpectedValue());
break;
}
return refFlag;
}
return str.equals(getExpectedValue());
}
}
@Override
public AssertionResult getResult(SampleResult samplerResult) {
AssertionResult result = new AssertionResult(getName());
String responseData = samplerResult.getResponseDataAsString();
if (responseData.isEmpty()) {
return result.setResultForNull();
}
result.setFailure(false);
result.setFailureMessage("");
if (!isInvert()) {
try {
doAssert(responseData);
} catch (Exception e) {
log.debug("Assertion failed", e);
result.setFailure(true);
result.setFailureMessage(e.getMessage());
}
} else {
try {
doAssert(responseData);
result.setFailure(true);
if (isJsonValidationBool()) {
if (isExpectNull()) {
result.setFailureMessage("Failed that JSONPath " + getJsonPath() + " not matches null");
} else {
result.setFailureMessage("Failed that JSONPath " + getJsonPath() + " not matches " + getExpectedValue());
}
} else {
result.setFailureMessage("Failed that JSONPath not exists: " + getJsonPath());
}
} catch (Exception e) {
log.debug("Assertion failed, as expected", e);
}
}
return result;
}
public static String objectToString(Object subj) {
String str;
if (subj == null) {
str = "null";
} else if (subj instanceof Map) {
//noinspection unchecked
str = new JSONObject((Map<String, Object>) subj).toJSONString();
} else if (subj instanceof Double || subj instanceof Float) {
str = decimalFormatter.get().format(subj);
} else {
str = subj.toString();
}
return str;
}
@Override
public void threadStarted() {
// nothing to do on thread start
}
@Override
public void threadFinished() {
decimalFormatter.remove();
}
}

File diff suppressed because it is too large Load Diff

View File

@ -45,3 +45,9 @@ alter table test_plan_api_scenario change environment_id environment longtext nu
-- file add sort column
alter table file_metadata add sort int default 0;
-- add Original state
alter table api_definition add original_state varchar(64);
alter table api_scenario add original_state varchar(64);
update api_definition set original_state='Underway';
update api_scenario set original_state='Underway';

View File

@ -17,7 +17,6 @@
"@fortawesome/vue-fontawesome": "^0.1.9",
"axios": "^0.21.1",
"core-js": "^3.4.3",
"default-passive-events": "^2.0.0",
"diffable-html": "^4.0.0",
"echarts": "^4.6.0",
"el-table-infinite-scroll": "^1.0.10",

View File

@ -30,6 +30,7 @@ import MsUser from "./components/common/head/HeaderUser";
import MsHeaderOrgWs from "./components/common/head/HeaderOrgWs";
import MsLanguageSwitch from "./components/common/head/LanguageSwitch";
import {saveLocalStorage} from "@/common/js/utils";
import {registerRequestHeaders} from "@/common/js/ajax";
const requireComponent = require.context('@/business/components/xpack/', true, /\.vue$/);
const header = requireComponent.keys().length > 0 ? requireComponent("./license/LicenseMessage.vue") : {};
@ -53,6 +54,7 @@ export default {
window.addEventListener("beforeunload", () => {
localStorage.setItem("store", JSON.stringify(this.$store.state))
})
registerRequestHeaders();
},
beforeCreate() {
this.$get("/isLogin").then(response => {

View File

@ -100,6 +100,19 @@
active() {
this.isActive = !this.isActive;
},
formatResult(res) {
let resMap = new Map;
if (res && res.scenarios) {
res.scenarios.forEach(item => {
if (item && item.requestResults) {
item.requestResults.forEach(req => {
resMap.set(req.id, req);
})
}
})
}
this.$emit('refresh', resMap);
},
getReport() {
this.init();
if (this.reportId) {
@ -113,7 +126,7 @@
if (!this.content) {
this.content = {scenarios: []};
}
this.$emit('refresh');
this.formatResult(this.content);
} catch (e) {
throw e;
}

View File

@ -145,7 +145,7 @@
<!--测试计划-->
<el-drawer :visible.sync="planVisible" :destroy-on-close="true" direction="ltr" :withHeader="false"
:title="$t('test_track.plan_view.test_result')" :modal="false" size="90%">
<ms-test-plan-list @addTestPlan="addTestPlan" @cancel="cancel"/>
<ms-test-plan-list @addTestPlan="addTestPlan(arguments)" @cancel="cancel" ref="testPlanList" :row="selectRows"/>
</el-drawer>
</div>
</el-card>
@ -276,7 +276,9 @@
},
{
name: this.$t('test_track.case.batch_move_case'), handleClick: this.handleBatchMove
}
},
{name: this.$t('api_test.definition.request.batch_delete'), handleClick: this.handleDeleteBatch},
],
isSelectAllDate: false,
selectRows: new Set(),
@ -318,7 +320,7 @@
],
principal: [],
environmentId: [],
projectEnv: []
projectEnv: [],
},
}
},
@ -496,17 +498,31 @@
cancel() {
this.planVisible = false;
},
addTestPlan(plans) {
let obj = {planIds: plans, scenarioIds: this.selection};
addTestPlan(params) {
let obj = {planIds: params[0], scenarioIds: this.selection};
obj.projectId = getCurrentProjectID();
obj.selectAllDate = this.isSelectAllDate;
obj.unSelectIds = this.unSelection;
obj = Object.assign(obj, this.condition);
// obj.projectId = getCurrentProjectID();
// obj.selectAllDate = this.isSelectAllDate;
// obj.unSelectIds = this.unSelection;
// obj = Object.assign(obj, this.condition);
// todo
if (this.isSelectAllDate) {
this.$warning("暂不支持批量添加所有场景到测试计划!");
}
this.planVisible = false;
let map = new Map();
this.selectRows.forEach(row => {
map.set(row.id, row.projectIds);
})
obj.mapping = strMapToObj(map);
obj.envMap = strMapToObj(params[1]);
this.$post("/api/automation/scenario/plan", obj, response => {
this.$success(this.$t("commons.save_success"));
this.search();
});
},
getReport() {
@ -582,6 +598,29 @@
this.search();
})
},
handleDeleteBatch(row) {
if (this.trashEnable) {
let ids = Array.from(this.selectRows).map(row => row.id);
this.$post('/api/automation/deleteBatch/', ids, () => {
this.$success(this.$t('commons.delete_success'));
this.search();
});
return;
}
this.$alert(this.$t('api_test.definition.request.delete_confirm') + " ", '', {
confirmButtonText: this.$t('commons.confirm'),
callback: (action) => {
if (action === 'confirm') {
let ids = Array.from(this.selectRows).map(row => row.id);
this.$post('/api/automation/removeToGc/', ids, () => {
this.$success(this.$t('commons.delete_success'));
this.search();
});
}
}
});
},
execute(row) {
this.infoDb = false;
let url = "/api/automation/run";

View File

@ -184,7 +184,7 @@
<!-- 调试结果 -->
<el-drawer v-if="type!=='detail'" :visible.sync="debugVisible" :destroy-on-close="true" direction="ltr"
:withHeader="true" :modal="false" size="90%">
<ms-api-report-detail :report-id="reportId" :debug="true" :currentProjectId="projectId"/>
<ms-api-report-detail :report-id="reportId" :debug="true" :currentProjectId="projectId" @refresh="detailRefresh"/>
</el-drawer>
<!--场景公共参数-->
@ -224,6 +224,7 @@
import MsComponentConfig from "./component/ComponentConfig";
import {handleCtrlSEvent} from "../../../../../common/js/utils";
import EnvPopover from "@/business/components/api/automation/scenario/EnvPopover";
let jsonPath = require('jsonpath');
export default {
name: "EditApiScenario",
@ -290,7 +291,8 @@
response: {},
projectIds: new Set,
projectEnvMap: new Map,
projectList: []
projectList: [],
debugResult: new Map,
}
},
created() {
@ -400,7 +402,7 @@
},
{
title: this.$t('api_test.automation.scenario_import'),
show:this.showButton("scenario"),
show: this.showButton("scenario"),
titleColor: "#606266",
titleBgColor: "#F4F4F5",
icon: "movie",
@ -547,21 +549,32 @@
if (arr[i].hashTree != undefined && arr[i].hashTree.length > 0) {
this.recursiveSorting(arr[i].hashTree);
}
// debug
if (this.debugResult && this.debugResult.get(arr[i].id)) {
arr[i].requestResult = this.debugResult.get(arr[i].id);
}
}
},
sort() {
for (let i in this.scenarioDefinition) {
//
this.scenarioDefinition[i].index = Number(i) + 1;
//
if (this.scenarioDefinition[i].type === ELEMENT_TYPE.LoopController && this.scenarioDefinition[i].hashTree
&& this.scenarioDefinition[i].hashTree.length > 1) {
this.scenarioDefinition[i].countController.proceed = true;
}
// ID
if (!this.scenarioDefinition[i].projectId) {
this.scenarioDefinition.projectId = getCurrentProjectID();
this.scenarioDefinition[i].projectId = getCurrentProjectID();
}
if (this.scenarioDefinition[i].hashTree != undefined && this.scenarioDefinition[i].hashTree.length > 0) {
this.recursiveSorting(this.scenarioDefinition[i].hashTree);
}
// debug
if (this.debugResult && this.debugResult.get(this.scenarioDefinition[i].id)) {
this.scenarioDefinition[i].requestResult = this.debugResult.get(this.scenarioDefinition[i].id);
}
}
},
addCustomizeApi(request) {
@ -575,6 +588,7 @@
this.customizeRequest = {};
this.sort();
this.reload();
this.initProjectIds();
},
addScenario(arr) {
if (arr && arr.length > 0) {
@ -677,7 +691,7 @@
}
const index = hashTree.findIndex(d => d.resourceId === row.resourceId);
if (index != -1) {
hashTree.splice(index+1, 0, obj);
hashTree.splice(index + 1, 0, obj);
} else {
hashTree.push(obj);
}
@ -1019,11 +1033,16 @@
//
this.$nextTick(() => {
this.projectIds.clear();
this.scenarioDefinition.forEach(data=>{
this.scenarioDefinition.forEach(data => {
let arr = jsonPath.query(data, "$..projectId");
arr.forEach(a => this.projectIds.add(a));
})
})
},
detailRefresh(result) {
//
this.debugResult = result;
this.sort()
}
}
}

View File

@ -112,7 +112,17 @@ export default {
}
})
} else {
sign = false;
//
if (this.envMap && this.envMap.size > 0) {
this.projectIds.forEach(id => {
if (!this.envMap.get(id)) {
sign = false;
return false;
}
})
} else {
sign = false;
}
}
if (!sign) {

View File

@ -6,8 +6,8 @@ export const ELEMENTS = new Map([
['JDBCSampler', ["ConstantTimer", "JSR223PreProcessor", "JSR223PostProcessor", "Assertions", "Extract"]],
['TCPSampler', ["ConstantTimer", "JSR223PreProcessor", "JSR223PostProcessor", "Assertions", "Extract"]],
['OT_IMPORT', ["ConstantTimer", "JSR223PreProcessor", "JSR223PostProcessor", "Assertions", "Extract"]],
['IfController', ["IfController","scenario", "HTTPSamplerProxy", "DubboSampler", "JDBCSampler", "TCPSampler", "OT_IMPORT", "ConstantTimer", "JSR223Processor", "JSR223PreProcessor", "JSR223PostProcessor", "Assertions", "Extract", "CustomizeReq"]],
['LoopController', ["IfController", "HTTPSamplerProxy", "DubboSampler", "JDBCSampler", "TCPSampler", "OT_IMPORT", "ConstantTimer", "JSR223Processor", "JSR223PreProcessor", "JSR223PostProcessor", "Assertions", "Extract", "CustomizeReq"]],
['IfController', ["IfController", "scenario", "HTTPSamplerProxy", "DubboSampler", "JDBCSampler", "TCPSampler", "OT_IMPORT", "ConstantTimer", "JSR223Processor", "JSR223PreProcessor", "JSR223PostProcessor", "Assertions", "Extract", "CustomizeReq"]],
['LoopController', ["IfController", "scenario", "HTTPSamplerProxy", "DubboSampler", "JDBCSampler", "TCPSampler", "OT_IMPORT", "ConstantTimer", "JSR223Processor", "JSR223PreProcessor", "JSR223PostProcessor", "Assertions", "Extract", "CustomizeReq"]],
['ConstantTimer', []],
['JSR223Processor', ["ConstantTimer", "JSR223PreProcessor", "JSR223PostProcessor", "Assertions", "Extract"]],
['JSR223PreProcessor', []],

View File

@ -3,9 +3,11 @@
<template v-slot:header>
<ms-table-header :is-tester-permission="true" :condition.sync="condition"
@search="initTableData" :showCreate="false"
:title="$t('test_track.plan.test_plan')"/>
:title="$t('test_track.plan.test_plan')">
</ms-table-header>
</template>
<env-popover :env-map="projectEnvMap" :project-ids="projectIds" @setProjectEnvMap="setProjectEnvMap"
:project-list="projectList" ref="envPopover" class="env-popover" style="float: right; margin-top: 4px;"/>
<el-table
border
class="adjust-table"
@ -142,7 +144,7 @@ import MsTableOperatorButton from "../../../../common/components/MsTableOperator
import MsTableOperator from "../../../../common/components/MsTableOperator";
import PlanStatusTableItem from "../../../../track/common/tableItems/plan/PlanStatusTableItem";
import PlanStageTableItem from "../../../../track/common/tableItems/plan/PlanStageTableItem";
import {checkoutTestManagerOrTestUser} from "@/common/js/utils";
import {checkoutTestManagerOrTestUser, strMapToObj} from "@/common/js/utils";
import TestReportTemplateList from "../../../../track/plan/view/comonents/TestReportTemplateList";
import TestCaseReportView from "../../../../track/plan/view/comonents/report/TestCaseReportView";
import MsDeleteConfirm from "../../../../common/components/MsDeleteConfirm";
@ -150,6 +152,7 @@ import {TEST_PLAN_CONFIGS} from "../../../../common/components/search/search-com
import {LIST_CHANGE, TrackEvent} from "@/business/components/common/head/ListEvent";
import {getCurrentProjectID} from "../../../../../../common/js/utils";
import {_filter, _sort} from "@/common/js/tableUtils";
import EnvPopover from "@/business/components/api/automation/scenario/EnvPopover";
export default {
@ -160,7 +163,10 @@ export default {
TestReportTemplateList,
PlanStageTableItem,
PlanStatusTableItem,
MsTableOperator, MsTableOperatorButton, MsDialogFooter, MsTableHeader, MsCreateBox, MsTablePagination
MsTableOperator, MsTableOperatorButton, MsDialogFooter, MsTableHeader, MsCreateBox, MsTablePagination, EnvPopover
},
props: {
row: Set
},
data() {
return {
@ -187,6 +193,9 @@ export default {
{text: this.$t('test_track.plan.system_test'), value: 'system'},
{text: this.$t('test_track.plan.regression_test'), value: 'regression'},
],
projectEnvMap: new Map(),
projectList: [],
projectIds: new Set()
}
},
watch: {
@ -200,22 +209,37 @@ export default {
this.projectId = this.$route.params.projectId;
this.isTestManagerOrTestUser = checkoutTestManagerOrTestUser();
this.initTableData();
this.setScenarioSelectRows(this.row);
this.getWsProjects();
},
methods: {
confirm() {
if (this.selection.length==0) {
this.$warning(this.$t("api_test.definition.request.test_plan_select"));
}else{
this.$emit('addTestPlan', this.selection);
const sign = this.checkEnv();
if (!sign) {
return false;
}
this.$emit('addTestPlan', this.selection, this.projectEnvMap);
}
},
cancel(){
this.$emit('cancel');
},
setProjectEnvMap(projectEnvMap) {
this.projectEnvMap = projectEnvMap;
},
select(selection) {
this.selection = selection.map(s => s.id);
this.$emit('selection', selection);
},
setScenarioSelectRows(rows) {
this.projectIds.clear();
rows.forEach(row => {
row.projectIds.forEach(id => this.projectIds.add(id));
})
},
initTableData() {
if (this.planId) {
this.condition.planId = this.planId;
@ -284,6 +308,14 @@ export default {
this.$refs.testCaseReportView.open(planId, reportId);
}
},
checkEnv() {
return this.$refs.envPopover.checkEnv();
},
getWsProjects() {
this.$get("/project/listAll", res => {
this.projectList = res.data;
})
},
}
}
</script>

View File

@ -1,13 +1,24 @@
<template>
<div>
<div v-loading="loading">
<el-row :gutter="10" type="flex" justify="space-between" align="middle">
<el-col>
<el-input :disabled="isReadOnly" v-model="jsonPath.expression" maxlength="200" size="small" show-word-limit
:placeholder="$t('api_test.request.extract.json_path_expression')"/>
</el-col>
<el-col>
<el-select v-model="jsonPath.option" class="ms-col-type" size="small" style="width:40%;margin-right: 10px" @change="reload">
<el-option :label="$t('api_test.request.assertions.contains')" value="CONTAINS"/>
<el-option :label="$t('api_test.request.assertions.not_contains')" value="NOT_CONTAINS"/>
<el-option :label="$t('api_test.request.assertions.equals')" value="EQUALS"/>
<el-option :label="$t('commons.adv_search.operators.not_equals')" value="NOT_EQUALS"/>
<el-option label="正则匹配" value="REGEX"/>
</el-select>
<el-input :disabled="isReadOnly" v-model="jsonPath.expect" size="small" show-word-limit
:placeholder="$t('api_test.request.assertions.expect')"/>
:placeholder="$t('api_test.request.assertions.expect')" style="width: 50%"/>
<el-tooltip placement="top" v-if="jsonPath.option === 'REGEX'">
<div slot="content">特殊字符"$ ( ) * + . [ ] \ ^ { } |"需转义为"\ "+"特殊字符","\$"</div>
<i class="el-icon-question" style="cursor: pointer"/>
</el-tooltip>
</el-col>
<el-col class="assertion-btn">
<el-button :disabled="isReadOnly" type="danger" size="mini" icon="el-icon-delete" circle @click="remove" v-if="edit"/>
@ -44,8 +55,15 @@
}
},
created() {
if (!this.jsonPath.option) {
this.jsonPath.option = "REGEX";
}
},
data() {
return {
loading: false
}
},
@ -71,6 +89,12 @@
jsonPath.description = jsonPath.expression + " expect: " + (jsonPath.expect ? jsonPath.expect : '');
return jsonPath;
},
reload() {
this.loading = true
this.$nextTick(() => {
this.loading = false
})
},
setJSONPathDescription() {
this.jsonPath.description = this.jsonPath.expression + " expect: " + (this.jsonPath.expect ? this.jsonPath.expect : '');
}

View File

@ -56,31 +56,31 @@
</template>
<script>
import MsApiAssertionText from "./ApiAssertionText";
import MsApiAssertionRegex from "./ApiAssertionRegex";
import MsApiAssertionDuration from "./ApiAssertionDuration";
import {ASSERTION_TYPE, JSONPath} from "../../model/ApiTestModel";
import MsApiAssertionsEdit from "./ApiAssertionsEdit";
import MsApiAssertionJsonPath from "./ApiAssertionJsonPath";
import MsApiAssertionJsr223 from "./ApiAssertionJsr223";
import MsApiJsonpathSuggestList from "./ApiJsonpathSuggestList";
import MsApiAssertionXPath2 from "./ApiAssertionXPath2";
import {getUUID} from "@/common/js/utils";
import ApiJsonPathSuggestButton from "./ApiJsonPathSuggestButton";
import MsApiJsonpathSuggest from "./ApiJsonpathSuggest";
import ApiBaseComponent from "../../../automation/scenario/common/ApiBaseComponent";
import MsApiAssertionText from "./ApiAssertionText";
import MsApiAssertionRegex from "./ApiAssertionRegex";
import MsApiAssertionDuration from "./ApiAssertionDuration";
import {ASSERTION_TYPE, JSONPath} from "../../model/ApiTestModel";
import MsApiAssertionsEdit from "./ApiAssertionsEdit";
import MsApiAssertionJsonPath from "./ApiAssertionJsonPath";
import MsApiAssertionJsr223 from "./ApiAssertionJsr223";
import MsApiJsonpathSuggestList from "./ApiJsonpathSuggestList";
import MsApiAssertionXPath2 from "./ApiAssertionXPath2";
import {getUUID} from "@/common/js/utils";
import ApiJsonPathSuggestButton from "./ApiJsonPathSuggestButton";
import MsApiJsonpathSuggest from "./ApiJsonpathSuggest";
import ApiBaseComponent from "../../../automation/scenario/common/ApiBaseComponent";
export default {
name: "MsApiAssertions",
components: {
ApiBaseComponent,
MsApiJsonpathSuggest,
ApiJsonPathSuggestButton,
MsApiAssertionXPath2,
MsApiAssertionJsr223,
MsApiJsonpathSuggestList,
MsApiAssertionJsonPath,
MsApiAssertionsEdit, MsApiAssertionDuration, MsApiAssertionRegex, MsApiAssertionText
export default {
name: "MsApiAssertions",
components: {
ApiBaseComponent,
MsApiJsonpathSuggest,
ApiJsonPathSuggestButton,
MsApiAssertionXPath2,
MsApiAssertionJsr223,
MsApiJsonpathSuggestList,
MsApiAssertionJsonPath,
MsApiAssertionsEdit, MsApiAssertionDuration, MsApiAssertionRegex, MsApiAssertionText
},
props: {
draggable: {
@ -146,6 +146,11 @@ export default {
jsonItem.expression = data.path;
jsonItem.expect = data.value;
jsonItem.setJSONPathDescription();
let expect = jsonItem.expect.replaceAll('\\', "\\\\").replaceAll('(', "\\(").replaceAll(')', "\\)")
.replaceAll('+', "\\+").replaceAll('.', "\\.").replaceAll('[', "\\[").replaceAll(']', "\\]")
.replaceAll('?', "\\?").replaceAll('/', "\\/").replaceAll('*', "\\*")
.replaceAll('^', "\\^").replaceAll('{', "\\{").replaceAll('}', "\\}").replaceAll('$', "\\$");
jsonItem.expect = expect;
this.assertions.jsonPath.push(jsonItem);
},
clearJson() {

View File

@ -18,7 +18,7 @@
v-permission="['test_manager','test_user','test_viewer']">
{{ $t('commons.performance') }}
</el-menu-item>
<el-menu-item index="/reportForm" v-permission="['test_manager','test_user','test_viewer']" v-if="isReport">
<el-menu-item index="/report" v-permission="['test_manager','test_user','test_viewer']" v-if="isReport">
{{ $t('commons.report_statistics.title') }}
</el-menu-item>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -178,8 +178,11 @@ export default {
},
methods: {
calculateLoadConfiguration: function (data) {
for (let i = 0; i < data.length; i++) {
for (let i = 0; i < this.threadGroups.length; i++) {
let d = data[i];
if (!d) {
return;
}
d.forEach(item => {
switch (item.key) {
case TARGET_LEVEL:
@ -225,7 +228,7 @@ export default {
default:
break;
}
})
});
this.calculateChart(this.threadGroups[i]);
}

View File

@ -117,6 +117,36 @@
</el-form>
</el-col>
</el-row>
<el-row>
<el-col :span="8">
<el-form :inline="true">
<el-form-item>
<div>
{{ $t('load_test.granularity') }}
<el-popover
placement="bottom"
width="400"
trigger="hover">
<el-table :data="granularityData">
<el-table-column property="start" :label="$t('load_test.duration')">
<template v-slot:default="scope">
<span>{{ scope.row.start }} - {{ scope.row.end }}</span>
</template>
</el-table-column>
<el-table-column property="granularity" :label="$t('load_test.granularity')"/>
</el-table>
<i slot="reference" class="el-icon-info pointer"/>
</el-popover>
</div>
</el-form-item>
<el-form-item>
<el-select v-model="granularity" :placeholder="$t('commons.please_select')" size="mini" clearable>
<el-option v-for="op in granularityData" :key="op.granularity" :label="op.granularity" :value="op.granularity"></el-option>
</el-select>
</el-form-item>
</el-form>
</el-col>
</el-row>
</div>
</template>
@ -134,6 +164,18 @@ export default {
domains: [],
params: [],
statusCodeStr: '',
granularity: undefined,
granularityData: [
{start: 0, end: 100, granularity: 1},
{start: 101, end: 500, granularity: 5},
{start: 501, end: 1000, granularity: 10},
{start: 1001, end: 3000, granularity: 30},
{start: 3001, end: 6000, granularity: 60},
{start: 6001, end: 30000, granularity: 300},
{start: 30001, end: 60000, granularity: 600},
{start: 60001, end: 180000, granularity: 1800},
{start: 180001, end: 360000, granularity: 3600},
]
}
},
props: {
@ -166,6 +208,7 @@ export default {
this.statusCodeStr = this.statusCode.join(',');
this.domains = data.domains || [];
this.params = data.params || [];
this.granularity = data.granularity;
}
});
},
@ -252,6 +295,7 @@ export default {
statusCode: statusCode,
params: this.params,
domains: this.domains,
granularity: this.granularity,
};
},
}
@ -287,4 +331,8 @@ export default {
align: center;
}
.pointer {
cursor: pointer;
}
</style>

View File

@ -70,7 +70,7 @@
<el-row type="flex" justify="start" align="middle">
<el-upload
style="padding-right: 10px;"
accept=".jar,.csv"
accept=".jar,.csv,.json,.pdf,.jpg,.png,.jpeg,.doc,.docx,.xlsx"
action=""
:limit="fileNumLimit"
multiple
@ -170,7 +170,7 @@ export default {
fileList: [],
tableData: [],
uploadList: [],
metadataIdList:[],
metadataIdList: [],
fileNumLimit: 10,
threadGroups: [],
loadFileVisible: false,
@ -276,7 +276,10 @@ export default {
let self = this;
let file = uploadResources.file;
self.uploadList.push(file);
let type = file.name.substring(file.name.lastIndexOf(".") + 1);
if (type.toLowerCase() !== 'jmx') {
return;
}
let jmxReader = new FileReader();
jmxReader.onload = (event) => {
self.threadGroups = self.threadGroups.concat(findThreadGroup(event.target.result, file.name));

View File

@ -22,7 +22,6 @@ import {left2RightDrag, bottom2TopDrag, right2LeftDrag} from "../common/js/direc
import JsonSchemaEditor from './components/common/json-schema/schema/index';
import JSONPathPicker from 'vue-jsonpath-picker';
import VueClipboard from 'vue-clipboard2'
Vue.use(JsonSchemaEditor);
import VuePapaParse from 'vue-papa-parse'
Vue.use(VuePapaParse)

View File

@ -1,13 +1,23 @@
import {Message, MessageBox} from 'element-ui';
import axios from "axios";
import i18n from '../../i18n/i18n'
import {TokenKey} from "@/common/js/constants";
export function registerRequestHeaders() {
axios.interceptors.request.use(config => {
let user = JSON.parse(localStorage.getItem(TokenKey));
if (user && user.csrfToken) {
config.headers['CSRF-TOKEN'] = user.csrfToken;
}
return config;
});
}
export default {
install(Vue) {
// 登入请求不重定向
let unRedirectUrls = new Set(['signin','ldap/signin','/signin', '/ldap/signin']);
let unRedirectUrls = new Set(['signin', 'ldap/signin', '/signin', '/ldap/signin']);
if (!axios) {
window.console.error('You have to install axios');

View File

@ -478,7 +478,8 @@ export default {
delete_file: "The file already exists, please delete the file with the same name first!",
thread_num: 'Concurrent users:',
input_thread_num: 'Please enter the number of threads',
duration: 'Duration time (seconds):',
duration: 'Duration time (seconds)',
granularity: 'Aggregation time (seconds)',
input_duration: 'Please enter a duration',
rps_limit: 'RPS Limit:',
input_rps_limit: 'Please enter a limit',

View File

@ -475,7 +475,8 @@ export default {
delete_file: "文件已存在,请先删除同名文件!",
thread_num: '并发用户数:',
input_thread_num: '请输入线程数',
duration: '压测时长(秒):',
duration: '压测时长(秒)',
granularity: '聚合时间(秒)',
input_duration: '请输入时长',
rps_limit: 'RPS上限',
input_rps_limit: '请输入限制',

View File

@ -475,7 +475,8 @@ export default {
delete_file: "文件已存在,請先刪除同名文件!",
thread_num: '並發用戶數:',
input_thread_num: '請輸入線程數',
duration: '壓測時長(秒):',
duration: '壓測時長(秒)',
granularity: '聚合時間(秒)',
input_duration: '請輸入時長',
rps_limit: 'RPS上限',
input_rps_limit: '請輸入限制',