UNPKG

yapi-plugin-pl-auto-test

Version:

YAPI自动化测试插件,支持在YAPI设置测试计划,历史测试结果存入ES,界面显示测试结果,自定义通知。

423 lines (387 loc) 15.2 kB
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;