yapi-plugin-pl-auto-test
Version:
YAPI自动化测试插件,支持在YAPI设置测试计划,历史测试结果存入ES,界面显示测试结果,自定义通知。
423 lines (387 loc) • 15.2 kB
JavaScript
const openController = require('controllers/open.js');
const userModel = require('models/user.js');
const projectModel = require('models/project.js');
const groupModel = require('models/group.js');
const testResultModel = require('./../models/result');
const testPlanModel = require('./../models/plan');
const yapi = require('yapi.js');
const _ = require('underscore');
const renderToHtml = require('../../../server/utils/reportHtml');
const tools = require('../utils/tools');
const Config = require('../utils/config');
const elasticsearch = require('../elasticsearch');
const ops = Config.instance;
const logType = "pl-auto-test";
const URL = require('url');
class testResultController extends openController {
constructor(ctx) {
super(ctx);
this.testResultModel = yapi.getInst(testResultModel);
this.testPlanModel = yapi.getInst(testPlanModel);
this.projectModel = yapi.getInst(projectModel);
this.groupModel = yapi.getInst(groupModel);
this.userModel = yapi.getInst(userModel);
this.elasticsearch = yapi.getInst(elasticsearch);
this.indexName = `${ops.indexName}`;
this.esStorage = true;
const esHost = `${ops.esHost}`;
if (esHost === 'undefined' || esHost === "") {
this.esStorage = false;
}
this.schemaMap = {
runAutoTest: {
'*id': 'number',
extraIds: 'string',
project_id: 'string',
token: 'string',
mode: {
type: 'string',
default: 'json'
},
email: {
type: 'boolean',
default: false
},
download: {
type: 'boolean',
default: false
},
closeRemoveAdditional: true
}
};
}
/**
* 执行测试计划
* @param {*} ctx
*/
async runAutoTest (ctx) {
if (!this.$tokenAuth) {
return (ctx.body = yapi.commons.resReturn(null, 40022, 'token 验证失败'));
}
const planId = ctx.params.plan_id || -1;
const startTime = new Date().getTime();
let records = (this.records = {});
let reports = (this.reports = {});
const testList = [], testColNames = [];
let id = ctx.params.id;
let extraIds = ctx.params.extraIds ? ctx.params.extraIds.split(',') : [];
const curEnvList = this.handleEvnParams(ctx.params);
const colData = await this.interfaceColModel.get(id);
if (!colData) {
return (ctx.body = yapi.commons.resReturn(null, 40022, 'collection id值不存在'));
}
const originUrl = Config.instance.host || ctx.request.origin;
const planInfo = await this.testPlanModel.find(planId);
const projectId = planInfo.project_id;
const colLink = `${originUrl}/project/${projectId}/interface/col/${id}`;
const projectInfo = await this.projectModel.get(projectId);
extraIds.push(id);
//get user info added by liyue
const userId = this.getUid();
const userInfo = await this.userModel.findById(userId);
//get group info
const groupInfo = await this.groupModel.get(projectInfo.group_id);
// if (this.esStorage){
// //make es index avaliable
// const exits = await this.elasticsearch.indicesExists(this.indexName);
// if (!exits){
// yapi.commons.log(`will create index ${ops.indexName}`,logType);
// await this.elasticsearch.indexCreate(this.indexName)
// }
// }
for (let idIndex = 0; idIndex < extraIds.length; idIndex++) {
//add-by-liyue-start
const colStartTime = new Date().getTime();
//id means colId
id = extraIds[idIndex];
let caseList = await yapi.commons.getCaseList(id);
if (caseList.errcode !== 0) {
ctx.body = caseList;
}
const colDataInfo = caseList.colData || {};
testColNames.push(colDataInfo.name);
caseList = caseList.data;
let colTestList = new Array();
let colReportsList = new Array();
let stoped = false;
for (let i = 0, l = caseList.length; i < l; i++) {
let item = caseList[i];
const projectEvn = await this.projectModel.getByEnv(item.project_id);
item.id = item._id;
const curEnvItem = _.find(curEnvList, key => {
return key.project_id == item.project_id;
});
item.case_env = curEnvItem ? curEnvItem.curEnv || item.case_env : item.case_env;
item.req_headers = this.handleReqHeader(item.req_headers, projectEvn.env, item.case_env);
item.pre_script = projectInfo.pre_script;
item.after_script = projectInfo.after_script;
item.env = projectEvn.env;
let result = {
status: 0,
statusText: 'stoped',
id: item.id,
name: item.casename,
path: item.path,
code: 2,
};
try {
if (item.enable_script && item.enable_delay) {
const delayTime = item.delay_time;
await sleep(delayTime);
}
if (!stoped) {
result = await this.handleTest(item);
// for es storage
// result.res_body = JSON.stringify(result.res_body);
// result.data = JSON.stringify(result.data);
// result.params = JSON.stringify(result.params);
}
} catch (err) {
result = err;
}
reports[item.id] = result;
records[item.id] = {
params: result.params,
body: result.res_body
};
testList.push(result);
colTestList.push(result);
colReportsList.push(result);
if (!stoped && item.enable_script && result.code == 1 && item.enable_stop) { // 中断循环
stoped = true;
}
}
const colEndTime = new Date().getTime();
const message = getMessage(colTestList);
// col finish
const esData = {
source: `${ops.dataSource}`,
status: message.len === message.successNum ? 'success' : 'fail',
message: message,
numbs: colTestList.length,
group_id: projectInfo.group_id,
group_name: groupInfo.group_name,
project_id: colDataInfo.project_id,
project_name: projectInfo.name,
test_col_id: colDataInfo._id,
test_col_name: colDataInfo.name,
test_start: datetimeFormatLong2String(colStartTime),
test_end: datetimeFormatLong2String(colEndTime),
runTime: ((colEndTime - colStartTime) / 1000) + 's',
user_id: userInfo.username,
list: colReportsList
}
if (planId != -1 && !planInfo) {
esData.plan_id = planId;
esData.plan_name = planInfo.plan_name;
esData.plan_cron = planInfo.plan_cron;
esData.plan_url = planInfo.plan_url;
}
try{
if (this.esStorage) {
// const indexName = `${this.indexName}-${groupInfo.group_name}-${projectInfo.name}-v1`;
// const insert = await this.elasticsearch.insertData(indexName, esData);
// yapi.commons.log(`Project:${groupInfo.group_name}/${projectInfo.name},Plan:${planInfo.plan_name},Colletions:${colDataInfo.name},Cron:${planInfo.plan_cron}`, logType);
}
}catch (err){
yapi.commons.log(`Project:${groupInfo.group_name}/${projectInfo.name},Plan:${planInfo.plan_name},Colletions:${colDataInfo.name},Cron:${planInfo.plan_cron}`, logType);
}
}
function sleep (time) {
return new Promise((resolve) => setTimeout(resolve, time));
}
function getMessage (testList) {
const failedCases = [];
let successNum = 0,
failedNum = 0,
stopedNum = 0,
len = 0,
msg = '';
testList.forEach(item => {
len++;
if (item.code === 0) {
successNum++;
} else if (item.code === 1) {
failedNum++;
failedCases.push(item.name);
} else {
stopedNum++;
}
});
if (failedNum === 0 && stopedNum === 0) {
msg = `一共 ${len} 测试用例,全部验证通过`;
} else {
msg = `一共 ${len} 测试用例,${successNum} 个验证通过, ${failedNum} 个未通过, ${stopedNum} 个未执行`;
}
return { msg, len, successNum, failedNum, stopedNum, failedCases };
}
// format time to ISO8601
function datetimeFormatLong2String (longMillTypeDate) {
const date = new Date();
date.setTime(longMillTypeDate);
return date.toISOString()
}
const endTime = new Date().getTime();
const executionTime = (endTime - startTime) / 1000;
const reportsResult = {
message: getMessage(testList),
runTime: executionTime + 's',
startTime: startTime,
numbs: testList.length,
list: testList
};
const mode = ctx.params.mode || 'html';
if (ctx.params.download === true) {
ctx.set('Content-Disposition', `attachment; filename=test.${mode}`);
}
try {
const testData = {
project_id: projectId,
plan_id: planId,
uid: this.getUid(),
col_names: testColNames,
env: curEnvList,
test_url: ctx.href,
status: reportsResult.message.len === reportsResult.message.successNum ? "成功" : "失败",
data: reportsResult
};
const saveResult = await this.testResultModel.save(testData);
const autoTestUrl = `${originUrl}/api/open/plugin/test/result?id=${saveResult._id}`;
if (planId && planId !== -1) {
await this.testPlanModel.update(planId, { last_test_time: yapi.commons.time() });
// let plan = await this.testPlanModel.find(planId);
const trigger = planInfo.notice_trigger, notifier = planInfo.notifier ? planInfo.notifier.url : "";
const successNum = reportsResult.message.successNum;
const failedNum = reportsResult.message.failedNum;
const isSend = (trigger === "any")
|| (trigger === "success" && reportsResult.message.failedNum === 0)
|| ((trigger === "fail" || trigger === "part" || trigger === "existFail") && failedNum > 0);
// || (trigger === "fail" && successNum === 0)
// || (trigger === "part" && successNum < reportsResult.message.len && successNum > 0);
const fail = `未通过用例:<font color="blue"> ${reportsResult.message.failedCases} </font>`;
const success = ``;
const webhook = planInfo.webhook;
const webhookUrl = planInfo.webhook_url;
const webhookData = planInfo.webhook_data;
const failedCasesStr = `${reportsResult.message.failedCases}`;
const failedCasesArr = failedCasesStr.split(',');
if (isSend) {
// wework
if (notifier) {
const colorV = testData.status === "成功" ? "#058205" : "red";
const content = `测试结果:${planInfo.plan_name} 执行<font color="${colorV}">${testData.status}</font>\n${reportsResult.message.msg}
${testData.status === '失败' ? fail : success}
\n访问以下[链接查看](${autoTestUrl})测试结果详情`;
tools.sendWorkWX(notifier, content);
}
// webhook
if (webhook) {
try {
const postData = JSON.parse(webhookData)
const ydata = {
planName: planInfo.plan_name,
status: testData.status,
failedCases: failedCasesArr,
detail: `${autoTestUrl}`
}
postData.yapi = ydata;
const webHookResult = await tools.sendWebHook(webhookUrl, postData);
// yapi.commons.log(`webHookResult external:${JSON.stringify(webHookResult.data)}`);
await this.testPlanModel.update(planId, { webhook_status: `成功,${JSON.stringify(webHookResult.data)}` });
} catch (error) {
yapi.commons.log(`webhook send error:${error.message}`, logType);
await this.testPlanModel.update(planId, { webhook_status: `${error.message}` });
}
}
}
}
if (ctx.params.email === true && reportsResult.message.failedNum !== 0) {
yapi.commons.sendNotice(projectId, {
title: `YApi自动化测试报告`,
content: `
<html>
<head>
<title>测试报告</title>
<meta charset="utf-8" />
<body>
<div>
<h3>测试结果:</h3>
<p>${reportsResult.message.msg}</p>
<h3>访问以下链接查看测试结果详情:</h3>
<p><a herf="${autoTestUrl}">${autoTestUrl}</a></p>
</div>
</body>
</html>`
});
}
} catch (e) {
yapi.commons.log(e, 'error');
}
if (ctx.params.mode === 'json') {
return (ctx.body = reportsResult);
} else {
console.log(`reportsResult: ${JSON.stringify(reportsResult)}`);
return (ctx.body = renderToHtml(reportsResult,'', colLink));
}
}
/**
* 获取项目下的测试结果
* @param {*} ctx
*/
async getTestResults (ctx) {
try {
const projectId = ctx.params.project_id;
const planId = ctx.params.plan_id;
let results;
if (projectId) {
results = await this.testResultModel.findByProject(projectId)
}
if (planId) {
results = await this.testResultModel.findByPlan(planId)
}
ctx.body = yapi.commons.resReturn(results);
} catch (e) {
ctx.body = yapi.commons.resReturn(null, 401, e.message);
}
}
/**
* 获取测试结果
* @param {*} ctx
*/
async getTestResult (ctx) {
// if (!this.$tokenAuth && !this.$auth) {
// return (ctx.body = yapi.commons.resReturn(null, 40022, 'token 验证失败'));
// }
try {
const id = ctx.params.id;
const results = await this.testResultModel.get(id)
const projectId = results.project_id
const plan_url = results.test_url
const urlObj = URL.parse(plan_url, true);
const colId = urlObj.query.id;
const originUrl = Config.instance.host || ctx.request.origin;
const colLink = `${originUrl}/project/${projectId}/interface/col/${colId}`;
if (!results || !results.data) {
ctx.body = yapi.commons.resReturn(null, 404, '查找不到此报告');
}else{
ctx.body = renderToHtml(results.data, '', colLink);
}
} catch (e) {
ctx.body = yapi.commons.resReturn(null, 401, e.message);
}
}
/**
* 清空测试结果
* @param {*} ctx
*/
async delTestResults (ctx) {
try {
const plan_id = ctx.params.plan_id;
const result = await this.testResultModel.deleteAll(plan_id);
ctx.body = yapi.commons.resReturn(result);
} catch (e) {
ctx.body = yapi.commons.resReturn(null, 401, e.message);
}
}
}
module.exports = testResultController;