feat: 增加返回数据验证

This commit is contained in:
gaoxiaolin.gao 2018-08-08 15:33:14 +08:00
parent ab8d9ce127
commit ef42bb7156
8 changed files with 109 additions and 48 deletions

View File

@ -3,6 +3,7 @@
* json schema number和integer支持枚举
* 服务端测试增加下载功能
* 增加 mock 接口请求字段参数验证
* 增加返回数据验证
### Bug Fixed

View File

@ -13,19 +13,20 @@ import {
Tabs,
Switch,
Row,
Col
Col,
Alert
} from 'antd';
import constants from '../../constants/variable.js';
import AceEditor from 'client/components/AceEditor/AceEditor';
import _ from 'underscore';
import { isJson, deepCopyJson } from '../../common.js';
import { isJson, deepCopyJson, json5_parse } from '../../common.js';
import axios from 'axios';
import ModalPostman from '../ModalPostman/index.js';
import CheckCrossInstall, { initCrossRequest } from './CheckCrossInstall.js';
import './Postman.scss';
import ProjectEnv from '../../containers/Project/Setting/ProjectEnv/index.js';
import json5 from 'json5';
const { handleParamsValue, ArrayToObject } = require('common/utils.js');
const { handleParamsValue, ArrayToObject, schemaValidator } = require('common/utils.js');
const {
handleParams,
checkRequestBodyIsRaw,
@ -264,7 +265,7 @@ export default class Run extends Component {
// console.log('global',global);
let globalValue = ArrayToObject(global);
return handleParamsValue(val, {
global:globalValue
global: globalValue
});
}
@ -295,7 +296,6 @@ export default class Run extends Component {
loading: true
});
let options = handleParams(this.state, this.handleValue),
result;
@ -335,6 +335,15 @@ export default class Run extends Component {
res_body_type: 'json'
});
}
// 对返回值数据结构和定义的返回数据结构进行 格式校验
let validResult = this.resBodyValidator(this.props.data, result.body);
if (!validResult.valid) {
this.setState({ test_valid_msg: `返回参数 ${validResult.message}` });
} else {
this.setState({ test_valid_msg: '' });
}
this.setState({
resStatusCode: result.status,
resStatusText: result.statusText,
@ -342,6 +351,20 @@ export default class Run extends Component {
test_res_body: result.body
});
};
// 返回数据与定义数据的比较判断
resBodyValidator = (interfaceData, test_res_body) => {
const { res_body_type, res_body_is_json_schema, res_body } = interfaceData;
let validResult = { valid: true };
if (res_body_type === 'json' && res_body_is_json_schema) {
const schema = json5_parse(res_body);
const params = json5_parse(test_res_body);
validResult = schemaValidator(schema, params);
}
console.log(validResult);
return validResult;
};
changeParam = (name, v, index, key) => {
key = key || 'value';
@ -848,6 +871,14 @@ export default class Run extends Component {
>
{this.state.resStatusCode + ' ' + this.state.resStatusText}
</h2>
{this.state.test_valid_msg && (
<Alert
message="Warning"
type="warning"
showIcon
description={this.state.test_valid_msg}
/>
)}
<div className="container-header-body">
<div className="header">
@ -861,7 +892,7 @@ export default class Run extends Component {
readOnly={true}
className="pretty-editor-header"
data={this.state.test_res_header}
mode="json"
mode="json"
/>
</div>
<div className="resizer">

View File

@ -1,6 +1,7 @@
const Mock = require('mockjs');
const filter = require('./power-string.js').filter;
const json5 = require('json5');
const Ajv = require('ajv');
/**
* 作用解析规则串 key 然后根据规则串的规则以及路径找到在 json 中对应的数据
* 规则串$.{key}.{body||params}.{dataPath} 其中 body 为返回数据params 为请求数据datapath 为数据的路径
@ -237,3 +238,40 @@ exports.timeago = function(timestamp) {
return '刚刚';
}
};
// json schema 验证器
exports.schemaValidator = function(schema, params) {
try {
const ajv = new Ajv({
format: false,
meta: false,
allErrors: true
});
let metaSchema = require('ajv/lib/refs/json-schema-draft-04.json');
ajv.addMetaSchema(metaSchema);
ajv._opts.defaultMeta = metaSchema.id;
ajv._refs['http://json-schema.org/schema'] = 'http://json-schema.org/draft-04/schema';
var localize = require('ajv-i18n');
const validate = ajv.compile(schema);
let valid = validate(params);
let message = '';
if (!valid) {
localize.zh(validate.errors);
message += ajv.errorsText(validate.errors, { separator: '\n' });
}
return {
valid: valid,
message: message
};
} catch (e) {
return {
valid: false,
message: e.message
};
}
};

View File

@ -223,7 +223,7 @@ class interfaceColController extends baseController {
if ((await this.checkAuth(project._id, 'project', 'view')) !== true) {
return (ctx.body = yapi.commons.resReturn(null, 406, '没有权限'));
}
}
}
for (let index = 0; index < resultList.length; index++) {
let result = resultList[index].toObject();
@ -559,9 +559,9 @@ class interfaceColController extends baseController {
return (ctx.body = yapi.commons.resReturn(null, 400, '用例id不能为空'));
}
if (!params.casename) {
return (ctx.body = yapi.commons.resReturn(null, 400, '用例名称不能为空'));
}
// if (!params.casename) {
// return (ctx.body = yapi.commons.resReturn(null, 400, '用例名称不能为空'));
// }
let caseData = await this.caseModel.get(params.id);
let auth = await this.checkAuth(caseData.project_id, 'project', 'edit');
@ -638,6 +638,7 @@ class interfaceColController extends baseController {
result.req_params = yapi.commons.handleParamsValue(data.req_params, result.req_params);
result.interface_up_time = data.up_time;
result.req_body_is_json_schema = data.req_body_is_json_schema;
result.res_body_is_json_schema = data.res_body_is_json_schema;
ctx.body = yapi.commons.resReturn(result);
} catch (e) {
ctx.body = yapi.commons.resReturn(null, 400, e.message);

View File

@ -212,6 +212,26 @@ class interfaceColController extends baseController {
ctx.body = yapi.commons.resReturn(null, 402, e.message);
}
}
/**
* 测试返回值
* @interface /test/response
* @method get
* @return {Object}
* @example
*/
async testResponse(ctx) {
try {
// let result = `<div><h2>12222222</h2></div>`
// let result = `wieieieieiieieie`
let result = {a: '12'}
ctx.body = result;
} catch (e) {
ctx.body = yapi.commons.resReturn(null, 402, e.message);
}
}
}
module.exports = interfaceColController;

View File

@ -2,6 +2,7 @@ const yapi = require('../yapi.js');
const projectModel = require('../models/project.js');
const interfaceModel = require('../models/interface.js');
const mockExtra = require('../../common/mock-extra.js');
const { schemaValidator } = require('../../common/utils.js');
const _ = require('underscore');
const Mock = require('mockjs');
/**
@ -117,13 +118,13 @@ function mockValidator(interfaceData, ctx) {
if (interfaceData.req_body_type === 'json' && interfaceData.res_body_is_json_schema === true) {
const schema = yapi.commons.json_parse(interfaceData.req_body_other);
const params = yapi.commons.json_parse(ctx.request.body);
validResult = yapi.commons.schemaValidator(schema, params);
validResult = schemaValidator(schema, params);
}
if (noRequiredArr.length > 0 || (validResult && !validResult.valid)) {
let message = `错误信息:`;
message += noRequiredArr.length > 0 ? `缺少必须字段 ${noRequiredArr.join(',')}` : '';
message += validResult && !validResult.valid ? `shema 验证 ${validResult.message}` : '';
message += validResult && !validResult.valid ? `shema 验证请求参数 ${validResult.message}` : '';
return {
valid: false,

View File

@ -538,6 +538,11 @@ let routerConfig = {
action: 'testRaw',
path: 'raw',
method: 'post'
},
{
action: 'testResponse',
path: 'response',
method: 'get'
}
],
open: [

View File

@ -617,39 +617,3 @@ exports.handleMockScript = function(script, context) {
context.delay = sandbox.delay;
};
// json schema 验证器
exports.schemaValidator = function(schema, params) {
try {
const ajv = new Ajv({
format: false,
meta: false,
allErrors: true
});
let metaSchema = require('ajv/lib/refs/json-schema-draft-04.json');
ajv.addMetaSchema(metaSchema);
ajv._opts.defaultMeta = metaSchema.id;
ajv._refs['http://json-schema.org/schema'] = 'http://json-schema.org/draft-04/schema';
var localize = require('ajv-i18n');
const validate = ajv.compile(schema);
let valid = validate(params);
let message = '请求参数 ';
if (!valid) {
localize.zh(validate.errors);
message += ajv.errorsText(validate.errors, { separator: '\n' });
}
return {
valid: valid,
message: message
};
} catch (e) {
return {
valid: false,
message: e.message
};
}
};