diff --git a/backend/pom.xml b/backend/pom.xml
index c54fc88b7c..7c5cb8a95b 100644
--- a/backend/pom.xml
+++ b/backend/pom.xml
@@ -139,7 +139,7 @@
org.springdoc
springdoc-openapi-ui
- 1.2.32
+ 1.5.6
@@ -280,7 +280,7 @@
io.swagger.parser.v3
swagger-parser
- 2.0.22
+ 2.0.24
diff --git a/backend/src/main/java/io/metersphere/api/controller/APITestController.java b/backend/src/main/java/io/metersphere/api/controller/APITestController.java
index d43ea2acb9..185abbd176 100644
--- a/backend/src/main/java/io/metersphere/api/controller/APITestController.java
+++ b/backend/src/main/java/io/metersphere/api/controller/APITestController.java
@@ -12,8 +12,7 @@ import io.metersphere.api.dto.datacount.response.TaskInfoResult;
import io.metersphere.api.dto.definition.RunDefinitionRequest;
import io.metersphere.api.dto.scenario.request.dubbo.RegistryCenter;
import io.metersphere.api.service.*;
-import io.metersphere.base.domain.ApiTest;
-import io.metersphere.base.domain.Schedule;
+import io.metersphere.base.domain.*;
import io.metersphere.commons.constants.RoleConstants;
import io.metersphere.commons.constants.ScheduleGroup;
import io.metersphere.commons.utils.CronUtils;
@@ -30,13 +29,15 @@ import org.apache.commons.lang3.StringUtils;
import org.apache.jorphan.collections.HashTree;
import org.apache.shiro.authz.annotation.Logical;
import org.apache.shiro.authz.annotation.RequiresRoles;
-import org.python.core.AstList;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.text.DecimalFormat;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
import static io.metersphere.commons.utils.JsonPathUtils.getListJson;
@@ -251,9 +252,7 @@ public class APITestController {
@GetMapping("/testSceneInfoCount/{projectId}")
public ApiDataCountDTO testSceneInfoCount(@PathVariable String projectId) {
-
ApiDataCountDTO apiCountResult = new ApiDataCountDTO();
-
long scenarioCountNumber = apiAutomationService.countScenarioByProjectID(projectId);
apiCountResult.setAllApiDataCountNumber(scenarioCountNumber);
@@ -272,17 +271,46 @@ public class APITestController {
//未执行、未通过、已通过
List countResultByRunResult = apiAutomationService.countRunResultByProjectID(projectId);
apiCountResult.countRunResult(countResultByRunResult);
-
long allCount = apiCountResult.getUnexecuteCount() + apiCountResult.getExecutionPassCount() + apiCountResult.getExecutionFailedCount();
-
+ DecimalFormat df = new DecimalFormat("0.0");
if (allCount != 0) {
+ //通过率
float coverageRageNumber = (float) apiCountResult.getExecutionPassCount() * 100 / allCount;
- DecimalFormat df = new DecimalFormat("0.0");
apiCountResult.setPassRage(df.format(coverageRageNumber) + "%");
}
-
return apiCountResult;
+ }
+ @GetMapping("/countInterfaceCoverage/{projectId}")
+ public String countInterfaceCoverage(@PathVariable String projectId) {
+ String returnStr = "100%";
+ /**
+ * 接口覆盖率
+ * 复制的接口定义/复制或引用的单接口用例/ 添加的自定义请求 url 路径与现有的接口定义一致的请求
+ */
+ long startTime1 = System.currentTimeMillis();
+ List allScenarioInfoList = apiAutomationService.selectIdAndScenarioByProjectId(projectId);
+ long startTime2 = System.currentTimeMillis();
+ System.out.println("Search data time : " + (startTime2 - startTime1));
+ startTime1 = System.currentTimeMillis();
+ List allEffectiveApiIdList = apiDefinitionService.selectEffectiveIdByProjectId(projectId);
+ startTime2 = System.currentTimeMillis();
+ System.out.println("Search data time (api info) : " + (startTime2 - startTime1));
+ List allEffectiveApiCaseList = apiTestCaseService.selectEffectiveTestCaseByProjectId(projectId);
+ long startTime3 = System.currentTimeMillis();
+ System.out.println("Search data time (case info): " + (startTime3 - startTime2));
+
+ try {
+ startTime1 = System.currentTimeMillis();
+ float intetfaceCoverageRageNumber = apiAutomationService.countInterfaceCoverage(allScenarioInfoList, allEffectiveApiIdList, allEffectiveApiCaseList);
+ startTime2 = System.currentTimeMillis();
+ System.out.println("Count data time : " + (startTime2 - startTime1));
+ DecimalFormat df = new DecimalFormat("0.0");
+ returnStr = df.format(intetfaceCoverageRageNumber) + "%";
+ }catch (Exception e){
+ e.printStackTrace();
+ }
+ return returnStr;
}
@GetMapping("/scheduleTaskInfoCount/{projectId}")
@@ -346,11 +374,11 @@ public class APITestController {
@GetMapping("/runningTask/{projectID}/{callFrom}")
public List runningTask(@PathVariable String projectID, @PathVariable String callFrom) {
List typeFilter = new ArrayList<>();
- if(StringUtils.equals(callFrom, "api_test")) { // 接口测试首页显示的运行中定时任务,只要这3种,不需要 性能测试、api_test(旧版)
+ if (StringUtils.equals(callFrom, "api_test")) { // 接口测试首页显示的运行中定时任务,只要这3种,不需要 性能测试、api_test(旧版)
typeFilter.add(ScheduleGroup.API_SCENARIO_TEST.name());
typeFilter.add(ScheduleGroup.SWAGGER_IMPORT.name());
typeFilter.add(ScheduleGroup.TEST_PLAN_TEST.name());
- } else if(StringUtils.equals(callFrom, "track_home")) { // 测试跟踪首页只显示测试计划的定时任务
+ } else if (StringUtils.equals(callFrom, "track_home")) { // 测试跟踪首页只显示测试计划的定时任务
typeFilter.add(ScheduleGroup.TEST_PLAN_TEST.name());
}
List resultList = scheduleService.findRunningTaskInfoByProjectID(projectID, typeFilter);
@@ -386,7 +414,7 @@ public class APITestController {
String testName = runRequest.getName();
//将jmx处理封装为通用方法
- JmxInfoDTO dto = apiTestService.updateJmxString(jmxString,testName,false);
+ JmxInfoDTO dto = apiTestService.updateJmxString(jmxString, testName, false);
dto.setName(runRequest.getName() + ".jmx");
return dto;
}
diff --git a/backend/src/main/java/io/metersphere/api/dto/datacount/response/ApiDataCountDTO.java b/backend/src/main/java/io/metersphere/api/dto/datacount/response/ApiDataCountDTO.java
index 1ed49369c7..088cb896f5 100644
--- a/backend/src/main/java/io/metersphere/api/dto/datacount/response/ApiDataCountDTO.java
+++ b/backend/src/main/java/io/metersphere/api/dto/datacount/response/ApiDataCountDTO.java
@@ -111,6 +111,11 @@ public class ApiDataCountDTO {
*/
private String successRage = " 0%";
+ /**
+ * 接口覆盖率
+ */
+ private String interfaceCoverage = " 0%";
+
public ApiDataCountDTO(){}
/**
diff --git a/backend/src/main/java/io/metersphere/api/service/ApiAutomationService.java b/backend/src/main/java/io/metersphere/api/service/ApiAutomationService.java
index 33626513da..c4c71db6f1 100644
--- a/backend/src/main/java/io/metersphere/api/service/ApiAutomationService.java
+++ b/backend/src/main/java/io/metersphere/api/service/ApiAutomationService.java
@@ -1,6 +1,7 @@
package io.metersphere.api.service;
import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.JSONPath;
import com.fasterxml.jackson.core.type.TypeReference;
@@ -29,6 +30,7 @@ import io.metersphere.commons.utils.*;
import io.metersphere.controller.request.ScheduleRequest;
import io.metersphere.i18n.Translator;
import io.metersphere.job.sechedule.ApiScenarioTestJob;
+import io.metersphere.job.sechedule.SwaggerUrlImportJob;
import io.metersphere.job.sechedule.TestPlanTestJob;
import io.metersphere.service.ScheduleService;
import io.metersphere.track.dto.TestPlanDTO;
@@ -230,6 +232,7 @@ public class ApiAutomationService {
scenario.setPrincipal(request.getPrincipal());
scenario.setStepTotal(request.getStepTotal());
scenario.setUpdateTime(System.currentTimeMillis());
+ scenario.setDescription(request.getDescription());
scenario.setScenarioDefinition(JSON.toJSONString(request.getScenarioDefinition()));
if (StringUtils.isNotEmpty(request.getStatus())) {
scenario.setStatus(request.getStatus());
@@ -710,6 +713,10 @@ public class ApiAutomationService {
return extApiScenarioMapper.countByProjectID(projectId);
}
+ public List selectIdAndScenarioByProjectId(String projectId) {
+ return extApiScenarioMapper.selectIdAndScenarioByProjectId(projectId);
+ }
+
public long countScenarioByProjectIDAndCreatInThisWeek(String projectId) {
Map startAndEndDateInWeek = DateUtils.getWeedFirstTimeAndLastTime(new Date());
Date firstTime = startAndEndDateInWeek.get("firstTime");
@@ -811,7 +818,9 @@ public class ApiAutomationService {
if (StringUtils.equals(request.getGroup(), ScheduleGroup.TEST_PLAN_TEST.name())) {
scheduleService.addOrUpdateCronJob(
request, TestPlanTestJob.getJobKey(request.getResourceId()), TestPlanTestJob.getTriggerKey(request.getResourceId()), TestPlanTestJob.class);
- } else {
+ }else if(StringUtils.equals(request.getGroup(), ScheduleGroup.SWAGGER_IMPORT.name())){
+ scheduleService.addOrUpdateCronJob(request, SwaggerUrlImportJob.getJobKey(request.getResourceId()), SwaggerUrlImportJob.getTriggerKey(request.getResourceId()), SwaggerUrlImportJob.class);
+ } else{
scheduleService.addOrUpdateCronJob(
request, ApiScenarioTestJob.getJobKey(request.getResourceId()), ApiScenarioTestJob.getTriggerKey(request.getResourceId()), ApiScenarioTestJob.class);
}
@@ -1031,4 +1040,131 @@ public class ApiAutomationService {
this.deleteBatch(request.getIds());
}
+
+ /**
+ * 统计接口覆盖率
+ * 1.场景中复制的接口
+ * 2.场景中引用/复制的案例
+ * 3.场景中的自定义路径与接口定义中的匹配
+ *
+ * @param allScenarioInfoList 场景集合(id / scenario大字段 必须有数据)
+ * @param allEffectiveApiList 接口集合(id / path 必须有数据)
+ * @param allEffectiveApiCaseList 案例集合(id /api_definition_id 必须有数据)
+ * @return
+ */
+ public float countInterfaceCoverage(List allScenarioInfoList, List allEffectiveApiList, List allEffectiveApiCaseList) {
+// float coverageRageNumber = (float) apiCountResult.getExecutionPassCount() * 100 / allCount;
+ float intetfaceCoverage = 0;
+ if (allEffectiveApiList == null || allEffectiveApiList.isEmpty()) {
+ return 100;
+ }
+
+ /**
+ * 前置工作:
+ * 1。将接口集合转化数据结构: map> urlMap 用来做3的筛选
+ * 2。将案例集合转化数据结构:map> caseIdMap 用来做2的筛选
+ * 3。将接口集合转化数据结构: List allApiIdList 用来做1的筛选
+ * 4。自定义List coveragedIdList 已覆盖的id集合。 最终计算公式是 coveragedIdList/allApiIdList
+ *
+ * 解析allScenarioList的scenarioDefinition字段。
+ * 1。提取每个步骤的url。 在 urlMap筛选
+ * 2。提取每个步骤的id. 在caseIdMap 和 allApiIdList。
+ */
+ Map> urlMap = new HashMap<>();
+ List allApiIdList = new ArrayList<>();
+ Map> caseIdMap = new HashMap<>();
+ for (ApiDefinition model : allEffectiveApiList) {
+ String url = model.getPath();
+ String id = model.getId();
+ allApiIdList.add(id);
+ if (urlMap.containsKey(url)) {
+ urlMap.get(url).add(id);
+ } else {
+ List list = new ArrayList<>();
+ list.add(id);
+ urlMap.put(url, list);
+ }
+ }
+ for (ApiTestCase model : allEffectiveApiCaseList){
+ String caseId = model.getId();
+ String apiId = model.getApiDefinitionId();
+ if (urlMap.containsKey(caseId)) {
+ urlMap.get(caseId).add(apiId);
+ } else {
+ List list = new ArrayList<>();
+ list.add(apiId);
+ urlMap.put(caseId, list);
+ }
+ }
+
+ if (allApiIdList.isEmpty()) {
+ return 100;
+ }
+
+ List urlList = new ArrayList<>();
+ List idList= new ArrayList<>();
+
+ for (ApiScenarioWithBLOBs model : allScenarioInfoList) {
+ String scenarioDefiniton = model.getScenarioDefinition();
+ this.addUrlAndIdToList(scenarioDefiniton,urlList,idList);
+ }
+
+ List containsApiIdList = new ArrayList<>();
+
+ for (String url : urlList) {
+ List apiIdList = urlMap.get(url);
+ if(apiIdList!=null ){
+ for (String api : apiIdList) {
+ if(!containsApiIdList.contains(api)){
+ containsApiIdList.add(api);
+ }
+ }
+ }
+ }
+
+ for (String id : idList) {
+ List apiIdList = caseIdMap.get(id);
+ if(apiIdList!=null ){
+ for (String api : apiIdList) {
+ if(!containsApiIdList.contains(api)){
+ containsApiIdList.add(api);
+ }
+ }
+ }
+
+ if(allApiIdList.contains(id)){
+ if(!containsApiIdList.contains(id)){
+ containsApiIdList.add(id);
+ }
+ }
+ }
+
+ float coverageRageNumber = (float) containsApiIdList.size() * 100 / allApiIdList.size();
+ return coverageRageNumber;
+ }
+
+ private void addUrlAndIdToList(String scenarioDefiniton, List urlList, List idList) {
+ try {
+ JSONObject scenarioObj = JSONObject.parseObject(scenarioDefiniton);
+ if(scenarioObj.containsKey("hashTree")){
+ JSONArray hashArr = scenarioObj.getJSONArray("hashTree");
+ for (int i = 0; i < hashArr.size(); i++) {
+ JSONObject elementObj = hashArr.getJSONObject(i);
+ if(elementObj.containsKey("id")){
+ String id = elementObj.getString("id");
+ idList.add(id);
+ }
+ if(elementObj.containsKey("url")){
+ String url = elementObj.getString("url");
+ urlList.add(url);
+ }
+ if(elementObj.containsKey("path")){
+ String path = elementObj.getString("path");
+ urlList.add(path);
+ }
+ }
+ }
+ }catch (Exception e){
+ }
+ }
}
diff --git a/backend/src/main/java/io/metersphere/api/service/ApiDefinitionService.java b/backend/src/main/java/io/metersphere/api/service/ApiDefinitionService.java
index 7884538fcc..eb3847aaf5 100644
--- a/backend/src/main/java/io/metersphere/api/service/ApiDefinitionService.java
+++ b/backend/src/main/java/io/metersphere/api/service/ApiDefinitionService.java
@@ -912,4 +912,8 @@ public class ApiDefinitionService {
}
return apiExportResult;
}
+
+ public List selectEffectiveIdByProjectId(String projectId) {
+ return extApiDefinitionMapper.selectEffectiveIdByProjectId(projectId);
+ }
}
diff --git a/backend/src/main/java/io/metersphere/api/service/ApiTestCaseService.java b/backend/src/main/java/io/metersphere/api/service/ApiTestCaseService.java
index aa7f0724a4..341c68747e 100644
--- a/backend/src/main/java/io/metersphere/api/service/ApiTestCaseService.java
+++ b/backend/src/main/java/io/metersphere/api/service/ApiTestCaseService.java
@@ -648,4 +648,8 @@ public class ApiTestCaseService {
public ApiDefinitionExecResult getInfo(String id){
return apiDefinitionExecResultMapper.selectByPrimaryKey(id);
}
+
+ public List selectEffectiveTestCaseByProjectId(String projectId) {
+ return extApiTestCaseMapper.selectEffectiveTestCaseByProjectId(projectId);
+ }
}
diff --git a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtApiDefinitionMapper.java b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtApiDefinitionMapper.java
index 44cf4c6abe..3230662b65 100644
--- a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtApiDefinitionMapper.java
+++ b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtApiDefinitionMapper.java
@@ -38,4 +38,6 @@ public interface ExtApiDefinitionMapper {
List listRelevance(@Param("request")ApiDefinitionRequest request);
List listRelevanceReview(@Param("request")ApiDefinitionRequest request);
List selectIds(@Param("request") BaseQueryRequest query);
+
+ List selectEffectiveIdByProjectId(String projectId);
}
\ No newline at end of file
diff --git a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtApiDefinitionMapper.xml b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtApiDefinitionMapper.xml
index bfaa0afcd0..7067ce9a49 100644
--- a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtApiDefinitionMapper.xml
+++ b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtApiDefinitionMapper.xml
@@ -465,6 +465,11 @@
+
diff --git a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtApiScenarioMapper.java b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtApiScenarioMapper.java
index 6bd9153db2..d7d397f6bb 100644
--- a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtApiScenarioMapper.java
+++ b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtApiScenarioMapper.java
@@ -6,7 +6,6 @@ import io.metersphere.api.dto.datacount.ApiDataCountResult;
import io.metersphere.base.domain.ApiScenario;
import io.metersphere.base.domain.ApiScenarioExample;
import io.metersphere.base.domain.ApiScenarioWithBLOBs;
-import io.metersphere.controller.request.BaseQueryRequest;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@@ -28,6 +27,8 @@ public interface ExtApiScenarioMapper {
long countByProjectID(String projectId);
+ List selectIdAndScenarioByProjectId(String projectId);
+
long countByProjectIDAndCreatInThisWeek(@Param("projectId") String projectId, @Param("firstDayTimestamp") long firstDayTimestamp, @Param("lastDayTimestamp") long lastDayTimestamp);
List countRunResultByProjectID(String projectId);
diff --git a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtApiScenarioMapper.xml b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtApiScenarioMapper.xml
index c7db6225fe..db83f076af 100644
--- a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtApiScenarioMapper.xml
+++ b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtApiScenarioMapper.xml
@@ -304,6 +304,9 @@
+