feat: merge dev
@ -1,6 +1,9 @@
|
||||
## 1.1.3
|
||||
### Bug Fixed
|
||||
* 修复了切换集合环境的 Bug
|
||||
* 修复了 mockServer 拿不到 Post 请求 Body
|
||||
* 修复了接口调试 pathParams 无法使用 mock 参数和变量参数
|
||||
|
||||
|
||||
|
||||
## 1.1.2
|
||||
|
@ -26,12 +26,14 @@ YApi 是<strong>高效</strong>、<strong>易用</strong>、<strong>功能强大
|
||||
|
||||
#### 升级
|
||||
升级项目版本是非常容易的,并且不会影响已有的项目数据,只会同步 vendors 目录下的源码文件。
|
||||
|
||||
cd {项目目录}
|
||||
yapi ls //查看版本号列表
|
||||
yapi update //更新到最新版本
|
||||
yapi update -v {Version} //更新到指定版本
|
||||
|
||||
|
||||
|
||||
### 在线 Demo
|
||||
<p><a target="_blank" href="http://yapi.demo.qunar.com">yapi.demo.qunar.com</a></p>
|
||||
|
||||
|
@ -1,7 +1,9 @@
|
||||
import React from 'react';
|
||||
import moment from 'moment';
|
||||
import constants from './constants/variable';
|
||||
import constants from './constants/variable'
|
||||
import Mock from 'mockjs'
|
||||
import json5 from 'json5'
|
||||
import MockExtra from 'common/mock-extra.js'
|
||||
|
||||
const Roles = {
|
||||
0 : 'admin',
|
||||
@ -194,6 +196,10 @@ function handleMockWord(word) {
|
||||
return Mock.mock(word);
|
||||
}
|
||||
|
||||
exports.getMockText = (mockTpl) => {
|
||||
return JSON.stringify(Mock.mock(MockExtra(json5.parse(mockTpl), {})), null, " ")
|
||||
}
|
||||
|
||||
/**
|
||||
* 合并后新的对象属性与 Obj 一致,nextObj 有对应属性则取 nextObj 属性值,否则取 Obj 属性值
|
||||
* @param {Object} Obj 旧对象
|
||||
|
@ -11,6 +11,7 @@ const MockExtra = require('common/mock-extra.js')
|
||||
import './Postman.scss';
|
||||
import json5 from 'json5'
|
||||
import { handleMockWord, isJson } from '../../common.js'
|
||||
import _ from "underscore"
|
||||
|
||||
function json_parse(data) {
|
||||
try {
|
||||
@ -137,7 +138,6 @@ export default class Run extends Component {
|
||||
case_env = '',
|
||||
test_status = '',
|
||||
test_res_body = '',
|
||||
test_report = [],
|
||||
test_res_header = '',
|
||||
mock_verify = false
|
||||
} = data;
|
||||
@ -174,7 +174,7 @@ export default class Run extends Component {
|
||||
bodyType: req_body_type || 'form',
|
||||
loading: false,
|
||||
test_status: test_status,
|
||||
validRes: test_report,
|
||||
validRes: [],
|
||||
res: test_res_body,
|
||||
resHeader: test_res_header,
|
||||
resMockTest: mock_verify
|
||||
@ -197,7 +197,7 @@ export default class Run extends Component {
|
||||
return;
|
||||
}
|
||||
const { headers, bodyForm, pathParam, bodyOther, caseEnv, domains, method, pathname, query, bodyType } = this.state;
|
||||
const urlObj = URL.parse(domains.find(item => item.name === caseEnv).domain);
|
||||
const urlObj = URL.parse(_.find(domains, item => item.name === caseEnv).domain);
|
||||
let path = pathname
|
||||
pathParam.forEach(item => {
|
||||
path = path.replace(`:${item.name}`, item.value || `:${item.name}`);
|
||||
@ -236,6 +236,7 @@ export default class Run extends Component {
|
||||
data: reqBody,
|
||||
files: bodyType === 'form' ? this.getFiles(bodyForm) : {},
|
||||
file: bodyType === 'file' ? 'single-file' : null,
|
||||
timeout: 8240000, //因浏览器限制,超时时间最多为两分钟
|
||||
success: (res, header, third) => {
|
||||
console.log('suc', third);
|
||||
this.setState({
|
||||
@ -377,6 +378,7 @@ export default class Run extends Component {
|
||||
}
|
||||
pathParam[index].name = v;
|
||||
} else {
|
||||
|
||||
pathParam[index].value = v;
|
||||
}
|
||||
this.setState({ pathParam, pathname: newPathname });
|
||||
@ -569,7 +571,8 @@ export default class Run extends Component {
|
||||
let isResJson = isJsonData(resHeader);
|
||||
let path = pathname;
|
||||
pathParam.forEach(item => {
|
||||
path = path.replace(`:${item.name}`, item.value || `:${item.name}`);
|
||||
let val = handleMockWord(item.value);
|
||||
path = path.replace(`:${item.name}`, val || `:${item.name}`);
|
||||
});
|
||||
const search = decodeURIComponent(URL.format({ query: this.getQueryObj(query) }));
|
||||
|
||||
|
@ -164,5 +164,10 @@ export default {
|
||||
{ name: '挑选(枚举)', mock: '@pick' },
|
||||
{ name: '打乱数组', mock: '@shuffle' },
|
||||
{ name: '协议', mock: '@protocol' }
|
||||
]
|
||||
],
|
||||
IP_REGEXP: /^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])(\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])){3}$/,
|
||||
docHref: {
|
||||
adv_mock_case: 'https://yapi.ymfe.org/adv_mock.html#Mock_期望',
|
||||
adv_mock_script: 'https://yapi.ymfe.org/adv_mock.html#自定义_Mock_脚本'
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ import {
|
||||
setGroupList,
|
||||
fetchGroupMsg
|
||||
} from '../../../reducer/modules/group.js'
|
||||
import _ from 'underscore'
|
||||
|
||||
import './GroupList.scss';
|
||||
|
||||
@ -185,7 +186,8 @@ export default class GroupList extends Component {
|
||||
@autobind
|
||||
selectGroup(e) {
|
||||
const groupId = e.key;
|
||||
const currGroup = this.props.groupList.find((group) => { return +group._id === +groupId });
|
||||
//const currGroup = this.props.groupList.find((group) => { return +group._id === +groupId });
|
||||
const currGroup = _.find(this.props.groupList, (group) => { return +group._id === +groupId });
|
||||
this.props.setCurrGroup(currGroup);
|
||||
this.props.history.replace(`${currGroup._id}`);
|
||||
this.props.fetchNewsData(groupId, "group", 1, 10)
|
||||
|
@ -76,8 +76,8 @@ export default class InterfaceCaseContent extends Component {
|
||||
let currColId = this.getColId(result.payload.data.data, currCaseId);
|
||||
this.props.history.push('/project/' + params.id + '/interface/case/' + currCaseId)
|
||||
await this.props.fetchCaseData(currCaseId)
|
||||
this.props.setColData({currCaseId: +currCaseId, currColId, isShowCol: false})
|
||||
this.setState({editCasename: this.props.currCase.casename})
|
||||
this.props.setColData({ currCaseId: +currCaseId, currColId, isShowCol: false })
|
||||
this.setState({ editCasename: this.props.currCase.casename })
|
||||
}
|
||||
|
||||
async componentWillReceiveProps(nextProps) {
|
||||
@ -87,8 +87,8 @@ export default class InterfaceCaseContent extends Component {
|
||||
let currColId = this.getColId(interfaceColList, newCaseId);
|
||||
if (oldCaseId !== newCaseId) {
|
||||
await this.props.fetchCaseData(newCaseId);
|
||||
this.props.setColData({currCaseId: +newCaseId, currColId, isShowCol: false})
|
||||
this.setState({editCasename: this.props.currCase.casename})
|
||||
this.props.setColData({ currCaseId: +newCaseId, currColId, isShowCol: false })
|
||||
this.setState({ editCasename: this.props.currCase.casename })
|
||||
}
|
||||
}
|
||||
|
||||
@ -97,7 +97,7 @@ export default class InterfaceCaseContent extends Component {
|
||||
}
|
||||
|
||||
updateCase = async () => {
|
||||
|
||||
|
||||
const {
|
||||
caseEnv: case_env,
|
||||
pathname: path,
|
||||
@ -110,9 +110,9 @@ export default class InterfaceCaseContent extends Component {
|
||||
bodyOther: req_body_other,
|
||||
resMockTest: mock_verify
|
||||
} = this.postman.state;
|
||||
|
||||
const {editCasename: casename} = this.state;
|
||||
const {_id: id} = this.props.currCase;
|
||||
|
||||
const { editCasename: casename } = this.state;
|
||||
const { _id: id } = this.props.currCase;
|
||||
let params = {
|
||||
id,
|
||||
casename,
|
||||
@ -127,15 +127,14 @@ export default class InterfaceCaseContent extends Component {
|
||||
req_body_other,
|
||||
mock_verify
|
||||
};
|
||||
if(this.postman.state.test_status !== 'error'){
|
||||
if (this.postman.state.test_status !== 'error') {
|
||||
params.test_res_body = this.postman.state.res;
|
||||
params.test_report = this.postman.state.validRes;
|
||||
params.test_status = this.postman.state.test_status;
|
||||
params.test_res_header = this.postman.state.resHeader;
|
||||
}
|
||||
|
||||
|
||||
if(params.test_res_body && typeof params.test_res_body === 'object'){
|
||||
|
||||
if (params.test_res_body && typeof params.test_res_body === 'object') {
|
||||
params.test_res_body = JSON.stringify(params.test_res_body, null, ' ');
|
||||
}
|
||||
|
||||
@ -167,30 +166,18 @@ export default class InterfaceCaseContent extends Component {
|
||||
render() {
|
||||
const { currCase, currProject } = this.props;
|
||||
const { isEditingCasename, editCasename } = this.state;
|
||||
const data = Object.assign({}, currCase, currProject, {_id: currCase._id});
|
||||
const data = Object.assign({}, currCase, currProject, { _id: currCase._id });
|
||||
return (
|
||||
<div style={{padding: '6px 0'}} className="case-content">
|
||||
<div style={{ padding: '6px 0' }} className="case-content">
|
||||
<div className="case-title">
|
||||
{!isEditingCasename && <Tooltip title="点击编辑" placement="bottom"><div className="case-name" onClick={this.triggerEditCasename}>
|
||||
{currCase.casename}
|
||||
</div></Tooltip>}
|
||||
|
||||
{isEditingCasename && <div className="edit-case-name">
|
||||
<Input value={editCasename} onChange={e => this.setState({editCasename: e.target.value})} style={{fontSize: 18}} />
|
||||
{/*<Button
|
||||
title="Enter"
|
||||
onClick={this.saveCasename}
|
||||
type="primary"
|
||||
style={{ marginLeft: 8 }}
|
||||
>保存</Button>
|
||||
<Button
|
||||
title="Esc"
|
||||
onClick={this.cancelEditCasename}
|
||||
type="primary"
|
||||
style={{ marginLeft: 8 }}
|
||||
>取消</Button>*/}
|
||||
<Input value={editCasename} onChange={e => this.setState({ editCasename: e.target.value })} style={{ fontSize: 18 }} />
|
||||
</div>}
|
||||
<span className="inter-link" style={{margin: '0px 8px 0px 6px', fontSize: 12}}>
|
||||
<span className="inter-link" style={{ margin: '0px 8px 0px 6px', fontSize: 12 }}>
|
||||
<Link className="text" to={`/project/${currProject._id}/interface/api/${currCase.interface_id}`}>对应接口</Link>
|
||||
</span>
|
||||
</div>
|
||||
|
@ -4,11 +4,12 @@ import PropTypes from 'prop-types'
|
||||
import { withRouter } from 'react-router'
|
||||
import { Link } from 'react-router-dom'
|
||||
import constants from '../../../../constants/variable.js'
|
||||
import { Tooltip, Icon, Button, Spin, Modal, message ,Select} from 'antd'
|
||||
import { Tooltip, Icon, Button, Spin, Modal, message, Select, Switch } from 'antd'
|
||||
import { fetchInterfaceColList, fetchCaseList, setColData } from '../../../../reducer/modules/interfaceCol'
|
||||
import HTML5Backend from 'react-dnd-html5-backend';
|
||||
import { DragDropContext } from 'react-dnd';
|
||||
import { isJson, handleMockWord, simpleJsonPathParse } from '../../../../common.js'
|
||||
import mockEditor from '../InterfaceList/mockEditor';
|
||||
import * as Table from 'reactabular-table';
|
||||
import * as dnd from 'reactabular-dnd';
|
||||
import * as resolve from 'table-resolver';
|
||||
@ -17,6 +18,8 @@ import URL from 'url';
|
||||
import Mock from 'mockjs'
|
||||
import json5 from 'json5'
|
||||
import CaseReport from './CaseReport.js'
|
||||
import _ from 'underscore'
|
||||
|
||||
const MockExtra = require('common/mock-extra.js')
|
||||
const Option = Select.Option;
|
||||
function json_parse(data) {
|
||||
@ -28,6 +31,14 @@ function json_parse(data) {
|
||||
}
|
||||
const HTTP_METHOD = constants.HTTP_METHOD;
|
||||
|
||||
function handleReport(json){
|
||||
try{
|
||||
return JSON.parse(json);
|
||||
}catch(e){
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@connect(
|
||||
state => {
|
||||
@ -74,7 +85,10 @@ class InterfaceColContent extends Component {
|
||||
visible: false,
|
||||
curCaseid: null,
|
||||
hasPlugin: false,
|
||||
currColEnv: ''
|
||||
currColEnv: '',
|
||||
advVisible: false,
|
||||
curScript: '',
|
||||
enableScript: false
|
||||
};
|
||||
this.onRow = this.onRow.bind(this);
|
||||
this.onMoveRow = this.onMoveRow.bind(this);
|
||||
@ -85,12 +99,15 @@ class InterfaceColContent extends Component {
|
||||
let { currColId } = this.props;
|
||||
const params = this.props.match.params;
|
||||
const { actionId } = params;
|
||||
currColId = +actionId ||
|
||||
result.payload.data.data.find(item => +item._id === +currColId) && +currColId ||
|
||||
this.currColId = currColId = +actionId ||
|
||||
result.payload.data.data[0]._id;
|
||||
this.props.history.push('/project/' + params.id + '/interface/col/' + currColId)
|
||||
if (currColId && currColId != 0) {
|
||||
await this.props.fetchCaseList(currColId);
|
||||
let result = await this.props.fetchCaseList(currColId);
|
||||
if(result.payload.data.errcode === 0){
|
||||
this.reports = handleReport(result.payload.data.colData.test_report);
|
||||
}
|
||||
|
||||
this.props.setColData({ currColId: +currColId, isShowCol: true })
|
||||
this.handleColdata(this.props.currCaseList)
|
||||
}
|
||||
@ -112,6 +129,7 @@ class InterfaceColContent extends Component {
|
||||
})
|
||||
}
|
||||
}, 500)
|
||||
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
@ -119,21 +137,16 @@ class InterfaceColContent extends Component {
|
||||
}
|
||||
|
||||
handleColdata = (rows) => {
|
||||
let { currColEnv } = this.state;
|
||||
const colEnv = this.props.currProject.env;
|
||||
currColEnv = currColEnv || colEnv[0].name;
|
||||
rows = rows.map((item) => {
|
||||
item.id = item._id;
|
||||
item._test_status = item.test_status;
|
||||
item.case_env = currColEnv;
|
||||
return item;
|
||||
})
|
||||
rows = rows.sort((n, o) => {
|
||||
return n.index - o.index;
|
||||
})
|
||||
this.setState({
|
||||
rows: rows,
|
||||
currColEnv
|
||||
rows: rows
|
||||
})
|
||||
}
|
||||
|
||||
@ -161,20 +174,20 @@ class InterfaceColContent extends Component {
|
||||
status = 'error';
|
||||
result = e;
|
||||
}
|
||||
|
||||
|
||||
let query = this.arrToObj(curitem.req_query);
|
||||
if(!query || typeof query !== 'object'){
|
||||
if (!query || typeof query !== 'object') {
|
||||
query = {};
|
||||
}
|
||||
let body = {};
|
||||
if(HTTP_METHOD[curitem.method].request_body){
|
||||
if(curitem.req_body_type === 'form'){
|
||||
if (HTTP_METHOD[curitem.method].request_body) {
|
||||
if (curitem.req_body_type === 'form') {
|
||||
body = this.arrToObj(curitem.req_body_form);
|
||||
}else {
|
||||
} else {
|
||||
body = isJson(curitem.req_body_other);
|
||||
}
|
||||
|
||||
if(!body || typeof body !== 'object'){
|
||||
|
||||
if (!body || typeof body !== 'object') {
|
||||
body = {};
|
||||
}
|
||||
}
|
||||
@ -193,24 +206,29 @@ class InterfaceColContent extends Component {
|
||||
rows: newRows
|
||||
})
|
||||
}
|
||||
await axios.post('/api/col/up_col', { col_id: this.props.currColId, test_report: JSON.stringify(this.reports) })
|
||||
}
|
||||
|
||||
handleTest = (interfaceData) => {
|
||||
handleTest = async (interfaceData) => {
|
||||
const { currProject } = this.props;
|
||||
const { case_env } = interfaceData;
|
||||
let { case_env } = interfaceData;
|
||||
let path = URL.resolve(currProject.basepath, interfaceData.path);
|
||||
interfaceData.req_params = interfaceData.req_params || [];
|
||||
interfaceData.req_params.forEach(item => {
|
||||
path = path.replace(`:${item.name}`, item.value || `:${item.name}`);
|
||||
let val = this.handleValue(item.value);
|
||||
path = path.replace(`:${item.name}`, val || `:${item.name}`);
|
||||
});
|
||||
const domains = currProject.env.concat();
|
||||
let currDomain = domains.find(item => item.name === case_env);
|
||||
if(!currDomain){
|
||||
|
||||
case_env = this.state.currColEnv ? this.state.currColEnv : case_env ;
|
||||
|
||||
let currDomain = _.find(domains, item => item.name === case_env);
|
||||
if (!currDomain) {
|
||||
currDomain = domains[0];
|
||||
}
|
||||
const urlObj = URL.parse(currDomain.domain);
|
||||
if(urlObj.pathname){
|
||||
if(urlObj.pathname[urlObj.pathname.length - 1] !== '/'){
|
||||
if (urlObj.pathname) {
|
||||
if (urlObj.pathname[urlObj.pathname.length - 1] !== '/') {
|
||||
urlObj.pathname += '/'
|
||||
}
|
||||
}
|
||||
@ -222,89 +240,132 @@ class InterfaceColContent extends Component {
|
||||
query: this.getQueryObj(interfaceData.req_query)
|
||||
});
|
||||
|
||||
let result = { code: 400, msg: '数据异常', validRes: [] };
|
||||
let that = this;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
let result = { code: 400, msg: '数据异常', validRes: [] };
|
||||
let that = this;
|
||||
|
||||
result.url = href;
|
||||
result.method = interfaceData.method;
|
||||
result.headers = that.getHeadersObj(interfaceData.req_headers);
|
||||
if(interfaceData.req_body_type === 'form'){
|
||||
result.body = that.arrToObj(interfaceData.req_body_form)
|
||||
}else{
|
||||
let reqBody = isJson(interfaceData.req_body_other);
|
||||
if(reqBody === false){
|
||||
result.body = this.handleValue(interfaceData.req_body_other)
|
||||
}else{
|
||||
result.body = JSON.stringify(this.handleJson(reqBody))
|
||||
}
|
||||
|
||||
result.url = href;
|
||||
result.method = interfaceData.method;
|
||||
result.headers = that.getHeadersObj(interfaceData.req_headers);
|
||||
if (interfaceData.req_body_type === 'form') {
|
||||
result.body = that.arrToObj(interfaceData.req_body_form)
|
||||
} else {
|
||||
let reqBody = isJson(interfaceData.req_body_other);
|
||||
if (reqBody === false) {
|
||||
result.body = this.handleValue(interfaceData.req_body_other)
|
||||
} else {
|
||||
result.body = JSON.stringify(this.handleJson(reqBody))
|
||||
}
|
||||
|
||||
window.crossRequest({
|
||||
|
||||
}
|
||||
try{
|
||||
let data =await this.crossRequest({
|
||||
url: href,
|
||||
method: interfaceData.method,
|
||||
headers: that.getHeadersObj(interfaceData.req_headers),
|
||||
data: result.body,
|
||||
success: (res, header) => {
|
||||
res = json_parse(res);
|
||||
result.res_header = header;
|
||||
result.res_body = res;
|
||||
if (res && typeof res === 'object') {
|
||||
let tpl = MockExtra(json_parse(interfaceData.res_body), {
|
||||
query: interfaceData.req_query,
|
||||
body: interfaceData.req_body_form
|
||||
})
|
||||
|
||||
let validRes = [];
|
||||
if (interfaceData.mock_verify) {
|
||||
validRes = Mock.valid(tpl, res);
|
||||
}
|
||||
if (validRes.length === 0) {
|
||||
result.code = 0;
|
||||
result.validRes = [{ message: '验证通过' }];
|
||||
resolve(result);
|
||||
} else if (validRes.length > 0) {
|
||||
result.code = 1;
|
||||
result.validRes = validRes;
|
||||
resolve(result)
|
||||
}
|
||||
} else {
|
||||
reject(result)
|
||||
}
|
||||
},
|
||||
error: (err, header) => {
|
||||
try {
|
||||
err = json_parse(err);
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
|
||||
err = err || '请求异常';
|
||||
result.code = 400;
|
||||
result.res_header = header;
|
||||
result.res_body = err;
|
||||
reject(result)
|
||||
}
|
||||
data: result.body
|
||||
})
|
||||
let res = data.res.body = json_parse(data.res.body);
|
||||
let header = data.res.header;
|
||||
result.res_header = header;
|
||||
result.res_body = res;
|
||||
let validRes = [];
|
||||
if (res && typeof res === 'object') {
|
||||
if (interfaceData.mock_verify) {
|
||||
let tpl = MockExtra(json_parse(interfaceData.res_body), {
|
||||
query: interfaceData.req_query,
|
||||
body: interfaceData.req_body_form
|
||||
})
|
||||
validRes = Mock.valid(tpl, res);
|
||||
}
|
||||
}
|
||||
let responseData = Object.assign({}, {
|
||||
status:data.res.status,
|
||||
body: res,
|
||||
header: data.res.header,
|
||||
statusText: data.res.statusText
|
||||
})
|
||||
await that.handleScriptTest(interfaceData, responseData, validRes);
|
||||
if (validRes.length === 0) {
|
||||
result.code = 0;
|
||||
result.validRes = [{ message: '验证通过' }];
|
||||
} else if (validRes.length > 0) {
|
||||
result.code = 1;
|
||||
result.validRes = validRes;
|
||||
}
|
||||
return result;
|
||||
|
||||
}catch(data){
|
||||
if(data.err){
|
||||
data.err = data.err || '请求异常';
|
||||
try {
|
||||
data.err = json_parse( data.err);
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
result.res_body = data.err;
|
||||
result.res_header = data.header;
|
||||
}else{
|
||||
result.res_body = data.message;
|
||||
}
|
||||
|
||||
result.code = 400;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
crossRequest = (options)=>{
|
||||
return new Promise((resolve, reject)=>{
|
||||
options.success = function(res, header, data){
|
||||
resolve(data);
|
||||
}
|
||||
options.error = function(err, header){
|
||||
reject({
|
||||
err,
|
||||
header
|
||||
})
|
||||
}
|
||||
window.crossRequest(options);
|
||||
})
|
||||
}
|
||||
|
||||
handleJson = (data)=>{
|
||||
if(!data){
|
||||
//response, validRes
|
||||
handleScriptTest =async (interfaceData,response, validRes)=>{
|
||||
if(interfaceData.enable_script !== true){
|
||||
return null;
|
||||
}
|
||||
try{
|
||||
let test = await axios.post('/api/col/run_script', {
|
||||
response: response,
|
||||
records: this.records,
|
||||
script: interfaceData.test_script
|
||||
})
|
||||
if(test.data.errcode !== 0){
|
||||
validRes.push({
|
||||
message: test.data.data[0]
|
||||
})
|
||||
}
|
||||
}catch(err){
|
||||
console.log(err);
|
||||
validRes.push({
|
||||
message: err.message
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
handleJson = (data) => {
|
||||
if (!data) {
|
||||
return data;
|
||||
}
|
||||
if(typeof data === 'string'){
|
||||
if (typeof data === 'string') {
|
||||
return this.handleValue(data);
|
||||
}else if(typeof data === 'object'){
|
||||
for(let i in data){
|
||||
} else if (typeof data === 'object') {
|
||||
for (let i in data) {
|
||||
data[i] = this.handleJson(data[i]);
|
||||
}
|
||||
}else{
|
||||
} else {
|
||||
return data;
|
||||
}
|
||||
return data;
|
||||
return data;
|
||||
}
|
||||
|
||||
handleValue = (val) => {
|
||||
@ -329,7 +390,7 @@ class InterfaceColContent extends Component {
|
||||
return obj;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
getQueryObj = (query) => {
|
||||
query = query || [];
|
||||
@ -370,7 +431,6 @@ class InterfaceColContent extends Component {
|
||||
index: index
|
||||
})
|
||||
})
|
||||
|
||||
axios.post('/api/col/up_col_index', changes).then()
|
||||
if (rows) {
|
||||
this.setState({ rows });
|
||||
@ -378,17 +438,12 @@ class InterfaceColContent extends Component {
|
||||
}
|
||||
|
||||
async componentWillReceiveProps(nextProps) {
|
||||
const { interfaceColList } = nextProps;
|
||||
const { actionId: oldColId, id } = this.props.match.params
|
||||
let newColId = nextProps.match.params.actionId
|
||||
if (!interfaceColList.find(item => +item._id === +newColId)&&interfaceColList[0]._id) {
|
||||
this.props.history.push('/project/' + id + '/interface/col/' + interfaceColList[0]._id)
|
||||
} else if ((oldColId !== newColId) || interfaceColList !== this.props.interfaceColList) {
|
||||
if (newColId && newColId != 0) {
|
||||
await this.props.fetchCaseList(newColId);
|
||||
this.props.setColData({ currColId: +newColId, isShowCol: true })
|
||||
this.handleColdata(this.props.currCaseList)
|
||||
}
|
||||
let newColId = !isNaN(nextProps.match.params.actionId) ? +nextProps.match.params.actionId : 0;
|
||||
if(newColId && this.currColId && newColId !== this.currColId){
|
||||
this.currColId = newColId;
|
||||
await this.props.fetchCaseList(newColId);
|
||||
this.props.setColData({ currColId: +newColId, isShowCol: true })
|
||||
this.handleColdata(this.props.currCaseList)
|
||||
}
|
||||
}
|
||||
|
||||
@ -400,7 +455,57 @@ class InterfaceColContent extends Component {
|
||||
visible: true,
|
||||
curCaseid: id
|
||||
})
|
||||
}
|
||||
|
||||
openAdv = (id) => {
|
||||
let findCase = _.find(this.props.currCaseList, item=> item.id === id)
|
||||
|
||||
this.setState({
|
||||
enableScript: findCase.enable_script,
|
||||
curScript: findCase.test_script,
|
||||
advVisible: true,
|
||||
curCaseid: id
|
||||
}, () => {
|
||||
let that = this;
|
||||
if(that.Editor){
|
||||
that.Editor.setValue(this.state.curScript);
|
||||
}else{
|
||||
that.Editor = mockEditor({
|
||||
container: 'case-script',
|
||||
data: this.state.curScript,
|
||||
onChange: function (d) {
|
||||
that.setState({
|
||||
curScript: d.text
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
handleAdvCancel = () => {
|
||||
this.setState({
|
||||
advVisible: false
|
||||
});
|
||||
}
|
||||
|
||||
handleAdvOk = async () => {
|
||||
const {curCaseid, enableScript, curScript} = this.state;
|
||||
const res = await axios.post('/api/col/up_case', {
|
||||
id: curCaseid,
|
||||
test_script: curScript,
|
||||
enable_script: enableScript
|
||||
});
|
||||
if(res.data.errcode === 0){
|
||||
message.success('更新成功');
|
||||
}
|
||||
this.setState({
|
||||
advVisible: false
|
||||
});
|
||||
let currColId = this.currColId;
|
||||
await this.props.fetchCaseList(currColId);
|
||||
this.props.setColData({ currColId: +currColId, isShowCol: true })
|
||||
this.handleColdata(this.props.currCaseList)
|
||||
}
|
||||
|
||||
handleCancel = () => {
|
||||
@ -411,7 +516,7 @@ class InterfaceColContent extends Component {
|
||||
|
||||
colEnvChange = (envName) => {
|
||||
let rows = [...this.state.rows];
|
||||
for(var i in rows){
|
||||
for (var i in rows) {
|
||||
rows[i].case_env = envName;
|
||||
}
|
||||
this.setState({
|
||||
@ -470,15 +575,19 @@ class InterfaceColContent extends Component {
|
||||
},
|
||||
cell: {
|
||||
formatters: [(value, { rowData }) => {
|
||||
switch (rowData.test_status) {
|
||||
case 'ok':
|
||||
return <div ><Icon style={{ color: '#00a854' }} type="check-circle" /></div>
|
||||
case 'error':
|
||||
let id = rowData._id;
|
||||
let code = this.reports[id] ? this.reports[id].code : 0;
|
||||
if(rowData.test_status === 'loading'){
|
||||
return <div ><Spin /></div>
|
||||
}
|
||||
|
||||
switch (code) {
|
||||
case 0:
|
||||
return <div ><Tooltip title="Pass"><Icon style={{ color: '#00a854' }} type="check-circle" /></Tooltip></div>
|
||||
case 400:
|
||||
return <div ><Tooltip title="请求异常"><Icon type="info-circle" style={{ color: '#f04134' }} /></Tooltip></div>
|
||||
case 'invalid':
|
||||
return <div ><Tooltip title="返回数据校验未通过"><Icon type="exclamation-circle" style={{ color: '#ffbf00' }} /></Tooltip></div>
|
||||
case 'loading':
|
||||
return <div ><Spin /></div>
|
||||
case 1:
|
||||
return <div ><Tooltip title="验证失败"><Icon type="exclamation-circle" style={{ color: '#ffbf00' }} /></Tooltip></div>
|
||||
default:
|
||||
return <div ><Icon style={{ color: '#00a854' }} type="check-circle" /></div>
|
||||
}
|
||||
@ -501,22 +610,29 @@ class InterfaceColContent extends Component {
|
||||
}
|
||||
]
|
||||
}
|
||||
}, {
|
||||
},
|
||||
{
|
||||
header: {
|
||||
label: '测试报告'
|
||||
label: '操作'
|
||||
|
||||
},
|
||||
props: {
|
||||
style: {
|
||||
width: '100px'
|
||||
width: '200px'
|
||||
}
|
||||
},
|
||||
cell: {
|
||||
formatters: [(text, { rowData }) => {
|
||||
if (!this.reports[rowData.id]) {
|
||||
return null;
|
||||
let reportFun = () => {
|
||||
if (!this.reports[rowData.id]) {
|
||||
return null;
|
||||
}
|
||||
return <Button onClick={() => this.openReport(rowData.id)}>测试报告</Button>
|
||||
}
|
||||
return <Button onClick={() => this.openReport(rowData.id)}>报告</Button>
|
||||
return <div className="interface-col-table-action">
|
||||
<Button onClick={() => this.openAdv(rowData.id)} type="primary">高级</Button>
|
||||
{reportFun()}
|
||||
</div>
|
||||
}]
|
||||
}
|
||||
}
|
||||
@ -542,16 +658,17 @@ class InterfaceColContent extends Component {
|
||||
<Tooltip title="点击查看文档"><Icon type="question-circle-o" /></Tooltip>
|
||||
</a></h2>
|
||||
<div style={{ display: 'inline-block', margin: 0, marginBottom: '16px' }}>
|
||||
<Select value={currColEnv} style={{ width: "320px" }} onChange={this.colEnvChange}>
|
||||
<Select value={currColEnv} style={{ width: "320px" }} onChange={this.colEnvChange}>
|
||||
<Option key="default" value="" >默认使用 case 详情页面保存的 domain</Option>
|
||||
{
|
||||
colEnv.map((item)=>{
|
||||
return <Option key={item._id} value={item.name}>{item.name+": "+item.domain}</Option>;
|
||||
colEnv.map((item) => {
|
||||
return <Option key={item._id} value={item.name}>{item.name + ": " + item.domain}</Option>;
|
||||
})
|
||||
}
|
||||
</Select>
|
||||
</div>
|
||||
{this.state.hasPlugin?
|
||||
<Button type="primary" style={{ float: 'right' }} onClick={this.executeTests}>开始测试</Button>:
|
||||
{this.state.hasPlugin ?
|
||||
<Button type="primary" style={{ float: 'right' }} onClick={this.executeTests}>开始测试</Button> :
|
||||
<Tooltip title="请安装 cross-request Chrome 插件">
|
||||
<Button disabled type="primary" style={{ float: 'right' }} >开始测试</Button>
|
||||
</Tooltip>
|
||||
@ -584,6 +701,21 @@ class InterfaceColContent extends Component {
|
||||
>
|
||||
<CaseReport {...this.reports[this.state.curCaseid]} />
|
||||
</Modal>
|
||||
|
||||
<Modal
|
||||
title="自定义测试脚本"
|
||||
width="660px"
|
||||
style={{ minHeight: '500px' }}
|
||||
visible={this.state.advVisible}
|
||||
onCancel={this.handleAdvCancel}
|
||||
onOk={this.handleAdvOk}
|
||||
>
|
||||
<h3>
|
||||
是否开启:
|
||||
<Switch checked={this.state.enableScript} onChange={e=>this.setState({enableScript: e})} />
|
||||
</h3>
|
||||
<div className="case-script" id="case-script" style={{ minHeight: 500 }}></div>
|
||||
</Modal>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -115,4 +115,14 @@
|
||||
.interface-col-table-body td{
|
||||
padding-left:5px;
|
||||
}
|
||||
|
||||
.interface-col-table-action button{
|
||||
margin-right: 5px;
|
||||
padding: 5px 10px;
|
||||
max-width: 90px;
|
||||
}
|
||||
}
|
||||
|
||||
.case-script{
|
||||
margin: 10px
|
||||
}
|
@ -211,7 +211,7 @@ class InterfaceEditForm extends Component {
|
||||
this.setState({
|
||||
req_radio_type: HTTP_METHOD[this.state.method].request_body ? 'req-body' : 'req-query'
|
||||
})
|
||||
let that = this, mockPreview, resBodyEditor;
|
||||
let that = this;
|
||||
const initReqBody = that.state.req_body_other;
|
||||
const initResBody = that.state.res_body;
|
||||
mockEditor({
|
||||
@ -225,24 +225,20 @@ class InterfaceEditForm extends Component {
|
||||
}
|
||||
})
|
||||
|
||||
resBodyEditor = mockEditor({
|
||||
this.resBodyEditor = mockEditor({
|
||||
container: 'res_body_json',
|
||||
data: that.state.res_body,
|
||||
onChange: function (d) {
|
||||
if (d.format === true) {
|
||||
mockPreview.setValue(d.mockText)
|
||||
}
|
||||
that.setState({
|
||||
res_body: d.text,
|
||||
res_body_mock: d.mockText
|
||||
res_body: d.text
|
||||
});
|
||||
EditFormContext.props.changeEditStatus(initResBody !== d.text);
|
||||
}
|
||||
})
|
||||
|
||||
mockPreview = mockEditor({
|
||||
this.mockPreview = mockEditor({
|
||||
container: 'mock-preview',
|
||||
data: resBodyEditor.curData.mockText,
|
||||
data: '',
|
||||
readOnly: true
|
||||
})
|
||||
|
||||
@ -281,9 +277,28 @@ class InterfaceEditForm extends Component {
|
||||
this.setState(newValue)
|
||||
}
|
||||
|
||||
handleJsonType = (key) => {
|
||||
handleMockPreview = ()=>{
|
||||
let str = '';
|
||||
try{
|
||||
if(this.resBodyEditor.curData.format === true){
|
||||
str = JSON.stringify(this.resBodyEditor.curData.mockData(), null , ' ');
|
||||
}else{
|
||||
str = '解析出错: ' + this.resBodyEditor.curData.format;
|
||||
}
|
||||
|
||||
}catch(err){
|
||||
str = '解析出错: ' + err.message;
|
||||
}
|
||||
this.mockPreview.setValue(
|
||||
str
|
||||
)
|
||||
}
|
||||
|
||||
handleJsonType = (key) => {
|
||||
key = key || 'tpl';
|
||||
if(key === 'preview'){
|
||||
this.handleMockPreview()
|
||||
}
|
||||
this.setState({
|
||||
jsonType: key
|
||||
})
|
||||
@ -742,7 +757,7 @@ class InterfaceEditForm extends Component {
|
||||
<TabPane tab="模板" key="tpl">
|
||||
|
||||
</TabPane>
|
||||
<TabPane tab="预览" key="preview">
|
||||
<TabPane tab="预览" key="preview">
|
||||
|
||||
</TabPane>
|
||||
|
||||
|
@ -68,10 +68,8 @@ function run(options) {
|
||||
var obj = json5.parse(json);
|
||||
curData.format = true;
|
||||
curData.jsonData = obj;
|
||||
curData.mockData = Mock.mock(MockExtra(obj, {}));
|
||||
curData.mockText = JSON.stringify(curData.mockData, null, " ");
|
||||
curData.mockData = ()=>Mock.mock(MockExtra(obj, {})); //为防止时时 mock 导致页面卡死的问题,改成函数式需要用到再计算
|
||||
} catch (e) {
|
||||
|
||||
curData.format = e.message;
|
||||
}
|
||||
}
|
||||
|
@ -156,6 +156,20 @@ hooks = {
|
||||
type: 'listener',
|
||||
mulit: true,
|
||||
listener: []
|
||||
},
|
||||
/*
|
||||
* 添加 reducer
|
||||
* @param Object reducerModules
|
||||
*
|
||||
* @info
|
||||
* importDataModule = {};
|
||||
*
|
||||
*/
|
||||
|
||||
add_reducer: {
|
||||
type: 'listener',
|
||||
mulit: true,
|
||||
listener: []
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -13,7 +13,7 @@ export default (state = initialState, action) => {
|
||||
case FETCH_MOCK_COL:
|
||||
return {
|
||||
...state,
|
||||
list: action.payload.data.data
|
||||
list: action.payload.data
|
||||
}
|
||||
default:
|
||||
return state
|
||||
|
@ -8,9 +8,10 @@ import news from './news.js'
|
||||
import addInterface from './addInterface.js'
|
||||
import menu from './menu.js'
|
||||
import follow from './follow.js'
|
||||
import mockCol from './mockCol.js'
|
||||
|
||||
export default combineReducers({
|
||||
import { emitHook } from 'client/plugin.js'
|
||||
|
||||
const reducerModules = {
|
||||
group,
|
||||
user,
|
||||
inter,
|
||||
@ -19,6 +20,9 @@ export default combineReducers({
|
||||
news,
|
||||
addInterface,
|
||||
menu,
|
||||
follow,
|
||||
mockCol
|
||||
})
|
||||
follow
|
||||
}
|
||||
emitHook('add_reducer', reducerModules);
|
||||
|
||||
|
||||
export default combineReducers(reducerModules)
|
||||
|
118
common/lib.js
@ -1,4 +1,5 @@
|
||||
const path = require('path');
|
||||
const _ =require('underscore');
|
||||
|
||||
function getPluginConfig(name, type) {
|
||||
let pluginConfig;
|
||||
@ -18,33 +19,98 @@ function getPluginConfig(name, type) {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
/**
|
||||
function isObj(object) {
|
||||
return object && typeof (object) == 'object' && Object.prototype.toString.call(object).toLowerCase() == "[object object]";
|
||||
}
|
||||
|
||||
function isArray(object) {
|
||||
return object && typeof (object) == 'object' && object.constructor == Array;
|
||||
}
|
||||
|
||||
function getLength(object) {
|
||||
return Object.keys(object).length;
|
||||
}
|
||||
|
||||
function Compare(objA, objB) {
|
||||
if (!isObj(objA) && !isObj(objB)){
|
||||
return objA === objB;
|
||||
}
|
||||
if (!isObj(objA) || !isObj(objB)) return false;
|
||||
if (getLength(objA) != getLength(objB)) return false;
|
||||
return CompareObj(objA, objB, true);
|
||||
}
|
||||
|
||||
function CompareObj(objA, objB, flag) {
|
||||
for (var key in objA) {
|
||||
if (!flag)
|
||||
break;
|
||||
if (!objB.hasOwnProperty(key)) { flag = false; break; }
|
||||
if (!isArray(objA[key])) {
|
||||
if (objB[key] != objA[key]) { flag = false; break; }
|
||||
} else {
|
||||
if (!isArray(objB[key])) { flag = false; break; }
|
||||
var oA = objA[key], oB = objB[key];
|
||||
if (oA.length != oB.length) { flag = false; break; }
|
||||
for (var k in oA) {
|
||||
if (!flag)
|
||||
break;
|
||||
flag = CompareObj(oA[k], oB[k], flag);
|
||||
}
|
||||
}
|
||||
}
|
||||
return flag;
|
||||
}
|
||||
|
||||
/**
|
||||
* type @string enum[plugin, ext] plugin是外部插件,ext是内部插件
|
||||
*/
|
||||
initPlugins: function (plugins, type) {
|
||||
if (!plugins) {
|
||||
return [];
|
||||
}
|
||||
if (typeof plugins !== 'object' || !Array.isArray(plugins)) {
|
||||
throw new Error('插件配置有误,请检查', plugins);
|
||||
}
|
||||
|
||||
return plugins.map(item => {
|
||||
let pluginConfig;
|
||||
if (item && typeof item === 'string') {
|
||||
pluginConfig = getPluginConfig(item, type);
|
||||
return Object.assign({}, pluginConfig, { name: item, enable: true })
|
||||
} else if (item && typeof item === 'object') {
|
||||
pluginConfig = getPluginConfig(item.name, type);
|
||||
return Object.assign({},
|
||||
pluginConfig,
|
||||
{
|
||||
name: item.name,
|
||||
options: item.options,
|
||||
enable: item.enable === false ? false : true
|
||||
})
|
||||
}
|
||||
})
|
||||
exports.initPlugins = function (plugins, type) {
|
||||
if (!plugins) {
|
||||
return [];
|
||||
}
|
||||
if (typeof plugins !== 'object' || !Array.isArray(plugins)) {
|
||||
throw new Error('插件配置有误,请检查', plugins);
|
||||
}
|
||||
|
||||
plugins = plugins.map(item => {
|
||||
let pluginConfig;
|
||||
if (item && typeof item === 'string') {
|
||||
pluginConfig = getPluginConfig(item, type);
|
||||
return Object.assign({}, pluginConfig, { name: item, enable: true })
|
||||
} else if (item && typeof item === 'object') {
|
||||
pluginConfig = getPluginConfig(item.name, type);
|
||||
return Object.assign({},
|
||||
pluginConfig,
|
||||
{
|
||||
name: item.name,
|
||||
options: item.options,
|
||||
enable: item.enable === false ? false : true
|
||||
})
|
||||
}
|
||||
})
|
||||
plugins = plugins.filter(item=>{
|
||||
return item.enable === true && (item.server || item.client)
|
||||
})
|
||||
|
||||
return _.uniq(plugins, item=>item.name)
|
||||
}
|
||||
exports.jsonEqual = Compare;
|
||||
|
||||
exports.isDeepMatch = function(obj, properties){
|
||||
if(!properties || typeof properties !== 'object'){
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!obj || typeof obj !== 'object'){
|
||||
return true;
|
||||
}
|
||||
|
||||
let match = true;
|
||||
for(var i in properties){
|
||||
if(!Compare(obj[i], properties[i])){
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return match;
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
var strRegex = /\${([a-zA-Z0-9_\.]+)\}/g;
|
||||
var strRegex = /\${([a-zA-Z]+)\.?([a-zA-Z0-9_\.]*)\}/i;
|
||||
var varSplit = '.';
|
||||
var mockSplit = '|';
|
||||
var Mock = require('mockjs');
|
||||
@ -33,7 +33,7 @@ function mock(mockJSON, context) {
|
||||
c[i] = (p[i].constructor === Array) ? [] : {};
|
||||
parse(p[i], c[i]);
|
||||
} else if(p[i] && typeof p[i] === 'string'){
|
||||
p[i] = handleStr(p[i]);
|
||||
p[i] = handleStr(p[i]);
|
||||
var filters = i.split(mockSplit), newFilters = [].concat(filters);
|
||||
c[i] = p[i];
|
||||
if (filters.length > 1) {
|
||||
@ -63,9 +63,17 @@ function mock(mockJSON, context) {
|
||||
if (typeof str !== 'string' || str.indexOf('{') === -1 || str.indexOf('}') === -1 || str.indexOf('$') === -1) {
|
||||
return str;
|
||||
}
|
||||
str = str.replace(strRegex, function (matchs, name) {
|
||||
|
||||
let matchs = str.match(strRegex);
|
||||
if(matchs){
|
||||
let name = matchs[1] + (matchs[2]? '.' + matchs[2] : '');
|
||||
if(!name) return str;
|
||||
var names = name.split(varSplit);
|
||||
var data = context;
|
||||
|
||||
if(typeof context[names[0]] === undefined){
|
||||
return str;
|
||||
}
|
||||
names.forEach(function (n) {
|
||||
if (data === '') return '';
|
||||
if (n in data) {
|
||||
@ -75,7 +83,7 @@ function mock(mockJSON, context) {
|
||||
}
|
||||
});
|
||||
return data;
|
||||
});
|
||||
}
|
||||
return str;
|
||||
}
|
||||
}
|
||||
|
BIN
doc/images/usage/adv-mock-case1.png
Normal file
After Width: | Height: | Size: 104 KiB |
BIN
doc/images/usage/adv-mock-case3.png
Normal file
After Width: | Height: | Size: 89 KiB |
BIN
doc/images/usage/adv-mock-case4.png
Normal file
After Width: | Height: | Size: 65 KiB |
BIN
doc/images/usage/adv-mock-case5.png
Normal file
After Width: | Height: | Size: 419 KiB |
BIN
doc/images/usage/case-list.gif
Normal file
After Width: | Height: | Size: 282 KiB |
101
doc/page/usage/adv_mock.md
Normal file
@ -0,0 +1,101 @@
|
||||
## Mock 期望
|
||||
在测试时,很多时候需要根据不同的请求参数、IP 返回不同的 HTTP Code、HTTP 头和 JSON 数据。
|
||||
|
||||
Mock 期望就是根据设置的请求过滤规则,返回期望数据。
|
||||
|
||||
### 使用方法
|
||||
1. 进入接口详情页,点击『高级 Mock』选项。
|
||||
<div class="doc-img-wrapper"><img class="doc-img-r" src="./images/usage/adv-mock-case1.png"/></div>
|
||||
2. 点击『添加期望』,填写过滤规则以及期望返回数据,点击『确定』保存。
|
||||
<div class="doc-img-wrapper"><img class="doc-img-r" src="./images/usage/adv-mock-case3.png"/></div>
|
||||
<div class="doc-img-wrapper"><img class="doc-img-r" src="./images/usage/adv-mock-case4.png"/></div>
|
||||
3. 然后尝试在浏览器里发送符合规则的请求,查看返回的数据是否符合期望。
|
||||
<div class="doc-img-wrapper"><img class="doc-img-r" src="./images/usage/adv-mock-case5.png"/></div>
|
||||
|
||||
### 期望填写
|
||||
* 期望名称:给此条期望命名
|
||||
|
||||
请求
|
||||
|
||||
* IP 过滤:请求的 IP 是设置的地址才可能返回期望。默认 IP 过滤关闭,任何 IP 地址都可能返回期望。
|
||||
* 参数过滤:请求必须包含设置的参数,并且值相等才可能返回期望。参数可以在 Body 或 Query 中。
|
||||
|
||||
响应
|
||||
|
||||
* HTTP Code:期望响应的 HTTP 状态码
|
||||
* 延时:期望响应的延迟时间
|
||||
* HTTP 头:期望响应带有的 HTTP 头
|
||||
* 返回 JSON:期望返回的 JSON 数据
|
||||
|
||||
|
||||
## 自定义 Mock 脚本
|
||||
在前端开发阶段,对于某些接口,业务相对复杂,而 UI 端也需要根据接口返回的不同内容去做相应的处理。
|
||||
|
||||
YApi 提供了写 JS 脚本方式处理这一问题,可以根据用户请求的参数修改返回内容。
|
||||
|
||||
### 全局变量
|
||||
请求
|
||||
|
||||
- `header` 请求的 HTTP 头
|
||||
- `params` 请求参数,包括 Body、Query 中所有参数
|
||||
- `cookie` 请求带的 Cookies
|
||||
|
||||
响应
|
||||
|
||||
- `mockJson`
|
||||
接口定义的响应数据 Mock 模板
|
||||
|
||||
- `resHeader`
|
||||
响应的 HTTP 头
|
||||
|
||||
- `httpCode`
|
||||
响应的 HTTP 状态码
|
||||
|
||||
- `delay`
|
||||
Mock 响应延时,单位为 ms
|
||||
|
||||
- `Random`
|
||||
Mock.Random 方法,详细使用方法请查看 <a href="https://github.com/nuysoft/Mock/wiki/Mock.Random">Wiki</a>
|
||||
|
||||
### 使用方法
|
||||
1. 首先开启此功能
|
||||
2. Mock 脚本就是用 JavaScript 对 `mockJson` 变量修改,请避免被全局变量(httpCode, resHeader, delay)的修改
|
||||
|
||||
|
||||
### 示例1, 根据请求参数重写 mockJson
|
||||
```
|
||||
if(params.type == 1){
|
||||
mockJson.errcode = 400;
|
||||
mockJson.errmsg = 'error;
|
||||
}
|
||||
|
||||
if(header.token == 't'){
|
||||
mockJson.errcode = 300;
|
||||
mockJson.errmsg = 'error;
|
||||
}
|
||||
|
||||
if(cookie.type == 'a'){
|
||||
mockJson.errcode = 500;
|
||||
mockJson.errmsg = 'error;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### 示例2, 生成高度自定义数据内容
|
||||
```
|
||||
var a = [1,1,1,1,1,1,1,1,1,1]
|
||||
|
||||
mockJson = {
|
||||
errcode: 0,
|
||||
email: Random.email('qq.com'),
|
||||
data: a.map(function(item){
|
||||
return Random.city() + '银行'
|
||||
})
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Mock 优先级说明
|
||||
请求 Mock 数据时,规则匹配优先级:Mock 期望 > 自定义 Mock 脚本 > 普通 Mock。
|
||||
|
||||
如果前面匹配到 Mock 数据,后面 Mock 则不返回。
|
@ -1,9 +1,9 @@
|
||||
## 介绍
|
||||
<p style='text-indent:2em;line-height:1.8em'>在平时的开发过程中,经常遇到的一个问题是每次调试接口都需要重新填写参数,YApi测试集可以保存之前填写的参数,方便下次的调试。YApi测试集还可以一次性测试所有接口,每个的请求参数可以通过前面已请求的接口数据读取,或填写mock随机字符串。</p>
|
||||
<p style='text-indent:2em;line-height:1.8em'>在平时的开发过程中,经常遇到的一个问题是每次调试接口都需要重新填写参数,YApi测试集不但能够保存之前填写的参数,方便下次的调试,还可以一次性测试所有接口,每个的请求参数可以通过前面已请求的接口数据读取,或填写mock随机字符串,通过设置断言脚本验证返回数据的正确性,</p>
|
||||
|
||||
## 测试列表
|
||||
|
||||
<img class="doc-img" style="width:100%" src="./images/usage/case-list.jpg" />
|
||||
<img class="doc-img" style="width: 618px;" src="./images/usage/case-list.gif" />
|
||||
|
||||
在测试列表可以看到每个测试用例的 key,还有 开始测试、报告等功能
|
||||
|
||||
@ -48,3 +48,33 @@ $.{key}.{params|body}.{path}
|
||||
其中 **$.** 是使用 **动态变量** 的标志,$.269.**params** 即表示 key 值为 269 用例的请求参数,$.269.**body** 即表示 key 值为 269 用例的返回值。
|
||||
|
||||
> Tips: 上下拖动测试集合的列表项可以调整测试的顺序。
|
||||
|
||||
### 高级
|
||||
可通过写断言脚本,实现精准测试,支持 js 所有语法
|
||||
|
||||
#### 公共变量
|
||||
* assert
|
||||
|
||||
断言函数,详细 api可查看 <a target="_blank" href="https://nodejs.org/dist/latest-v8.x/docs/api/assert.html">document</a>
|
||||
|
||||
* status
|
||||
|
||||
http 状态码
|
||||
|
||||
* body
|
||||
|
||||
返回 response body
|
||||
|
||||
* header
|
||||
|
||||
返回 response header
|
||||
|
||||
* records
|
||||
|
||||
记录的 http 请求信息,假设需要获取 key为555的接口参数或者响应数据,可通过 records[555].params 或 records[555].body 获取
|
||||
|
||||
#### 示例
|
||||
```
|
||||
assert.equal(body.errcode, 0)
|
||||
assert.equal(body.data.group_name, 'testGroup')
|
||||
```
|
||||
|
@ -1,16 +1,16 @@
|
||||
## Mock介绍
|
||||
|
||||
<p style='text-indent:2em;line-height:1.8em'>YApi的Mock功能可以根据用户的输入接口信息如协议、URL、接口名、请求头、请求参数、mock规则([点击到Mock规则](#mock))生成Mock接口,这些接口会自动生成模拟数据,创建者可以自由构造需要的数据。而且与常见的Mock方式如将Mock写在代码里和JS拦截等相比yapi的Mock在使用场景和效率和复杂度上是相差甚远的,正是由于yapi的Mock是一个第三方平台,那么在团队开发时任何人都可以权限许可下创建、修改接口信息等操作,这对于团队开发是很有好处的。 </p>
|
||||
<p style='text-indent:2em;line-height:1.8em'>YApi的 Mock 功能可以根据用户的输入接口信息如协议、URL、接口名、请求头、请求参数、mock 规则([点击到 Mock 规则](#mock))生成 Mock 接口,这些接口会自动生成模拟数据,创建者可以自由构造需要的数据。而且与常见的 Mock 方式如将 Mock 写在代码里和JS拦截等相比 yapi 的 Mock 在使用场景和效率和复杂度上是相差甚远的,正是由于 yapi 的 Mock 是一个第三方平台,那么在团队开发时任何人都可以权限许可下创建、修改接口信息等操作,这对于团队开发是很有好处的。 </p>
|
||||
|
||||
**mock地址解析**:yapi平台网址+mock+**您的项目id**+**接口实际请求path**
|
||||
**mock地址解析**:`YApi平台网址 + mock + 您的项目id + 接口实际请求path`
|
||||
|
||||
假设你 YApi 的部署地址为:http://yapi.xxx.com,然后后面的都可以用这个地址作为示例
|
||||
假设你 YApi 的部署地址为:http://yapi.xxx.com 然后用这个地址作为示例
|
||||
|
||||
mockd地址: http://yapi.xxx.com/mock/29/api/hackathon/login
|
||||
mockd 地址: http://yapi.xxx.com/mock/29/api/hackathon/login
|
||||
|
||||
注:项目id可以在项目设置里查看到
|
||||
> 注:项目 id 可以在项目设置里查看到
|
||||
|
||||
## 定义mock数据示例
|
||||
## 定义 mock 数据示例
|
||||
```
|
||||
{
|
||||
"status|0-1": 0, //接口状态
|
||||
@ -36,7 +36,7 @@
|
||||
|
||||
## YApi-Mock 跟 Mockjs 区别
|
||||
|
||||
<a href="http://mockjs.com">Mockjs 官网</a>
|
||||
<a href="http://mockjs.com/examples.html">Mockjs 官网</a>
|
||||
|
||||
1 因为 yapi 基于 json 定义 mock ,无法使用 mockjs 原有的函数功能,正则表达式需要基于 rule 书写,示例如下:
|
||||
|
||||
@ -48,7 +48,7 @@
|
||||
|
||||
```
|
||||
|
||||
2 支持替换请求的query,body参数
|
||||
2 支持替换请求的 query, body 参数
|
||||
|
||||
```
|
||||
{
|
||||
@ -58,9 +58,11 @@
|
||||
|
||||
```
|
||||
|
||||
## 如何使用Mock?
|
||||
### 1 在js代码直接请求yapi提供的mock地址(不用担心跨域问题)
|
||||
在代码直接请求yapi提供的mock地址,以jQuery为例:
|
||||
## 如何使用 Mock
|
||||
|
||||
### 1 在 js 代码直接请求yapi提供的 mock 地址(不用担心跨域问题)
|
||||
|
||||
在代码直接请求 yapi 提供的 mock 地址,以 jQuery 为例:
|
||||
|
||||
````javascript
|
||||
let prefix = 'http://yapi.xxx.com/mock/2817'
|
||||
@ -73,7 +75,7 @@ $.post(prefix+'/baseapi/path', {username: 'xxx'}, function(res){
|
||||
|
||||
优点:不用修改项目代码
|
||||
|
||||
#### 2.1 基于nginx反向代理
|
||||
#### 2.1 基于 nginx 反向代理
|
||||
|
||||
```` nginx
|
||||
location /baseapi
|
||||
@ -82,7 +84,7 @@ proxy_pass http://yapi.xxx.com/mock/2817/baseapi; #baseapi后面没有"/"
|
||||
}
|
||||
````
|
||||
|
||||
#### 2.2 基于ykit mock功能
|
||||
#### 2.2 基于 ykit mock功能
|
||||
|
||||
```javascript
|
||||
{
|
||||
@ -97,7 +99,7 @@ proxy_pass http://yapi.xxx.com/mock/2817/baseapi; #baseapi后面没有"/"
|
||||
|
||||
|
||||
|
||||
#### 2.3 基于ykit Jerry代理
|
||||
#### 2.3 基于 ykit Jerry 代理
|
||||
|
||||
假设您本地服务器访问地址是: http://xxx.com
|
||||
|
||||
@ -105,31 +107,10 @@ proxy_pass http://yapi.xxx.com/mock/2817/baseapi; #baseapi后面没有"/"
|
||||
|
||||
<span id="mock"></span>
|
||||
|
||||
## 高级Mock
|
||||
在前端开发阶段,对于某些接口,业务相对复杂,而 UI 端也需要根据接口返回的不同内容去做相应的处理
|
||||
|
||||
YApi 提供了写 js 脚本方式处理这一问题,可以根据用户请求的参数修改返回内容。
|
||||
|
||||
### 全局变量
|
||||
1. mockJson
|
||||
2. query
|
||||
3. body
|
||||
4. header
|
||||
5. cookie
|
||||
|
||||
### 使用方法
|
||||
1. 首先开启此功能
|
||||
2. mock脚本就是用 javascript 对 mockJson 变量修改
|
||||
|
||||
|
||||
### 示例:
|
||||
<img class="doc-img" style="width: 80%" src="./images/usage/adv-mock.jpg" />
|
||||
|
||||
|
||||
|
||||
|
||||
## Mock语法规范
|
||||
>了解更多Mock详情:[Mock.js 官方文档](https://github.com/nuysoft/Mock/wiki/Syntax-Specification)
|
||||
|
||||
## Mock 语法规范
|
||||
>了解更多Mock详情:[Mock.js 官方文档](http://mockjs.com/examples.html)
|
||||
|
||||
Mock.js 的语法规范包括两部分:
|
||||
|
||||
|
@ -8,6 +8,18 @@ body, h1, h2, h3, h4, h5, h6 {
|
||||
margin-top: 3em;
|
||||
}
|
||||
|
||||
.doc-img-wrapper {
|
||||
background: #f7f7f7;
|
||||
padding: 16px;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
width: 80%;
|
||||
position: relative;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.doc-img-r {
|
||||
display: block;
|
||||
}
|
||||
.doc-img {
|
||||
width: 50%;
|
||||
display: block;
|
||||
|
@ -3,8 +3,10 @@ import React, { Component } from 'react'
|
||||
import axios from 'axios'
|
||||
import PropTypes from 'prop-types'
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import { Form, Switch, Button, message, Icon, Tooltip } from 'antd';
|
||||
import { Form, Switch, Button, message, Icon, Tooltip, Radio } from 'antd';
|
||||
import MockCol from './MockCol/MockCol.js'
|
||||
import mockEditor from 'client/containers/Project/Interface/InterfaceList/mockEditor';
|
||||
import constants from '../../client/constants/variable.js'
|
||||
const FormItem = Form.Item;
|
||||
|
||||
|
||||
@ -18,7 +20,8 @@ class AdvMock extends Component {
|
||||
super(props);
|
||||
this.state = {
|
||||
enable: false,
|
||||
mock_script: ''
|
||||
mock_script: '',
|
||||
tab: 'case'
|
||||
}
|
||||
}
|
||||
|
||||
@ -74,6 +77,12 @@ class AdvMock extends Component {
|
||||
})
|
||||
}
|
||||
|
||||
handleTapChange = (e) => {
|
||||
this.setState({
|
||||
tab: e.target.value
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
const formItemLayout = {
|
||||
labelCol: {
|
||||
@ -91,27 +100,39 @@ class AdvMock extends Component {
|
||||
}
|
||||
}
|
||||
};
|
||||
const { tab } = this.state;
|
||||
const isShowCase = tab === 'case';
|
||||
return <div style={{ padding: '20px 10px' }}>
|
||||
|
||||
<Form onSubmit={this.handleSubmit}>
|
||||
<FormItem
|
||||
label={<span>是否开启 <a target="_blank" rel="noopener noreferrer" href="https://yapi.ymfe.org/mock.html#高级Mock" ><Tooltip title="点击查看文档"><Icon type="question-circle-o" /></Tooltip></a></span>}
|
||||
{...formItemLayout}
|
||||
>
|
||||
<Switch checked={this.state.enable} onChange={this.onChange} checkedChildren="开" unCheckedChildren="关" />
|
||||
</FormItem>
|
||||
<div style={{textAlign: 'center', marginBottom: 20}}>
|
||||
<Radio.Group value={tab} size="large" onChange={this.handleTapChange}>
|
||||
<Radio.Button value="case">期望</Radio.Button>
|
||||
<Radio.Button value="script">脚本</Radio.Button>
|
||||
</Radio.Group>
|
||||
</div>
|
||||
<div style={{display: isShowCase ? 'none' : ''}}>
|
||||
<Form onSubmit={this.handleSubmit}>
|
||||
<FormItem
|
||||
label={<span>是否开启 <a target="_blank" rel="noopener noreferrer" href={constants.docHref.adv_mock_script} ><Tooltip title="点击查看文档"><Icon type="question-circle-o" /></Tooltip></a></span>}
|
||||
{...formItemLayout}
|
||||
>
|
||||
<Switch checked={this.state.enable} onChange={this.onChange} checkedChildren="开" unCheckedChildren="关" />
|
||||
</FormItem>
|
||||
|
||||
<FormItem
|
||||
label="Mock脚本"
|
||||
{...formItemLayout}
|
||||
>
|
||||
<div id="mock-script" style={{ minHeight: '500px' }} ></div>
|
||||
</FormItem>
|
||||
<FormItem {...tailFormItemLayout}>
|
||||
<Button type="primary" htmlType="submit">保存</Button>
|
||||
</FormItem>
|
||||
<FormItem
|
||||
label="Mock脚本"
|
||||
{...formItemLayout}
|
||||
>
|
||||
<div id="mock-script" style={{ minHeight: '500px' }} ></div>
|
||||
</FormItem>
|
||||
<FormItem {...tailFormItemLayout}>
|
||||
<Button type="primary" htmlType="submit">保存</Button>
|
||||
</FormItem>
|
||||
|
||||
</Form>
|
||||
</Form>
|
||||
</div>
|
||||
<div style={{display: isShowCase ? '' : 'none'}}>
|
||||
<MockCol/>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
@ -1,23 +1,33 @@
|
||||
import React, { Component } from 'react'
|
||||
// import axios from 'axios'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Button, Form, Input, Switch, Select, Icon, Modal } from 'antd';
|
||||
import { Button, Form, Input, Switch, Select, Icon, Modal, Col, Row, InputNumber, AutoComplete } from 'antd';
|
||||
import { safeAssign } from '../../../client/common.js';
|
||||
import { httpCodes } from '../index.js';
|
||||
import mockEditor from '../../../client/containers/Project/Interface/InterfaceList/mockEditor';
|
||||
import constants from '../../../client/constants/variable.js'
|
||||
import { httpCodes } from '../index.js'
|
||||
import { connect } from 'react-redux'
|
||||
|
||||
import './CaseDesModal.scss'
|
||||
|
||||
const Option = Select.Option;
|
||||
const FormItem = Form.Item;
|
||||
// const RadioButton = Radio.Button;
|
||||
// const RadioGroup = Radio.Group;
|
||||
const formItemLayout = {
|
||||
labelCol: {
|
||||
xs: { span: 24 },
|
||||
sm: { span: 5 }
|
||||
},
|
||||
wrapperCol: {
|
||||
xs: { span: 24 },
|
||||
sm: { span: 12 }
|
||||
}
|
||||
labelCol: { span: 5 },
|
||||
wrapperCol: { span: 12 }
|
||||
};
|
||||
const formItemLayoutWithOutLabel = {
|
||||
wrapperCol: { span: 12, offset: 5 }
|
||||
};
|
||||
|
||||
@connect(
|
||||
state => {
|
||||
return {
|
||||
currInterface: state.inter.curdata
|
||||
}
|
||||
}
|
||||
)
|
||||
@Form.create()
|
||||
export default class CaseDesModal extends Component {
|
||||
static propTypes = {
|
||||
@ -26,7 +36,14 @@ export default class CaseDesModal extends Component {
|
||||
onCancel: PropTypes.func,
|
||||
onOk: PropTypes.func,
|
||||
isAdd: PropTypes.bool,
|
||||
visible: PropTypes.bool
|
||||
visible: PropTypes.bool,
|
||||
currInterface: PropTypes.object
|
||||
}
|
||||
|
||||
state = {
|
||||
headers: [],
|
||||
paramsArr: [],
|
||||
paramsForm: 'form'
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
@ -34,6 +51,12 @@ export default class CaseDesModal extends Component {
|
||||
}
|
||||
|
||||
preProcess = caseData => {
|
||||
try {
|
||||
caseData = JSON.parse(JSON.stringify(caseData))
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
// caseModel
|
||||
// const a = {
|
||||
// interface_id: { type: Number, required: true },
|
||||
// project_id: {type: Number, required: true},
|
||||
@ -51,7 +74,7 @@ export default class CaseDesModal extends Component {
|
||||
// up_time: Number,
|
||||
// res_body: {type: String, required: true}
|
||||
// }
|
||||
const initCaseData = {
|
||||
const initCaseData = {
|
||||
ip: '',
|
||||
ip_enable: false,
|
||||
name: '',
|
||||
@ -59,19 +82,32 @@ export default class CaseDesModal extends Component {
|
||||
deplay: 0,
|
||||
headers: [{name: '', value: ''}],
|
||||
paramsArr: [{name: '', value: ''}],
|
||||
params: '',
|
||||
res_body: ''
|
||||
}
|
||||
caseData.paramsArr = caseData.params && caseData.params.length ? Object.keys(caseData.params).map(key => {
|
||||
const paramsArr = caseData.params && Object.keys(caseData.params).length ? Object.keys(caseData.params).map(key => {
|
||||
return { name: key, value: caseData.params[key] }
|
||||
}).filter(item => {
|
||||
if (typeof item.value === 'object') {
|
||||
this.setState({ paramsForm: 'json' })
|
||||
}
|
||||
return typeof item.value !== 'object'
|
||||
}) : [{name: '', value: ''}];
|
||||
caseData.headers = caseData.headers && caseData.headers.length ? caseData.headers : [{name: '', value: ''}];
|
||||
caseData = safeAssign(initCaseData, caseData);
|
||||
const headers = caseData.headers && caseData.headers.length ? caseData.headers : [{name: '', value: ''}];
|
||||
caseData.code = ''+caseData.code;
|
||||
caseData.params = JSON.stringify(caseData.params, null, 2);
|
||||
this.setState({
|
||||
headers,
|
||||
paramsArr
|
||||
})
|
||||
caseData = safeAssign(initCaseData, { ...caseData, headers, paramsArr });
|
||||
return caseData;
|
||||
}
|
||||
|
||||
endProcess = caseData => {
|
||||
const headers = [];
|
||||
const params = {};
|
||||
const { paramsForm } = this.state;
|
||||
caseData.headers.forEach(item => {
|
||||
if (item.name) {
|
||||
headers.push({
|
||||
@ -86,29 +122,164 @@ export default class CaseDesModal extends Component {
|
||||
}
|
||||
})
|
||||
caseData.headers = headers;
|
||||
caseData.params = params;
|
||||
if (paramsForm === 'form') {
|
||||
caseData.params = params;
|
||||
} else {
|
||||
try {
|
||||
caseData.params = JSON.parse(caseData.params)
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
delete caseData.paramsArr;
|
||||
return caseData;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.props.form.setFieldsValue(this.preProcess(this.props.caseData))
|
||||
this.shouldLoadEditor = true
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
if (this.shouldLoadEditor) {
|
||||
this.loadBodyEditor()
|
||||
this.loadParamsEditor()
|
||||
this.shouldLoadEditor = false
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (this.props.caseData !== nextProps.caseData) {
|
||||
this.props.form.setFieldsValue(this.preProcess(this.props.caseData))
|
||||
if (
|
||||
this.props.caseData !== nextProps.caseData ||
|
||||
this.props.visible !== nextProps.visible
|
||||
) {
|
||||
this.props.form.setFieldsValue(this.preProcess(nextProps.caseData))
|
||||
this.shouldLoadEditor = true
|
||||
}
|
||||
}
|
||||
|
||||
handleOk = () => {
|
||||
const form = this.props.form;
|
||||
this.props.onOk(this.endProcess(form.getFieldsValue()));
|
||||
form.validateFieldsAndScroll((err, values) => {
|
||||
if (!err) {
|
||||
this.props.onOk(this.endProcess(values));
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
addValues = (key) => {
|
||||
const { setFieldsValue, getFieldValue } = this.props.form;
|
||||
let values = getFieldValue(key);
|
||||
values = values.concat({ name: '', value: ''});
|
||||
this.setState({ [key]: values })
|
||||
setFieldsValue({ [key]: values })
|
||||
}
|
||||
removeValues = (key, index) => {
|
||||
const { setFieldsValue, getFieldValue } = this.props.form;
|
||||
let values = getFieldValue(key);
|
||||
values = values.filter((item, index2) => index !== index2);
|
||||
this.setState({ [key]: values })
|
||||
setFieldsValue({ [key]: values })
|
||||
}
|
||||
|
||||
getParamsKey = () => {
|
||||
let { req_query, req_body_form, req_body_type } = this.props.currInterface;
|
||||
const keys = [];
|
||||
|
||||
req_query.forEach(item => {
|
||||
keys.push(item.name)
|
||||
})
|
||||
if (req_body_type === 'form') {
|
||||
req_body_form.forEach(item => {
|
||||
keys.push(item.name)
|
||||
})
|
||||
}
|
||||
return keys
|
||||
}
|
||||
|
||||
loadBodyEditor = () => {
|
||||
const that = this;
|
||||
const { setFieldsValue } = this.props.form;
|
||||
this.props.visible && mockEditor({
|
||||
container: 'res_body_json',
|
||||
data: that.props.caseData.res_body,
|
||||
onChange: function (d) {
|
||||
if (d.format !== true) return false;
|
||||
setFieldsValue({ res_body: d.text })
|
||||
}
|
||||
});
|
||||
}
|
||||
loadParamsEditor = () => {
|
||||
const that = this;
|
||||
const { setFieldsValue } = this.props.form;
|
||||
this.props.visible && mockEditor({
|
||||
container: 'case_modal_params',
|
||||
data: that.props.caseData.params,
|
||||
onChange: function (d) {
|
||||
if (d.format !== true) return false;
|
||||
setFieldsValue({ params: d.text })
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
jsonValidator = (rule, value, callback) => {
|
||||
try {
|
||||
JSON.parse(value)
|
||||
callback()
|
||||
} catch (error) {
|
||||
callback(new Error())
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { getFieldDecorator, getFieldValue } = this.props.form;
|
||||
const { isAdd, visible, onCancel } = this.props;
|
||||
const { headers, paramsArr, paramsForm } = this.state;
|
||||
|
||||
const valuesTpl = (name, values, title) => {
|
||||
getFieldDecorator(name)
|
||||
const dataSource = name === 'headers' ? constants.HTTP_REQUEST_HEADER : this.getParamsKey();
|
||||
const display = (name === 'paramsArr' && paramsForm === 'json') ? 'none': ''
|
||||
return values.map((item, index) => (
|
||||
<div key={index} className={name} style={{ display }}>
|
||||
<FormItem
|
||||
{...(index === 0 ? formItemLayout : formItemLayoutWithOutLabel)}
|
||||
wrapperCol={index === 0 ? { span: 19 } : { span: 19, offset: 5 }}
|
||||
label={index ? '' : title}
|
||||
>
|
||||
<Row gutter={8}>
|
||||
<Col span={10}>
|
||||
<FormItem>
|
||||
{getFieldDecorator(`${name}[${index}].name`, { initialValue: item.name })(
|
||||
<AutoComplete
|
||||
dataSource={dataSource}
|
||||
filterOption={(inputValue, option) => option.props.children.toUpperCase().indexOf(inputValue.toUpperCase()) !== -1}
|
||||
/>
|
||||
)}
|
||||
</FormItem>
|
||||
</Col>
|
||||
<Col span={10}>
|
||||
<FormItem>
|
||||
{getFieldDecorator(`${name}[${index}].value`, { initialValue: item.value })(
|
||||
<Input />
|
||||
)}
|
||||
</FormItem>
|
||||
</Col>
|
||||
<Col span={4}>
|
||||
{values.length > 1 ? (
|
||||
<Icon
|
||||
className="dynamic-delete-button"
|
||||
type="minus-circle-o"
|
||||
onClick={() => this.removeValues(name, index)}
|
||||
/>
|
||||
) : null}
|
||||
</Col>
|
||||
</Row>
|
||||
</FormItem>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
getFieldDecorator('params')
|
||||
|
||||
return (
|
||||
<Modal
|
||||
@ -116,7 +287,10 @@ export default class CaseDesModal extends Component {
|
||||
visible={visible}
|
||||
maskClosable={false}
|
||||
onOk={this.handleOk}
|
||||
width={780}
|
||||
onCancel={() => onCancel()}
|
||||
afterClose={() => this.setState({paramsForm: 'form'})}
|
||||
className="case-des-modal"
|
||||
>
|
||||
<Form>
|
||||
<FormItem
|
||||
@ -129,23 +303,88 @@ export default class CaseDesModal extends Component {
|
||||
<Input placeholder="请输入期望名称" />
|
||||
)}
|
||||
</FormItem>
|
||||
<FormItem {...formItemLayout} label="IP 过滤">
|
||||
{getFieldDecorator('ip_enable', {
|
||||
valuePropName: 'checked',
|
||||
rules: [{ type: 'boolean' }]
|
||||
})(
|
||||
<Switch />
|
||||
)}
|
||||
{getFieldDecorator('ip')(
|
||||
<Input placeholder="请输入过滤的 IP 地址" />
|
||||
)}
|
||||
<h2 className="sub-title">请求</h2>
|
||||
<FormItem {...formItemLayout} label="IP 过滤" className="ip-filter">
|
||||
<Col span={6} className="ip-switch">
|
||||
<FormItem>
|
||||
{getFieldDecorator('ip_enable', {
|
||||
valuePropName: 'checked',
|
||||
rules: [{ type: 'boolean' }]
|
||||
})(
|
||||
<Switch />
|
||||
)}
|
||||
</FormItem>
|
||||
</Col>
|
||||
<Col span={18}>
|
||||
<div style={{display: getFieldValue('ip_enable') ? '' : 'none'}} className="ip">
|
||||
<FormItem>
|
||||
{getFieldDecorator('ip', getFieldValue('ip_enable') ? {
|
||||
rules: [{ pattern: constants.IP_REGEXP, message: '请填写正确的 IP 地址', required: true }]
|
||||
} : {})(
|
||||
<Input placeholder="请输入过滤的 IP 地址" />
|
||||
)}
|
||||
</FormItem>
|
||||
</div>
|
||||
</Col>
|
||||
</FormItem>
|
||||
<div className="params-form">
|
||||
<FormItem {...formItemLayoutWithOutLabel}>
|
||||
<Switch
|
||||
checkedChildren="JSON"
|
||||
unCheckedChildren="JSON"
|
||||
checked={paramsForm === 'json'}
|
||||
onChange={bool => {
|
||||
this.setState({ paramsForm: bool ? 'json' : 'form' }, () => {
|
||||
if (paramsForm === 'json') {
|
||||
this.loadParamsEditor()
|
||||
}
|
||||
})
|
||||
}}
|
||||
/>
|
||||
{
|
||||
// <RadioGroup
|
||||
// value={paramsForm}
|
||||
// size="small"
|
||||
// onChange={e => this.setState({ paramsForm: e.target.value })}
|
||||
// >
|
||||
// <RadioButton value="form">Form</RadioButton>
|
||||
// <RadioButton value="json">JSON</RadioButton>
|
||||
// </RadioGroup>
|
||||
}
|
||||
</FormItem>
|
||||
</div>
|
||||
{
|
||||
valuesTpl('paramsArr', paramsArr, '参数过滤')
|
||||
}
|
||||
<FormItem wrapperCol={{ span: 6, offset: 5 }} style={{display: paramsForm === 'form' ? '': 'none'}}>
|
||||
<Button size="default" type="primary" onClick={() => this.addValues('paramsArr')} style={{ width: '100%' }}>
|
||||
<Icon type="plus" /> 添加参数
|
||||
</Button>
|
||||
</FormItem>
|
||||
<FormItem {...formItemLayout} wrapperCol={{ span: 17 }} label="参数过滤" style={{display: paramsForm === 'form' ? 'none': ''}}>
|
||||
<div id="case_modal_params" style={{
|
||||
minHeight: "300px",
|
||||
border: "1px solid #d9d9d9",
|
||||
borderRadius: 4
|
||||
}} ></div>
|
||||
<FormItem
|
||||
{...formItemLayoutWithOutLabel}
|
||||
>
|
||||
{getFieldDecorator('params', paramsForm === 'json' ? {
|
||||
rules: [{ validator: this.jsonValidator, message: '请输入正确的 JSON!' }]
|
||||
} : {})(
|
||||
<Input style={{display: 'none'}} />
|
||||
)}
|
||||
</FormItem>
|
||||
</FormItem>
|
||||
<h2 className="sub-title">响应</h2>
|
||||
<FormItem
|
||||
{...formItemLayout}
|
||||
label="HTTP CODE"
|
||||
required
|
||||
label="HTTP Code"
|
||||
>
|
||||
{getFieldDecorator('code')(
|
||||
<Select search>
|
||||
<Select showSearch>
|
||||
{
|
||||
httpCodes.map(code => <Option key={''+code} value={''+code}>{''+code}</Option>)
|
||||
}
|
||||
@ -160,87 +399,33 @@ export default class CaseDesModal extends Component {
|
||||
initialValue: 0,
|
||||
rules: [{ required: true, message: '请输入延时时间!', type: 'integer' }]
|
||||
})(
|
||||
<Input placeholder="请输入延时时间" />
|
||||
<InputNumber placeholder="请输入延时时间" min={0}/>
|
||||
)}
|
||||
<span>ms</span>
|
||||
</FormItem>
|
||||
{
|
||||
getFieldDecorator('headers', { initialValue: [] }) &&
|
||||
getFieldValue('headers').map((item, index) => (
|
||||
<div key={index}>
|
||||
<FormItem
|
||||
{...formItemLayout}
|
||||
label={index ? '' : 'HTTP 头'}
|
||||
>
|
||||
{getFieldDecorator(`headers[${index}].name`)(
|
||||
<Input />
|
||||
)}
|
||||
</FormItem>
|
||||
<FormItem
|
||||
{...formItemLayout}
|
||||
>
|
||||
{getFieldDecorator(`headers[${index}].value`)(
|
||||
<Input />
|
||||
)}
|
||||
{getFieldValue('headers').length > 1 ? (
|
||||
<Icon
|
||||
className="dynamic-delete-button"
|
||||
type="minus-circle-o"
|
||||
onClick={() => this.removeHeaders(index)}
|
||||
/>
|
||||
) : null}
|
||||
</FormItem>
|
||||
</div>
|
||||
))
|
||||
valuesTpl('headers', headers, 'HTTP 头')
|
||||
}
|
||||
<FormItem>
|
||||
<Button type="dashed" onClick={this.addHeaders} style={{ width: '60%' }}>
|
||||
<FormItem wrapperCol={{ span: 6, offset: 5 }}>
|
||||
<Button size="default" type="primary" onClick={() => this.addValues('headers')} style={{ width: '100%' }}>
|
||||
<Icon type="plus" /> 添加 HTTP 头
|
||||
</Button>
|
||||
</FormItem>
|
||||
|
||||
{
|
||||
getFieldDecorator('paramsArr', { initialValue: [] }) &&
|
||||
getFieldValue('paramsArr').map((item, index) => (
|
||||
<div key={index}>
|
||||
<FormItem
|
||||
{...formItemLayout}
|
||||
label={index ? '' : '参数'}
|
||||
>
|
||||
{getFieldDecorator(`paramsArr[${index}].name`)(
|
||||
<Input />
|
||||
)}
|
||||
</FormItem>
|
||||
<FormItem
|
||||
>
|
||||
{getFieldDecorator(`paramsArr[${index}].value`)(
|
||||
<Input />
|
||||
)}
|
||||
{getFieldValue('paramsArr').length > 1 ? (
|
||||
<Icon
|
||||
className="dynamic-delete-button"
|
||||
type="minus-circle-o"
|
||||
onClick={() => this.removeParams(index)}
|
||||
/>
|
||||
) : null}
|
||||
</FormItem>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
<FormItem>
|
||||
<Button type="dashed" onClick={this.addParams} style={{ width: '60%' }}>
|
||||
<Icon type="plus" /> 添加参数
|
||||
</Button>
|
||||
</FormItem>
|
||||
|
||||
<FormItem
|
||||
{...formItemLayout}
|
||||
label="返回 JSON"
|
||||
>
|
||||
{getFieldDecorator('res_body', {
|
||||
rules: [{ required: true, message: '请输入期望名称!' }]
|
||||
})(
|
||||
<Input placeholder="返回 JSON" />
|
||||
)}
|
||||
<FormItem {...formItemLayout} wrapperCol={{ span: 17 }} label="返回 JSON" required>
|
||||
<div id="res_body_json" style={{
|
||||
minHeight: "300px",
|
||||
border: "1px solid #d9d9d9",
|
||||
borderRadius: 4
|
||||
}} ></div>
|
||||
<FormItem
|
||||
{...formItemLayoutWithOutLabel}
|
||||
>
|
||||
{getFieldDecorator('res_body', {
|
||||
rules: [{ validator: this.jsonValidator, message: '请输入正确的返回 JSON!' }]
|
||||
})(
|
||||
<Input style={{display: 'none'}} />
|
||||
)}
|
||||
</FormItem>
|
||||
</FormItem>
|
||||
</Form>
|
||||
</Modal>
|
||||
|
20
exts/yapi-plugin-advanced-mock/MockCol/CaseDesModal.scss
Normal file
@ -0,0 +1,20 @@
|
||||
.case-des-modal {
|
||||
.ant-modal-body {
|
||||
max-height: 520px;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
.ip-filter .ip>.ant-form-item, .ip-filter .ip-switch>.ant-form-item {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.headers>.ant-form-item, .paramsArr>.ant-form-item, .params-form>.ant-form-item {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.sub-title {
|
||||
clear: both;
|
||||
font-weight: normal;
|
||||
margin-top: .48rem;
|
||||
margin-bottom: .16rem;
|
||||
// border-left: 3px solid #2395f1;
|
||||
padding-left: 32px;
|
||||
}
|
||||
}
|
@ -3,15 +3,17 @@ import { connect } from 'react-redux'
|
||||
import axios from 'axios'
|
||||
import PropTypes from 'prop-types'
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import { Table, Button, message } from 'antd';
|
||||
import { fetchMockCol } from '../../../client/reducer/modules/mockCol'
|
||||
import { formatTime } from '../../../client/common.js';
|
||||
import { Table, Button, message, Popconfirm, Tooltip, Icon } from 'antd';
|
||||
import { fetchMockCol } from 'client/reducer/modules/mockCol'
|
||||
import { formatTime, getMockText } from 'client/common.js';
|
||||
import constants from 'client/constants/variable.js'
|
||||
import CaseDesModal from './CaseDesModal';
|
||||
|
||||
@connect(
|
||||
state => {
|
||||
return {
|
||||
list: state.mockCol.list
|
||||
list: state.mockCol.list,
|
||||
currInterface: state.inter.curdata
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -22,6 +24,7 @@ import CaseDesModal from './CaseDesModal';
|
||||
export default class MockCol extends Component {
|
||||
static propTypes = {
|
||||
list: PropTypes.array,
|
||||
currInterface: PropTypes.object,
|
||||
match: PropTypes.object,
|
||||
fetchMockCol: PropTypes.func
|
||||
}
|
||||
@ -42,7 +45,8 @@ export default class MockCol extends Component {
|
||||
}
|
||||
|
||||
handleOk = async (caseData) => {
|
||||
const interface_id = this.props.match.params.action;
|
||||
const { caseData: currcase } = this.state;
|
||||
const interface_id = this.props.match.params.actionId;
|
||||
const project_id = this.props.match.params.id;
|
||||
caseData = Object.assign({
|
||||
...caseData,
|
||||
@ -50,11 +54,25 @@ export default class MockCol extends Component {
|
||||
project_id: project_id
|
||||
})
|
||||
if (!this.state.isAdd) {
|
||||
caseData.id = 0;
|
||||
caseData.id = currcase._id;
|
||||
}
|
||||
axios.post('/api/plugin/advmock/case/save', caseData).then(res => {
|
||||
await axios.post('/api/plugin/advmock/case/save', caseData).then(async res => {
|
||||
if (res.data.errcode === 0) {
|
||||
message.success(this.state.isAdd ? '添加成功' : '保存成功');
|
||||
await this.props.fetchMockCol(interface_id);
|
||||
this.setState({ caseDesModalVisible: false })
|
||||
} else {
|
||||
message.error(res.data.errmsg);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
deleteCase = async (id) => {
|
||||
const interface_id = this.props.match.params.actionId;
|
||||
await axios.post('/api/plugin/advmock/case/del', {id}).then(async res => {
|
||||
if (res.data.errcode === 0) {
|
||||
message.success('删除成功');
|
||||
await this.props.fetchMockCol(interface_id);
|
||||
} else {
|
||||
message.error(res.data.errmsg);
|
||||
}
|
||||
@ -67,36 +85,103 @@ export default class MockCol extends Component {
|
||||
|
||||
render() {
|
||||
|
||||
const data = this.props.list;
|
||||
const { list: data, currInterface } = this.props;
|
||||
const { isAdd, caseData, caseDesModalVisible } = this.state;
|
||||
const initCaseData = {
|
||||
ip: '',
|
||||
ip_enable: false,
|
||||
name: currInterface.title,
|
||||
code: '200',
|
||||
deplay: 0,
|
||||
headers: [{name: '', value: ''}],
|
||||
paramsArr: [{name: '', value: ''}],
|
||||
res_body: getMockText(currInterface.res_body)
|
||||
}
|
||||
|
||||
let ipFilters = [];
|
||||
let ipObj = {};
|
||||
let userFilters = [];
|
||||
let userObj = {};
|
||||
data.forEach(item => {
|
||||
ipObj[item.ip_enable ? item.ip : ''] = '';
|
||||
userObj[item.username] = '';
|
||||
})
|
||||
ipFilters = Object.keys(Object.assign(ipObj)).map(value => {
|
||||
if (!value) {
|
||||
value = '无过滤'
|
||||
}
|
||||
return { text: value, value }
|
||||
})
|
||||
userFilters = Object.keys(Object.assign(userObj)).map(value => { return { text: value, value } })
|
||||
const columns = [{
|
||||
title: '期望名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
render: text => <a href="#">{text}</a>
|
||||
key: 'name'
|
||||
}, {
|
||||
title: 'ip',
|
||||
dataIndex: 'ip',
|
||||
key: 'ip'
|
||||
key: 'ip',
|
||||
render: (text, recode) => {
|
||||
if (!recode.ip_enable) {
|
||||
text = '';
|
||||
}
|
||||
return text;
|
||||
},
|
||||
onFilter: (value, record) => (record.ip === value && record.ip_enable) || (value === '无过滤' && !record.ip_enable),
|
||||
filters: ipFilters
|
||||
}, {
|
||||
title: '创建人',
|
||||
dataIndex: 'username',
|
||||
key: 'username'
|
||||
key: 'username',
|
||||
onFilter: (value, record) => record.username === value,
|
||||
filters: userFilters
|
||||
}, {
|
||||
title: '编辑时间',
|
||||
key: 'action',
|
||||
dataIndex: 'up_time',
|
||||
key: 'up_time',
|
||||
render: text => formatTime(text)
|
||||
}, {
|
||||
title: '操作',
|
||||
dataIndex: 'address',
|
||||
key: 'address'
|
||||
dataIndex: '_id',
|
||||
key: '_id',
|
||||
render: (_id, recode) => {
|
||||
return (
|
||||
<div>
|
||||
<span style={{marginRight: 5}}>
|
||||
<Button size="small" onClick={() => this.setState({
|
||||
isAdd: false,
|
||||
caseDesModalVisible: true,
|
||||
caseData: recode
|
||||
})}>编辑</Button>
|
||||
</span>
|
||||
<span>
|
||||
<Popconfirm
|
||||
title="你确定要删除这条期望?"
|
||||
onConfirm={() => this.deleteCase(_id)}
|
||||
okText="确定"
|
||||
cancelText="取消"
|
||||
>
|
||||
<Button size="small" onClick={() => {}}>删除</Button>
|
||||
</Popconfirm>
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}];
|
||||
|
||||
return (
|
||||
<div style={{ padding: '20px 10px' }}>
|
||||
<Button type="primary" onClick={() => this.setState({isAdd: true, caseDesModalVisible: true})}>添加期望</Button>
|
||||
<Table columns={columns} dataSource={data} />
|
||||
<div>
|
||||
<div style={{marginBottom: 8}}>
|
||||
<Button type="primary" onClick={() => this.setState({
|
||||
isAdd: true,
|
||||
caseDesModalVisible: true,
|
||||
caseData: initCaseData
|
||||
})}>添加期望</Button>
|
||||
<a target="_blank" rel="noopener noreferrer" href={constants.docHref.adv_mock_case} style={{marginLeft: 8}} >
|
||||
<Tooltip title="点击查看文档"><Icon type="question-circle-o" /></Tooltip>
|
||||
</a>
|
||||
</div>
|
||||
<Table columns={columns} dataSource={data} pagination={false} rowKey='_id' />
|
||||
<CaseDesModal
|
||||
visible={caseDesModalVisible}
|
||||
isAdd={isAdd}
|
||||
|
30
exts/yapi-plugin-advanced-mock/MockCol/mockColReducer.js
Normal file
@ -0,0 +1,30 @@
|
||||
import axios from 'axios'
|
||||
|
||||
// Actions
|
||||
const FETCH_MOCK_COL = 'yapi/mockCol/FETCH_MOCK_COL';
|
||||
|
||||
// Reducer
|
||||
const initialState = {
|
||||
list: []
|
||||
}
|
||||
|
||||
export default (state = initialState, action) => {
|
||||
switch (action.type) {
|
||||
case FETCH_MOCK_COL:
|
||||
return {
|
||||
...state,
|
||||
list: action.payload.data
|
||||
}
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
// Action Creators
|
||||
export async function fetchMockCol(interfaceId) {
|
||||
let result = await axios.get('/api/plugin/advmock/case/list?interface_id=' + interfaceId);
|
||||
return {
|
||||
type: FETCH_MOCK_COL,
|
||||
payload: result.data
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
import AdvMock from './MockCol/MockCol.js'
|
||||
import AdvMock from './AdvMock'
|
||||
import mockCol from './MockCol/mockColReducer.js'
|
||||
|
||||
module.exports = function(){
|
||||
this.bindHook('interface_tab', function(tabs){
|
||||
@ -7,4 +8,7 @@ module.exports = function(){
|
||||
component: AdvMock
|
||||
}
|
||||
})
|
||||
this.bindHook('add_reducer', function(reducerModules){
|
||||
reducerModules.mockCol = mockCol;
|
||||
})
|
||||
}
|
@ -2,6 +2,7 @@ const baseController = require('controllers/base.js');
|
||||
const advModel = require('./advMockModel.js');
|
||||
const yapi = require('yapi.js');
|
||||
const caseModel = require('./caseModel.js');
|
||||
const userModel = require('../../server/models/user.js');
|
||||
const config = require('./index.js');
|
||||
|
||||
class advMockController extends baseController{
|
||||
@ -9,6 +10,7 @@ class advMockController extends baseController{
|
||||
super(ctx);
|
||||
this.Model = yapi.getInst(advModel);
|
||||
this.caseModel = yapi.getInst(caseModel);
|
||||
this.userModel = yapi.getInst(userModel);
|
||||
}
|
||||
|
||||
async getMock(ctx){
|
||||
@ -56,6 +58,11 @@ class advMockController extends baseController{
|
||||
return ctx.body = yapi.commons.resReturn(null, 400, '缺少 interface_id');
|
||||
}
|
||||
let result = await this.caseModel.list(id);
|
||||
for(let i = 0, len = result.length; i < len; i++) {
|
||||
let userinfo = await this.userModel.findById(result[i].uid);
|
||||
result[i] = result[i].toObject();
|
||||
result[i].username = userinfo.username;
|
||||
}
|
||||
|
||||
ctx.body = yapi.commons.resReturn(result);
|
||||
}
|
||||
@ -68,6 +75,7 @@ class advMockController extends baseController{
|
||||
let result = await this.caseModel.get({
|
||||
_id: id
|
||||
})
|
||||
|
||||
ctx.body = yapi.commons.resReturn(result);
|
||||
}
|
||||
|
||||
@ -107,10 +115,12 @@ class advMockController extends baseController{
|
||||
ip_enable: data.ip_enable
|
||||
}
|
||||
|
||||
if(data.params && typeof data.params === 'object'){
|
||||
if(data.params && typeof data.params === 'object' && Object.keys(data.params).length >0){
|
||||
for(let i in data.params){
|
||||
findRepeatParams['params.' + i] = data.params[i];
|
||||
}
|
||||
}else{
|
||||
findRepeatParams.params = null;
|
||||
}
|
||||
|
||||
if(data.ip_enable){
|
||||
@ -118,8 +128,7 @@ class advMockController extends baseController{
|
||||
}
|
||||
|
||||
findRepeat = await this.caseModel.get(findRepeatParams);
|
||||
|
||||
if(findRepeat){
|
||||
if(findRepeat && findRepeat._id !== params.id){
|
||||
return ctx.body = yapi.commons.resReturn(null,400, '已存在的期望');
|
||||
}
|
||||
|
||||
@ -138,7 +147,8 @@ class advMockController extends baseController{
|
||||
if(!id){
|
||||
return ctx.body =yapi.commons.resReturn(null, 408, '缺少 id');
|
||||
}
|
||||
ctx.body = await this.caseModel.del(id);
|
||||
let result = await this.caseModel.del(id);
|
||||
return ctx.body = yapi.commons.resReturn(result);
|
||||
}
|
||||
|
||||
|
||||
|
@ -4,6 +4,9 @@ const caseModel = require('./caseModel.js');
|
||||
const yapi = require('yapi.js');
|
||||
const mongoose = require('mongoose');
|
||||
const _ = require('underscore');
|
||||
const path = require('path');
|
||||
const lib = require(path.resolve(yapi.WEBROOT, 'common/lib.js' ));
|
||||
const Mock = require('mockjs');
|
||||
|
||||
function arrToObj(arr){
|
||||
let obj = {};
|
||||
@ -45,7 +48,7 @@ module.exports = function(){
|
||||
let matchList = [];
|
||||
listWithIp.forEach(item=>{
|
||||
let params = item.params;
|
||||
if(_.isMatch(reqParams, params)){
|
||||
if(lib.isDeepMatch(reqParams, params)){
|
||||
matchList.push(item);
|
||||
}
|
||||
})
|
||||
@ -56,7 +59,7 @@ module.exports = function(){
|
||||
}).select('_id params')
|
||||
list.forEach(item=>{
|
||||
let params = item.params;
|
||||
if(_.isMatch(reqParams, item.params)){
|
||||
if(lib.isDeepMatch(reqParams, item.params)){
|
||||
matchList.push(item);
|
||||
}
|
||||
})
|
||||
@ -152,7 +155,7 @@ module.exports = function(){
|
||||
let caseData = await checkCase(context.ctx, interfaceId);
|
||||
if(caseData){
|
||||
let data = await handleByCase(caseData, context);
|
||||
context.mockJson = data.res_body;
|
||||
context.mockJson = yapi.commons.json_parse(data.res_body);
|
||||
context.resHeader = arrToObj(data.headers);
|
||||
context.httpCode = data.code;
|
||||
context.delay = data.delay;
|
||||
@ -172,7 +175,8 @@ module.exports = function(){
|
||||
params: Object.assign({}, context.ctx.query, context.ctx.request.body),
|
||||
resHeader: context.resHeader,
|
||||
httpCode: context.httpCode,
|
||||
delay: context.httpCode
|
||||
delay: context.httpCode,
|
||||
Random: Mock.Random
|
||||
}
|
||||
sandbox.cookie = {};
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "yapi",
|
||||
"version": "1.1.2",
|
||||
"version": "1.2.0",
|
||||
"description": "YAPI",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
@ -39,7 +39,7 @@
|
||||
"koa-websocket": "^4.0.0",
|
||||
"mockjs": "^1.0.1-beta3",
|
||||
"moment": "^2.18.1",
|
||||
"mongoose": "^4.12.3",
|
||||
"mongoose": "4.7.0",
|
||||
"mongoose-auto-increment": "^5.0.1",
|
||||
"nodemailer": "^4.0.1",
|
||||
"ora": "^1.3.0",
|
||||
|
@ -36,7 +36,7 @@ class interfaceController extends baseController {
|
||||
* @param {Boolean} [req_headers[].required] 是否是必须,默认为否
|
||||
* @param {String} [req_headers[].desc] header描述
|
||||
* @param {String} [req_body_type] 请求参数方式,有["form", "json", "text", "xml"]四种
|
||||
* @param {Array} [req_params] name, desc两个参数
|
||||
* @param {Array} [req_params] 路径参数 name, desc两个参数
|
||||
* @param {Mixed} [req_body_form] 请求参数,如果请求方式是form,参数是Array数组,其他格式请求参数是字符串
|
||||
* @param {String} [req_body_form[].name] 请求参数名
|
||||
* @param {String} [req_body_form[].value] 请求参数值,可填写生成规则(mock)。如@email,随机生成一条email
|
||||
|
@ -154,7 +154,9 @@ class interfaceColController extends baseController{
|
||||
resultList = resultList.sort((a,b)=>{
|
||||
return a.index - b.index;
|
||||
});
|
||||
ctx.body = yapi.commons.resReturn(resultList);
|
||||
let ctxBody = yapi.commons.resReturn(resultList);
|
||||
ctxBody.colData = colData;
|
||||
ctx.body = ctxBody;
|
||||
} catch (e) {
|
||||
ctx.body = yapi.commons.resReturn(null, 402, e.message);
|
||||
}
|
||||
@ -334,10 +336,6 @@ class interfaceColController extends baseController{
|
||||
return ctx.body = yapi.commons.resReturn(null, 400, '用例id不能为空');
|
||||
}
|
||||
|
||||
// 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');
|
||||
if (!auth) {
|
||||
@ -346,9 +344,9 @@ class interfaceColController extends baseController{
|
||||
|
||||
params.uid = this.getUid();
|
||||
|
||||
//不允许修改接口id和项目id
|
||||
delete params.interface_id;
|
||||
delete params.project_id;
|
||||
// delete params.col_id;
|
||||
|
||||
let result = await this.caseModel.up(params.id, params);
|
||||
let username = this.getUsername();
|
||||
@ -454,20 +452,22 @@ class interfaceColController extends baseController{
|
||||
try{
|
||||
let params = ctx.request.body;
|
||||
let id = params.col_id;
|
||||
if(!id){
|
||||
return ctx.body = yapi.commons.resReturn(null, 400, '缺少 col_id 参数');
|
||||
}
|
||||
let colData = await this.colModel.get(id);
|
||||
if(!colData){
|
||||
return ctx.body = yapi.commons.resReturn(null, 400, '不存在');
|
||||
}
|
||||
let auth = await this.checkAuth(colData.project_id, 'project', 'edit')
|
||||
if (!auth) {
|
||||
return ctx.body = yapi.commons.resReturn(null, 400, '没有权限');
|
||||
}
|
||||
|
||||
let result = await this.colModel.up(params.col_id, {
|
||||
name: params.name,
|
||||
desc: params.desc,
|
||||
up_time: yapi.commons.time()
|
||||
});
|
||||
delete params.col_id;
|
||||
let result = await this.colModel.up(id, params);
|
||||
let username = this.getUsername();
|
||||
yapi.commons.saveLog({
|
||||
contnet: `<a href="/user/profile/${this.getUid()}">${username}</a> 更新了接口集 <a href="/project/${colData.project_id}/interface/col/${params.col_id}">${params.name}</a> 的信息`,
|
||||
content: `<a href="/user/profile/${this.getUid()}">${username}</a> 更新了接口集 <a href="/project/${colData.project_id}/interface/col/${id}">${colData.name}</a> 的信息`,
|
||||
type: 'project',
|
||||
uid: this.getUid(),
|
||||
username: username,
|
||||
@ -595,6 +595,28 @@ class interfaceColController extends baseController{
|
||||
}
|
||||
}
|
||||
|
||||
async runCaseScript(ctx){
|
||||
let params = ctx.request.body;
|
||||
let script = params.script;
|
||||
if(!script){
|
||||
return ctx.body = yapi.commons.resReturn('ok');
|
||||
}
|
||||
|
||||
try{
|
||||
let a = yapi.commons.sandbox({
|
||||
assert: require('assert'),
|
||||
status: params.response.status,
|
||||
body: params.response.body,
|
||||
header: params.response.header,
|
||||
records: params.records
|
||||
}, script);
|
||||
return ctx.body = yapi.commons.resReturn('ok');
|
||||
}catch(err){
|
||||
let errArr = err.stack.split("\n");
|
||||
return ctx.body = yapi.commons.resReturn(errArr, 400, err.message)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -32,10 +32,10 @@ class interfaceCase extends baseModel {
|
||||
req_body_other: String,
|
||||
test_res_body: String,
|
||||
test_status: {type: String, enum: ['ok', 'invalid', 'error', '']},
|
||||
test_report: [],
|
||||
test_res_header: Schema.Types.Mixed,
|
||||
mock_verify: {type: Boolean, default: false}
|
||||
|
||||
mock_verify: {type: Boolean, default: false},
|
||||
enable_script: {type: Boolean, default: false},
|
||||
test_script: String
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -13,10 +13,11 @@ class interfaceCol extends baseModel {
|
||||
project_id: { type: Number, required: true },
|
||||
desc: String,
|
||||
add_time: Number,
|
||||
up_time: Number
|
||||
up_time: Number,
|
||||
test_report: {type:String, default: '{}'}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
save(data) {
|
||||
let m = new this.model(data);
|
||||
return m.save();
|
||||
@ -37,7 +38,7 @@ class interfaceCol extends baseModel {
|
||||
list(project_id) {
|
||||
return this.model.find({
|
||||
project_id: project_id
|
||||
}).exec();
|
||||
}).select('name uid project_id desc add_time up_time').exec();
|
||||
}
|
||||
|
||||
del(id) {
|
||||
|
@ -366,6 +366,10 @@ let routerConfig = {
|
||||
action: "delCase",
|
||||
path: "del_case",
|
||||
method: "get"
|
||||
},{
|
||||
action: "runCaseScript",
|
||||
path: "run_script",
|
||||
method: "post"
|
||||
}
|
||||
],
|
||||
"test": [{
|
||||
|
256
static/doc/adv_mock.html
Normal file
@ -0,0 +1,256 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no" />
|
||||
<meta name="format-detection" content="telephone=no,email=no" />
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge, chrome=1">
|
||||
<meta name="description" content="description of your site">
|
||||
<meta name="author" content="author of the site">
|
||||
<title>YApi 使用手册</title>
|
||||
<link rel="stylesheet" href="source/main.css" />
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" href="styles/theme.css" />
|
||||
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<div class="ydoc">
|
||||
<header class="ydoc-header">
|
||||
<div class="ydoc-header-area">
|
||||
|
||||
<a href="http://ued.qunar.com/ymfe/" class="navbar-brand">YMFE</a>
|
||||
|
||||
<button class="ydocIcon navbar-toggle"></button>
|
||||
<nav class="ydoc-nav">
|
||||
<ul class="navbar-left">
|
||||
|
||||
<li class="active">
|
||||
|
||||
<a href="index.html">使用手册</a>
|
||||
</li>
|
||||
|
||||
<li class="">
|
||||
|
||||
<a href="devops.html">内网部署</a>
|
||||
</li>
|
||||
|
||||
<li class="">
|
||||
|
||||
<a href="plugin.html">插件Wiki</a>
|
||||
</li>
|
||||
|
||||
<li class="">
|
||||
|
||||
<a href="qa.html">常见问题</a>
|
||||
</li>
|
||||
|
||||
<li class="">
|
||||
|
||||
<a href="releases.html">版本记录</a>
|
||||
</li>
|
||||
|
||||
<li class="">
|
||||
|
||||
<a href="http://yapi.demo.qunar.com/" target="_blank">demo站点</a>
|
||||
</li>
|
||||
|
||||
<li class="">
|
||||
|
||||
<a href="api.html"></a>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- <header style="height:20px"></header> -->
|
||||
|
||||
<!-- Docs page layout -->
|
||||
|
||||
<div class="ydoc-banner-bg">
|
||||
<div class="ydoc-banner ">
|
||||
<div class="ydoc-banner-area">
|
||||
<h1 >YApi</h1>
|
||||
<p class="desc ">高效、易用、功能强大的api管理平台,旨在为开发、产品、测试人员提供更优雅的接口管理服务。</p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="ydoc-container">
|
||||
|
||||
<div class="ydoc-container-content ">
|
||||
|
||||
|
||||
<div class="content-left staticsidenav" role="complementary">
|
||||
<nav class="docs-sidebar hidden-print hidden-xs hidden-sm">
|
||||
<ul class="nav docs-sidenav">
|
||||
|
||||
<!-- <li > -->
|
||||
<li >
|
||||
|
||||
<a href="getfamiliar.html">认识 YApi</a>
|
||||
</li>
|
||||
|
||||
<!-- <li > -->
|
||||
<li >
|
||||
|
||||
<a href="quickstart.html">创建第一个API</a>
|
||||
</li>
|
||||
|
||||
<!-- <li > -->
|
||||
<li >
|
||||
|
||||
<a href="manage.html">管理分组与项目</a>
|
||||
</li>
|
||||
|
||||
<!-- <li > -->
|
||||
<li >
|
||||
|
||||
<a href="project.html">项目操作</a>
|
||||
</li>
|
||||
|
||||
<!-- <li > -->
|
||||
<li >
|
||||
|
||||
<a href="interface.html">接口操作</a>
|
||||
</li>
|
||||
|
||||
<!-- <li > -->
|
||||
<li >
|
||||
|
||||
<a href="mock.html">普通 Mock</a>
|
||||
</li>
|
||||
|
||||
<!-- <li class="active" class="active" > -->
|
||||
<li class="active" >
|
||||
|
||||
<a href="adv_mock.html">高级 Mock</a>
|
||||
</li>
|
||||
|
||||
<ul class="nav docs-sidenav-extend" >
|
||||
|
||||
<li >
|
||||
<a href="#Mock_期望">Mock 期望</a>
|
||||
</li>
|
||||
|
||||
<li >
|
||||
<a href="#自定义_Mock_脚本">自定义 Mock 脚本</a>
|
||||
</li>
|
||||
|
||||
<li >
|
||||
<a href="#Mock_优先级说明">Mock 优先级说明</a>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
|
||||
<!-- <li > -->
|
||||
<li >
|
||||
|
||||
<a href="case.html">使用测试集</a>
|
||||
</li>
|
||||
|
||||
<!-- <li > -->
|
||||
<li >
|
||||
|
||||
<a href="data.html">数据导入</a>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div class="content-right markdown-body use-sidebar" role="main">
|
||||
|
||||
<h2 class="subject" id="Mock_期望">Mock 期望 <a class="hashlink" href="#Mock_期望">#</a></h2><p>在测试时,很多时候需要根据不同的请求参数、IP 返回不同的 HTTP Code、HTTP 头和 JSON 数据。</p>
|
||||
<p>Mock 期望就是根据设置的请求过滤规则,返回期望数据。</p>
|
||||
<h3 class="subject" id="使用方法">使用方法 <a class="hashlink" href="#使用方法">#</a></h3><ol>
|
||||
<li>进入接口详情页,点击『高级 Mock』选项。<div class="doc-img-wrapper"><img class="doc-img-r" src="./images/usage/adv-mock-case1.png"/></div></li><li>点击『添加期望』,填写过滤规则以及期望返回数据,点击『确定』保存。<div class="doc-img-wrapper"><img class="doc-img-r" src="./images/usage/adv-mock-case3.png"/></div>
|
||||
<div class="doc-img-wrapper"><img class="doc-img-r" src="./images/usage/adv-mock-case4.png"/></div></li><li>然后尝试在浏览器里发送符合规则的请求,查看返回的数据是否符合期望。<div class="doc-img-wrapper"><img class="doc-img-r" src="./images/usage/adv-mock-case5.png"/></div>
|
||||
|
||||
</li></ol>
|
||||
<h3 class="subject" id="期望填写">期望填写 <a class="hashlink" href="#期望填写">#</a></h3><ul>
|
||||
<li>期望名称:给此条期望命名</li></ul>
|
||||
<p>请求</p>
|
||||
<ul>
|
||||
<li>IP 过滤:请求的 IP 是设置的地址才可能返回期望。默认 IP 过滤关闭,任何 IP 地址都可能返回期望。</li><li>参数过滤:请求必须包含设置的参数,并且值相等才可能返回期望。参数可以在 Body 或 Query 中。</li></ul>
|
||||
<p>响应</p>
|
||||
<ul>
|
||||
<li>HTTP Code:期望响应的 HTTP 状态码</li><li>延时:期望响应的延迟时间</li><li>HTTP 头:期望响应带有的 HTTP 头</li><li>返回 JSON:期望返回的 JSON 数据</li></ul>
|
||||
<h2 class="subject" id="自定义_Mock_脚本">自定义 Mock 脚本 <a class="hashlink" href="#自定义_Mock_脚本">#</a></h2><p>在前端开发阶段,对于某些接口,业务相对复杂,而 UI 端也需要根据接口返回的不同内容去做相应的处理。</p>
|
||||
<p>YApi 提供了写 JS 脚本方式处理这一问题,可以根据用户请求的参数修改返回内容。</p>
|
||||
<h3 class="subject" id="全局变量">全局变量 <a class="hashlink" href="#全局变量">#</a></h3><p>请求</p>
|
||||
<ul>
|
||||
<li><code>header</code> 请求的 HTTP 头</li><li><code>params</code> 请求参数,包括 Body、Query 中所有参数</li><li><code>cookie</code> 请求带的 Cookies</li></ul>
|
||||
<p>响应</p>
|
||||
<ul>
|
||||
<li><p><code>mockJson</code>
|
||||
接口定义的响应数据 Mock 模板</p>
|
||||
</li><li><p><code>resHeader</code>
|
||||
响应的 HTTP 头</p>
|
||||
</li><li><p><code>httpCode</code>
|
||||
响应的 HTTP 状态码</p>
|
||||
</li><li><p><code>delay</code>
|
||||
Mock 响应延时,单位为 ms</p>
|
||||
</li><li><p><code>Random</code>
|
||||
Mock.Random 方法,详细使用方法请查看 <a href="https://github.com/nuysoft/Mock/wiki/Mock.Random">Wiki</a></p>
|
||||
</li></ul>
|
||||
<h3 class="subject" id="使用方法">使用方法 <a class="hashlink" href="#使用方法">#</a></h3><ol>
|
||||
<li>首先开启此功能</li><li>Mock 脚本就是用 JavaScript 对 <code>mockJson</code> 变量修改,请避免被全局变量(httpCode, resHeader, delay)的修改</li></ol>
|
||||
<h3 class="subject" id="示例1,_根据请求参数重写_mockJson">示例1, 根据请求参数重写 mockJson <a class="hashlink" href="#示例1,_根据请求参数重写_mockJson">#</a></h3><pre><code>if(params.type == <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">{</span>
|
||||
mockJson.errcode = <span class="token number">400</span><span class="token punctuation">;</span>
|
||||
mockJson.errmsg = 'error<span class="token punctuation">;</span>
|
||||
<span class="token punctuation">}</span>
|
||||
|
||||
if(header.token == 't'<span class="token punctuation">)</span><span class="token punctuation">{</span>
|
||||
mockJson.errcode = <span class="token number">300</span><span class="token punctuation">;</span>
|
||||
mockJson.errmsg = 'error<span class="token punctuation">;</span>
|
||||
<span class="token punctuation">}</span>
|
||||
|
||||
if(cookie.type == 'a'<span class="token punctuation">)</span><span class="token punctuation">{</span>
|
||||
mockJson.errcode = <span class="token number">500</span><span class="token punctuation">;</span>
|
||||
mockJson.errmsg = 'error<span class="token punctuation">;</span>
|
||||
<span class="token punctuation">}</span>
|
||||
</code></pre><h3 class="subject" id="示例2,_生成高度自定义数据内容">示例2, 生成高度自定义数据内容 <a class="hashlink" href="#示例2,_生成高度自定义数据内容">#</a></h3><pre><code>var a = <span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">,</span><span class="token number">1</span><span class="token punctuation">,</span><span class="token number">1</span><span class="token punctuation">,</span><span class="token number">1</span><span class="token punctuation">,</span><span class="token number">1</span><span class="token punctuation">,</span><span class="token number">1</span><span class="token punctuation">,</span><span class="token number">1</span><span class="token punctuation">,</span><span class="token number">1</span><span class="token punctuation">,</span><span class="token number">1</span><span class="token punctuation">,</span><span class="token number">1</span><span class="token punctuation">]</span>
|
||||
|
||||
mockJson = <span class="token punctuation">{</span>
|
||||
errcode<span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">,</span>
|
||||
email<span class="token operator">:</span> Random.email('qq.com'<span class="token punctuation">)</span><span class="token punctuation">,</span>
|
||||
data<span class="token operator">:</span> a.map(function(item<span class="token punctuation">)</span><span class="token punctuation">{</span>
|
||||
return Random.city(<span class="token punctuation">)</span> + '银行'
|
||||
<span class="token punctuation">}</span><span class="token punctuation">)</span>
|
||||
<span class="token punctuation">}</span>
|
||||
</code></pre><h2 class="subject" id="Mock_优先级说明">Mock 优先级说明 <a class="hashlink" href="#Mock_优先级说明">#</a></h2><p>请求 Mock 数据时,规则匹配优先级:Mock 期望 > 自定义 Mock 脚本 > 普通 Mock。</p>
|
||||
<p>如果前面匹配到 Mock 数据,后面 Mock 则不返回。</p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<footer class="footer">
|
||||
<div class="copyright">
|
||||
© 2016 <a href="http://ued.qunar.com/ymfe/">YMFE</a> Team. Build by <a href="http://ued.qunar.com/ydoc/">ydoc</a>.
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="open-panel"></div>
|
||||
<div class="mask"></div>
|
||||
|
||||
|
||||
<script src="source/main.js"></script>
|
||||
<script src="source/app.js"></script>
|
||||
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
@ -642,7 +642,7 @@
|
||||
|
||||
<p>
|
||||
<small class="text-muted">源码位置:</small>
|
||||
<a href="./static/server/controllers/group.js.html#227" target="_blank">./server/controllers/group.js:227</a>
|
||||
<a href="./static/server/controllers/group.js.html#213" target="_blank">./server/controllers/group.js:213</a>
|
||||
</p>
|
||||
|
||||
|
||||
@ -728,7 +728,7 @@
|
||||
|
||||
<p>
|
||||
<small class="text-muted">源码位置:</small>
|
||||
<a href="./static/server/controllers/group.js.html#279" target="_blank">./server/controllers/group.js:279</a>
|
||||
<a href="./static/server/controllers/group.js.html#265" target="_blank">./server/controllers/group.js:265</a>
|
||||
</p>
|
||||
|
||||
|
||||
@ -790,7 +790,7 @@
|
||||
|
||||
<p>
|
||||
<small class="text-muted">源码位置:</small>
|
||||
<a href="./static/server/controllers/group.js.html#305" target="_blank">./server/controllers/group.js:305</a>
|
||||
<a href="./static/server/controllers/group.js.html#291" target="_blank">./server/controllers/group.js:291</a>
|
||||
</p>
|
||||
|
||||
|
||||
@ -864,7 +864,7 @@
|
||||
|
||||
<p>
|
||||
<small class="text-muted">源码位置:</small>
|
||||
<a href="./static/server/controllers/group.js.html#356" target="_blank">./server/controllers/group.js:356</a>
|
||||
<a href="./static/server/controllers/group.js.html#342" target="_blank">./server/controllers/group.js:342</a>
|
||||
</p>
|
||||
|
||||
|
||||
@ -926,7 +926,7 @@
|
||||
|
||||
<p>
|
||||
<small class="text-muted">源码位置:</small>
|
||||
<a href="./static/server/controllers/group.js.html#418" target="_blank">./server/controllers/group.js:418</a>
|
||||
<a href="./static/server/controllers/group.js.html#404" target="_blank">./server/controllers/group.js:404</a>
|
||||
</p>
|
||||
|
||||
|
||||
@ -998,7 +998,7 @@
|
||||
|
||||
<p>
|
||||
<small class="text-muted">源码位置:</small>
|
||||
<a href="./static/server/controllers/group.js.html#458" target="_blank">./server/controllers/group.js:458</a>
|
||||
<a href="./static/server/controllers/group.js.html#448" target="_blank">./server/controllers/group.js:448</a>
|
||||
</p>
|
||||
|
||||
|
||||
@ -3379,7 +3379,7 @@
|
||||
<tr>
|
||||
<td>req_params</td>
|
||||
<td>Array</td>
|
||||
<td>name, desc两个参数</td>
|
||||
<td>路径参数 name, desc两个参数</td>
|
||||
<td>
|
||||
|
||||
</td>
|
||||
@ -3694,7 +3694,7 @@
|
||||
|
||||
<p>
|
||||
<small class="text-muted">源码位置:</small>
|
||||
<a href="./static/server/controllers/interface.js.html#326" target="_blank">./server/controllers/interface.js:326</a>
|
||||
<a href="./static/server/controllers/interface.js.html#329" target="_blank">./server/controllers/interface.js:329</a>
|
||||
</p>
|
||||
|
||||
|
||||
@ -3927,7 +3927,7 @@
|
||||
|
||||
<p>
|
||||
<small class="text-muted">源码位置:</small>
|
||||
<a href="./static/server/controllers/interface.js.html#530" target="_blank">./server/controllers/interface.js:530</a>
|
||||
<a href="./static/server/controllers/interface.js.html#533" target="_blank">./server/controllers/interface.js:533</a>
|
||||
</p>
|
||||
|
||||
|
||||
@ -3999,7 +3999,7 @@
|
||||
|
||||
<p>
|
||||
<small class="text-muted">源码位置:</small>
|
||||
<a href="./static/server/controllers/interface.js.html#729" target="_blank">./server/controllers/interface.js:729</a>
|
||||
<a href="./static/server/controllers/interface.js.html#732" target="_blank">./server/controllers/interface.js:732</a>
|
||||
</p>
|
||||
|
||||
|
||||
@ -4507,7 +4507,7 @@
|
||||
|
||||
<p>
|
||||
<small class="text-muted">源码位置:</small>
|
||||
<a href="./static/server/controllers/interfaceCol.js.html#162" target="_blank">./server/controllers/interfaceCol.js:162</a>
|
||||
<a href="./static/server/controllers/interfaceCol.js.html#164" target="_blank">./server/controllers/interfaceCol.js:164</a>
|
||||
</p>
|
||||
|
||||
|
||||
@ -4689,7 +4689,7 @@
|
||||
|
||||
<p>
|
||||
<small class="text-muted">源码位置:</small>
|
||||
<a href="./static/server/controllers/interfaceCol.js.html#304" target="_blank">./server/controllers/interfaceCol.js:304</a>
|
||||
<a href="./static/server/controllers/interfaceCol.js.html#306" target="_blank">./server/controllers/interfaceCol.js:306</a>
|
||||
</p>
|
||||
|
||||
|
||||
@ -4859,7 +4859,7 @@
|
||||
|
||||
<p>
|
||||
<small class="text-muted">源码位置:</small>
|
||||
<a href="./static/server/controllers/interfaceCol.js.html#373" target="_blank">./server/controllers/interfaceCol.js:373</a>
|
||||
<a href="./static/server/controllers/interfaceCol.js.html#371" target="_blank">./server/controllers/interfaceCol.js:371</a>
|
||||
</p>
|
||||
|
||||
|
||||
@ -4921,7 +4921,7 @@
|
||||
|
||||
<p>
|
||||
<small class="text-muted">源码位置:</small>
|
||||
<a href="./static/server/controllers/interfaceCol.js.html#440" target="_blank">./server/controllers/interfaceCol.js:440</a>
|
||||
<a href="./static/server/controllers/interfaceCol.js.html#438" target="_blank">./server/controllers/interfaceCol.js:438</a>
|
||||
</p>
|
||||
|
||||
|
||||
|
@ -124,7 +124,13 @@
|
||||
<!-- <li > -->
|
||||
<li >
|
||||
|
||||
<a href="mock.html">使用mock功能</a>
|
||||
<a href="mock.html">普通 Mock</a>
|
||||
</li>
|
||||
|
||||
<!-- <li > -->
|
||||
<li >
|
||||
|
||||
<a href="adv_mock.html">高级 Mock</a>
|
||||
</li>
|
||||
|
||||
<!-- <li class="active" class="active" > -->
|
||||
@ -161,9 +167,9 @@
|
||||
|
||||
<div class="content-right markdown-body use-sidebar" role="main">
|
||||
|
||||
<h2 class="subject" id="介绍">介绍 <a class="hashlink" href="#介绍">#</a></h2><p style='text-indent:2em;line-height:1.8em'>在平时的开发过程中,经常遇到的一个问题是每次调试接口都需要重新填写参数,YApi测试集可以保存之前填写的参数,方便下次的调试。YApi测试集还可以一次性测试所有接口,每个的请求参数可以通过前面已请求的接口数据读取,或填写mock随机字符串。</p>
|
||||
<h2 class="subject" id="介绍">介绍 <a class="hashlink" href="#介绍">#</a></h2><p style='text-indent:2em;line-height:1.8em'>在平时的开发过程中,经常遇到的一个问题是每次调试接口都需要重新填写参数,YApi测试集不但能够保存之前填写的参数,方便下次的调试,还可以一次性测试所有接口,每个的请求参数可以通过前面已请求的接口数据读取,或填写mock随机字符串,通过设置断言脚本验证返回数据的正确性,</p>
|
||||
|
||||
<h2 class="subject" id="测试列表">测试列表 <a class="hashlink" href="#测试列表">#</a></h2><p><img class="doc-img" style="width:100%" src="./images/usage/case-list.jpg" /></p>
|
||||
<h2 class="subject" id="测试列表">测试列表 <a class="hashlink" href="#测试列表">#</a></h2><p><img class="doc-img" style="width: 618px;" src="./images/usage/case-list.gif" /></p>
|
||||
<p>在测试列表可以看到每个测试用例的 key,还有 开始测试、报告等功能</p>
|
||||
<p>点击开始测试会按照 case 定义的参数从上往下一个一个进行测试,如果顺序有问题,可以拖动调整</p>
|
||||
<p>测试完成之后,点击报告查看该次请求的结果</p>
|
||||
@ -191,7 +197,22 @@
|
||||
<blockquote>
|
||||
<p>Tips: 上下拖动测试集合的列表项可以调整测试的顺序。</p>
|
||||
</blockquote>
|
||||
|
||||
<h3 class="subject" id="高级">高级 <a class="hashlink" href="#高级">#</a></h3><p>可通过写断言脚本,实现精准测试,支持 js 所有语法</p>
|
||||
<h4 class="subject" id="公共变量">公共变量 <a class="hashlink" href="#公共变量">#</a></h4><ul>
|
||||
<li><p>assert </p>
|
||||
<p>断言函数,详细 api可查看 <a target="_blank" href="https://nodejs.org/dist/latest-v8.x/docs/api/assert.html">document</a></p>
|
||||
</li><li><p>status</p>
|
||||
<p>http 状态码</p>
|
||||
</li><li><p>body </p>
|
||||
<p>返回 response body</p>
|
||||
</li><li><p>header </p>
|
||||
<p>返回 response header</p>
|
||||
</li><li><p>records </p>
|
||||
<p>记录的 http 请求信息,假设需要获取 key为555的接口参数或者响应数据,可通过 records[555].params 或 records[555].body 获取 </p>
|
||||
</li></ul>
|
||||
<h4 class="subject" id="示例">示例 <a class="hashlink" href="#示例">#</a></h4><pre><code>assert.equal(body.errcode<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span>
|
||||
assert.equal(body.data.group_name<span class="token punctuation">,</span> 'testGroup'<span class="token punctuation">)</span>
|
||||
</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -124,7 +124,13 @@
|
||||
<!-- <li > -->
|
||||
<li >
|
||||
|
||||
<a href="mock.html">使用mock功能</a>
|
||||
<a href="mock.html">普通 Mock</a>
|
||||
</li>
|
||||
|
||||
<!-- <li > -->
|
||||
<li >
|
||||
|
||||
<a href="adv_mock.html">高级 Mock</a>
|
||||
</li>
|
||||
|
||||
<!-- <li > -->
|
||||
|
@ -144,7 +144,13 @@
|
||||
<!-- <li > -->
|
||||
<li >
|
||||
|
||||
<a href="mock.html">使用mock功能</a>
|
||||
<a href="mock.html">普通 Mock</a>
|
||||
</li>
|
||||
|
||||
<!-- <li > -->
|
||||
<li >
|
||||
|
||||
<a href="adv_mock.html">高级 Mock</a>
|
||||
</li>
|
||||
|
||||
<!-- <li > -->
|
||||
|
BIN
static/doc/images/usage/adv-mock-case1.png
Normal file
After Width: | Height: | Size: 104 KiB |
BIN
static/doc/images/usage/adv-mock-case3.png
Normal file
After Width: | Height: | Size: 89 KiB |
BIN
static/doc/images/usage/adv-mock-case4.png
Normal file
After Width: | Height: | Size: 65 KiB |
BIN
static/doc/images/usage/adv-mock-case5.png
Normal file
After Width: | Height: | Size: 419 KiB |
BIN
static/doc/images/usage/case-list.gif
Normal file
After Width: | Height: | Size: 282 KiB |
@ -124,7 +124,13 @@
|
||||
<!-- <li > -->
|
||||
<li >
|
||||
|
||||
<a href="mock.html">使用mock功能</a>
|
||||
<a href="mock.html">普通 Mock</a>
|
||||
</li>
|
||||
|
||||
<!-- <li > -->
|
||||
<li >
|
||||
|
||||
<a href="adv_mock.html">高级 Mock</a>
|
||||
</li>
|
||||
|
||||
<!-- <li > -->
|
||||
|
@ -136,7 +136,13 @@
|
||||
<!-- <li > -->
|
||||
<li >
|
||||
|
||||
<a href="mock.html">使用mock功能</a>
|
||||
<a href="mock.html">普通 Mock</a>
|
||||
</li>
|
||||
|
||||
<!-- <li > -->
|
||||
<li >
|
||||
|
||||
<a href="adv_mock.html">高级 Mock</a>
|
||||
</li>
|
||||
|
||||
<!-- <li > -->
|
||||
|
@ -144,7 +144,13 @@
|
||||
<!-- <li > -->
|
||||
<li >
|
||||
|
||||
<a href="mock.html">使用mock功能</a>
|
||||
<a href="mock.html">普通 Mock</a>
|
||||
</li>
|
||||
|
||||
<!-- <li > -->
|
||||
<li >
|
||||
|
||||
<a href="adv_mock.html">高级 Mock</a>
|
||||
</li>
|
||||
|
||||
<!-- <li > -->
|
||||
|
@ -124,7 +124,7 @@
|
||||
<!-- <li class="active" class="active" > -->
|
||||
<li class="active" >
|
||||
|
||||
<a href="mock.html">使用mock功能</a>
|
||||
<a href="mock.html">普通 Mock</a>
|
||||
</li>
|
||||
|
||||
<ul class="nav docs-sidenav-extend" >
|
||||
@ -134,7 +134,7 @@
|
||||
</li>
|
||||
|
||||
<li >
|
||||
<a href="#定义mock数据示例">定义mock数据示例</a>
|
||||
<a href="#定义_mock_数据示例">定义 mock 数据示例</a>
|
||||
</li>
|
||||
|
||||
<li >
|
||||
@ -142,19 +142,21 @@
|
||||
</li>
|
||||
|
||||
<li >
|
||||
<a href="#如何使用Mock?">如何使用Mock?</a>
|
||||
<a href="#如何使用_Mock">如何使用 Mock</a>
|
||||
</li>
|
||||
|
||||
<li >
|
||||
<a href="#高级Mock">高级Mock</a>
|
||||
</li>
|
||||
|
||||
<li >
|
||||
<a href="#Mock语法规范">Mock语法规范</a>
|
||||
<a href="#Mock_语法规范">Mock 语法规范</a>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
|
||||
<!-- <li > -->
|
||||
<li >
|
||||
|
||||
<a href="adv_mock.html">高级 Mock</a>
|
||||
</li>
|
||||
|
||||
<!-- <li > -->
|
||||
<li >
|
||||
|
||||
@ -173,13 +175,15 @@
|
||||
|
||||
<div class="content-right markdown-body use-sidebar" role="main">
|
||||
|
||||
<h2 class="subject" id="Mock介绍">Mock介绍 <a class="hashlink" href="#Mock介绍">#</a></h2> <p style='text-indent:2em;line-height:1.8em'>YApi的Mock功能可以根据用户的输入接口信息如协议、URL、接口名、请求头、请求参数、mock规则(<a href="#mock">点击到Mock规则</a>)生成Mock接口,这些接口会自动生成模拟数据,创建者可以自由构造需要的数据。而且与常见的Mock方式如将Mock写在代码里和JS拦截等相比yapi的Mock在使用场景和效率和复杂度上是相差甚远的,正是由于yapi的Mock是一个第三方平台,那么在团队开发时任何人都可以权限许可下创建、修改接口信息等操作,这对于团队开发是很有好处的。 </p>
|
||||
<h2 class="subject" id="Mock介绍">Mock介绍 <a class="hashlink" href="#Mock介绍">#</a></h2> <p style='text-indent:2em;line-height:1.8em'>YApi的 Mock 功能可以根据用户的输入接口信息如协议、URL、接口名、请求头、请求参数、mock 规则(<a href="#mock">点击到 Mock 规则</a>)生成 Mock 接口,这些接口会自动生成模拟数据,创建者可以自由构造需要的数据。而且与常见的 Mock 方式如将 Mock 写在代码里和JS拦截等相比 yapi 的 Mock 在使用场景和效率和复杂度上是相差甚远的,正是由于 yapi 的 Mock 是一个第三方平台,那么在团队开发时任何人都可以权限许可下创建、修改接口信息等操作,这对于团队开发是很有好处的。 </p>
|
||||
|
||||
<p> <strong>mock地址解析</strong>:yapi平台网址+mock+<strong>您的项目id</strong>+<strong>接口实际请求path</strong></p>
|
||||
<p> 假设你 YApi 的部署地址为:<a href="http://yapi.xxx.com,然后后面的都可以用这个地址作为示例">http://yapi.xxx.com,然后后面的都可以用这个地址作为示例</a></p>
|
||||
<pre><code>mockd地址: http<span class="token operator">:</span>//yapi.xxx.com/mock/<span class="token number">29</span>/api/hackathon/login
|
||||
</code></pre><p> 注:项目id可以在项目设置里查看到</p>
|
||||
<h2 class="subject" id="定义mock数据示例">定义mock数据示例 <a class="hashlink" href="#定义mock数据示例">#</a></h2><pre><code><span class="token punctuation">{</span>
|
||||
<p> <strong>mock地址解析</strong>:<code>YApi平台网址 + mock + 您的项目id + 接口实际请求path</code></p>
|
||||
<p> 假设你 YApi 的部署地址为:<a href="http://yapi.xxx.com">http://yapi.xxx.com</a> 然后用这个地址作为示例</p>
|
||||
<pre><code>mockd 地址: http<span class="token operator">:</span>//yapi.xxx.com/mock/<span class="token number">29</span>/api/hackathon/login
|
||||
</code></pre><blockquote>
|
||||
<p>注:项目 id 可以在项目设置里查看到</p>
|
||||
</blockquote>
|
||||
<h2 class="subject" id="定义_mock_数据示例">定义 mock 数据示例 <a class="hashlink" href="#定义_mock_数据示例">#</a></h2><pre><code><span class="token punctuation">{</span>
|
||||
<span class="token property">"status|0-1"</span><span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">,</span> //接口状态
|
||||
<span class="token property">"message"</span><span class="token operator">:</span> <span class="token string">"请求完成"</span><span class="token punctuation">,</span> //消息提示
|
||||
<span class="token property">"data"</span><span class="token operator">:</span> <span class="token punctuation">{</span>
|
||||
@ -198,48 +202,41 @@
|
||||
<span class="token punctuation">}</span>
|
||||
<span class="token punctuation">]</span><span class="token punctuation">}</span>
|
||||
<span class="token punctuation">}</span>
|
||||
</code></pre><h2 class="subject" id="YApi-Mock_跟_Mockjs_区别">YApi-Mock 跟 Mockjs 区别 <a class="hashlink" href="#YApi-Mock_跟_Mockjs_区别">#</a></h2><p><a href="http://mockjs.com">Mockjs 官网</a></p>
|
||||
</code></pre><h2 class="subject" id="YApi-Mock_跟_Mockjs_区别">YApi-Mock 跟 Mockjs 区别 <a class="hashlink" href="#YApi-Mock_跟_Mockjs_区别">#</a></h2><p><a href="http://mockjs.com/examples.html">Mockjs 官网</a></p>
|
||||
<p>1 因为 yapi 基于 json 定义 mock ,无法使用 mockjs 原有的函数功能,正则表达式需要基于 rule 书写,示例如下:</p>
|
||||
<pre><code><span class="token punctuation">{</span>
|
||||
<span class="token property">"name|regexp"</span><span class="token operator">:</span> <span class="token string">"[a-z0-9_]+?"</span><span class="token punctuation">,</span>
|
||||
<span class="token property">"type|regexp"</span><span class="token operator">:</span> <span class="token string">"json|text|xml"</span> //枚举数据类型可这样实现
|
||||
<span class="token punctuation">}</span>
|
||||
</code></pre><p>2 支持替换请求的query,body参数</p>
|
||||
</code></pre><p>2 支持替换请求的 query, body 参数</p>
|
||||
<pre><code><span class="token punctuation">{</span>
|
||||
<span class="token property">"name"</span><span class="token operator">:</span> <span class="token string">"${query.name}"</span><span class="token punctuation">,</span> //请求的url是/path?name=xiaoming<span class="token punctuation">,</span> 返回的name字段是xiaoming
|
||||
<span class="token property">"type"</span><span class="token operator">:</span> <span class="token string">"${body.type}"</span> //请求的requestBody type=<span class="token number">1</span><span class="token punctuation">,</span>返回的type字段是<span class="token number">1</span>
|
||||
<span class="token punctuation">}</span>
|
||||
</code></pre><h2 class="subject" id="如何使用Mock?">如何使用Mock? <a class="hashlink" href="#如何使用Mock?">#</a></h2><h3 class="subject" id="1_在js代码直接请求yapi提供的mock地址(不用担心跨域问题)">1 在js代码直接请求yapi提供的mock地址(不用担心跨域问题) <a class="hashlink" href="#1_在js代码直接请求yapi提供的mock地址(不用担心跨域问题)">#</a></h3><p>在代码直接请求yapi提供的mock地址,以jQuery为例:</p>
|
||||
</code></pre><h2 class="subject" id="如何使用_Mock">如何使用 Mock <a class="hashlink" href="#如何使用_Mock">#</a></h2><h3 class="subject" id="1_在_js_代码直接请求yapi提供的_mock_地址(不用担心跨域问题)">1 在 js 代码直接请求yapi提供的 mock 地址(不用担心跨域问题) <a class="hashlink" href="#1_在_js_代码直接请求yapi提供的_mock_地址(不用担心跨域问题)">#</a></h3><p>在代码直接请求 yapi 提供的 mock 地址,以 jQuery 为例:</p>
|
||||
<pre><code class="lang-javascript"><span class="token keyword">let</span> prefix <span class="token operator">=</span> <span class="token string">'http://yapi.xxx.com/mock/2817'</span>
|
||||
$<span class="token punctuation">.</span><span class="token function">post</span><span class="token punctuation">(</span>prefix<span class="token operator">+</span><span class="token string">'/baseapi/path'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span>username<span class="token punctuation">:</span> <span class="token string">'xxx'</span><span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token keyword">function</span><span class="token punctuation">(</span>res<span class="token punctuation">)</span><span class="token punctuation">{</span>
|
||||
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>res<span class="token punctuation">)</span> <span class="token comment" spellcheck="true">//返回上图预览部分的数据</span>
|
||||
<span class="token punctuation">}</span><span class="token punctuation">)</span>
|
||||
</code></pre>
|
||||
<h3 class="subject" id="2_基于本地服务器反向代理">2 基于本地服务器反向代理 <a class="hashlink" href="#2_基于本地服务器反向代理">#</a></h3><p>优点:不用修改项目代码</p>
|
||||
<h4 class="subject" id="2.1_基于nginx反向代理">2.1 基于nginx反向代理 <a class="hashlink" href="#2.1_基于nginx反向代理">#</a></h4><pre><code class="lang-nginx"><span class="token keyword">location</span> <span class="token operator">/</span>baseapi
|
||||
<h4 class="subject" id="2.1_基于_nginx_反向代理">2.1 基于 nginx 反向代理 <a class="hashlink" href="#2.1_基于_nginx_反向代理">#</a></h4><pre><code class="lang-nginx"><span class="token keyword">location</span> <span class="token operator">/</span>baseapi
|
||||
<span class="token punctuation">{</span>
|
||||
<span class="token keyword">proxy_pass</span> <span class="token keyword">http</span><span class="token punctuation">:</span><span class="token operator">/</span><span class="token operator">/</span>yapi<span class="token punctuation">.</span>xxx<span class="token punctuation">.</span>com<span class="token operator">/</span>mock<span class="token operator">/</span><span class="token number">2817</span><span class="token operator">/</span>baseapi<span class="token punctuation">;</span> <span class="token comment" spellcheck="true">#baseapi后面没有"/"</span>
|
||||
<span class="token punctuation">}</span>
|
||||
</code></pre>
|
||||
<h4 class="subject" id="2.2_基于ykit_mock功能">2.2 基于ykit mock功能 <a class="hashlink" href="#2.2_基于ykit_mock功能">#</a></h4><pre><code class="lang-javascript"><span class="token punctuation">{</span>
|
||||
<h4 class="subject" id="2.2_基于_ykit_mock功能">2.2 基于 ykit mock功能 <a class="hashlink" href="#2.2_基于_ykit_mock功能">#</a></h4><pre><code class="lang-javascript"><span class="token punctuation">{</span>
|
||||
pattern<span class="token punctuation">:</span> <span class="token regex">/\/api\/(.*)/</span><span class="token punctuation">,</span>
|
||||
responder<span class="token punctuation">:</span> <span class="token string">'http://yapi.xxx.com/mock/58/api/$1'</span>
|
||||
<span class="token punctuation">}</span>
|
||||
</code></pre>
|
||||
<p>上面通过正则匹配,将所有接口转到 <a href="http://yapi.xxx.com">http://yapi.xxx.com</a> 上,比如 <code>http://localhost/api/user/status</code> 会成为 <code>http://yapi.xxx.com/mock/58/api/user/status</code></p>
|
||||
<p>详细使用指南: <a target="_blank" href="https://ykit.ymfe.org/plugins-mock.html#获取远程数据_Map_Remote_">ykit-config-mock</a></p>
|
||||
<h4 class="subject" id="2.3_基于ykit_Jerry代理">2.3 基于ykit Jerry代理 <a class="hashlink" href="#2.3_基于ykit_Jerry代理">#</a></h4><p>假设您本地服务器访问地址是: <a href="http://xxx.com">http://xxx.com</a></p>
|
||||
<h4 class="subject" id="2.3_基于_ykit_Jerry_代理">2.3 基于 ykit Jerry 代理 <a class="hashlink" href="#2.3_基于_ykit_Jerry_代理">#</a></h4><p>假设您本地服务器访问地址是: <a href="http://xxx.com">http://xxx.com</a></p>
|
||||
<p><img src="./images/ykit.jpg" /></p>
|
||||
<p><span id="mock"></span></p>
|
||||
<h2 class="subject" id="高级Mock">高级Mock <a class="hashlink" href="#高级Mock">#</a></h2><p>在前端开发阶段,对于某些接口,业务相对复杂,而 UI 端也需要根据接口返回的不同内容去做相应的处理</p>
|
||||
<p>YApi 提供了写 js 脚本方式处理这一问题,可以根据用户请求的参数修改返回内容。</p>
|
||||
<h3 class="subject" id="全局变量">全局变量 <a class="hashlink" href="#全局变量">#</a></h3><ol>
|
||||
<li>mockJson</li><li>query</li><li>body</li><li>header</li><li>cookie</li></ol>
|
||||
<h3 class="subject" id="使用方法">使用方法 <a class="hashlink" href="#使用方法">#</a></h3><ol>
|
||||
<li>首先开启此功能</li><li>mock脚本就是用 javascript 对 mockJson 变量修改</li></ol>
|
||||
<h3 class="subject" id="示例:">示例: <a class="hashlink" href="#示例:">#</a></h3><p><img class="doc-img" style="width: 80%" src="./images/usage/adv-mock.jpg" /></p>
|
||||
<h2 class="subject" id="Mock语法规范">Mock语法规范 <a class="hashlink" href="#Mock语法规范">#</a></h2><blockquote>
|
||||
<p>了解更多Mock详情:<a href="https://github.com/nuysoft/Mock/wiki/Syntax-Specification">Mock.js 官方文档</a></p>
|
||||
<h2 class="subject" id="Mock_语法规范">Mock 语法规范 <a class="hashlink" href="#Mock_语法规范">#</a></h2><blockquote>
|
||||
<p>了解更多Mock详情:<a href="http://mockjs.com/examples.html">Mock.js 官方文档</a></p>
|
||||
</blockquote>
|
||||
<p>Mock.js 的语法规范包括两部分:</p>
|
||||
<p><a href="#DTD">1. 数据模板定义规范(Data Template Definition,DTD)</a></p>
|
||||
|
@ -140,7 +140,13 @@
|
||||
<!-- <li > -->
|
||||
<li >
|
||||
|
||||
<a href="mock.html">使用mock功能</a>
|
||||
<a href="mock.html">普通 Mock</a>
|
||||
</li>
|
||||
|
||||
<!-- <li > -->
|
||||
<li >
|
||||
|
||||
<a href="adv_mock.html">高级 Mock</a>
|
||||
</li>
|
||||
|
||||
<!-- <li > -->
|
||||
|
@ -140,7 +140,13 @@
|
||||
<!-- <li > -->
|
||||
<li >
|
||||
|
||||
<a href="mock.html">使用mock功能</a>
|
||||
<a href="mock.html">普通 Mock</a>
|
||||
</li>
|
||||
|
||||
<!-- <li > -->
|
||||
<li >
|
||||
|
||||
<a href="adv_mock.html">高级 Mock</a>
|
||||
</li>
|
||||
|
||||
<!-- <li > -->
|
||||
|
@ -91,6 +91,20 @@
|
||||
<nav class="docs-sidebar hidden-print hidden-xs hidden-sm">
|
||||
<ul class="nav docs-sidenav">
|
||||
|
||||
<!-- <li > -->
|
||||
<li >
|
||||
|
||||
<a href="#1.1.3">1.1.3</a>
|
||||
</li>
|
||||
|
||||
<ul class="nav docs-sidenav-extend" >
|
||||
|
||||
<li >
|
||||
<a href="#Bug_Fixed">Bug Fixed</a>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
|
||||
<!-- <li > -->
|
||||
<li >
|
||||
|
||||
@ -179,7 +193,9 @@
|
||||
|
||||
<div class="content-right markdown-body use-sidebar" role="main">
|
||||
|
||||
<h2 class="subject" id="1.1.2">1.1.2 <a class="hashlink" href="#1.1.2">#</a></h2><h3 class="subject" id="Features">Features <a class="hashlink" href="#Features">#</a></h3><ul>
|
||||
<h2 class="subject" id="1.1.3">1.1.3 <a class="hashlink" href="#1.1.3">#</a></h2><h3 class="subject" id="Bug_Fixed">Bug Fixed <a class="hashlink" href="#Bug_Fixed">#</a></h3><ul>
|
||||
<li>修复了切换集合环境的 Bug</li><li>修复了 mockServer 拿不到 Post 请求 Body</li><li>修复了接口调试 pathParams 无法使用 mock 参数和变量参数</li></ul>
|
||||
<h2 class="subject" id="1.1.2">1.1.2 <a class="hashlink" href="#1.1.2">#</a></h2><h3 class="subject" id="Features">Features <a class="hashlink" href="#Features">#</a></h3><ul>
|
||||
<li>接口运行增加了 query 和 body 的 enable 选项,可选择是否请求该字段</li><li>Mock 支持了时间戳占位符 @timestamp</li><li>接口集运行页面可选择环境</li><li>接口集动态参数格式由原来的 $.{key}.{jsonPath} 改为 $.{key}.{body|params}.{jsonPath}</li></ul>
|
||||
<h3 class="subject" id="Bug_Fixed">Bug Fixed <a class="hashlink" href="#Bug_Fixed">#</a></h3><ul>
|
||||
<li>修复了接口集运行功能会忽略环境配置的 domain 路径</li><li>修复了动态路由 mock 返回结果不是该接口定义返回数据</li><li>修复了日志链接错误问题</li><li>修复了添加用户 loading 问题</li><li>修复了用户名编辑,前台未更新问题</li><li>修复了复制接口导致 GET 请求显示 request-body 问题</li><li>修复了接口集页面刷新后跳转到第一个接口集问题</li><li>修复了接口用例页面修改 header 参数值没有效果 bug</li><li>修复了接口集页面导入接口会导致 reqBody 清空 bug</li></ul>
|
||||
|
@ -205,20 +205,6 @@ class groupController extends baseController {
|
||||
}
|
||||
}
|
||||
|
||||
// params.role = ['owner', 'dev', 'guest'].find(v => v === params.role) || 'dev';
|
||||
|
||||
// var check = await groupInst.checkMemberRepeat(params.id, params.member_uid);
|
||||
// if (check > 0) {
|
||||
// return ctx.body = yapi.commons.resReturn(null, 400, '成员已存在');
|
||||
// }
|
||||
// let groupUserdata = await this.getUserdata(params.member_uid, params.role);
|
||||
// if (groupUserdata === null) {
|
||||
// return ctx.body = yapi.commons.resReturn(null, 400, '组长uid不存在')
|
||||
// }
|
||||
// if (groupUserdata._role === 'admin') {
|
||||
// return ctx.body = yapi.commons.resReturn(null, 400, '不能邀请管理员')
|
||||
// }
|
||||
// delete groupUserdata._role;
|
||||
try {
|
||||
let result = await groupInst.addMember(params.id, add_members);
|
||||
let username = this.getUsername();
|
||||
@ -475,10 +461,14 @@ class groupController extends baseController {
|
||||
await interfaceCaseInst.delByProjectId(p._id)
|
||||
await interfaceColInst.delByProjectId(p._id)
|
||||
})
|
||||
await projectInst.delByGroupid(id);
|
||||
if(projectList.length > 0){
|
||||
await projectInst.delByGroupid(id);
|
||||
}
|
||||
|
||||
let result = await groupInst.del(id);
|
||||
ctx.body = yapi.commons.resReturn(result);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
ctx.body = yapi.commons.resReturn(null, 402, err.message);
|
||||
}
|
||||
}
|
||||
|
@ -63,7 +63,7 @@ class interfaceController extends baseController {
|
||||
* @param {Boolean} [req_headers[].required] 是否是必须,默认为否
|
||||
* @param {String} [req_headers[].desc] header描述
|
||||
* @param {String} [req_body_type] 请求参数方式,有["form", "json", "text", "xml"]四种
|
||||
* @param {Array} [req_params] name, desc两个参数
|
||||
* @param {Array} [req_params] 路径参数 name, desc两个参数
|
||||
* @param {Mixed} [req_body_form] 请求参数,如果请求方式是form,参数是Array数组,其他格式请求参数是字符串
|
||||
* @param {String} [req_body_form[].name] 请求参数名
|
||||
* @param {String} [req_body_form[].value] 请求参数值,可填写生成规则(mock)。如@email,随机生成一条email
|
||||
@ -327,6 +327,9 @@ class interfaceController extends baseController {
|
||||
}
|
||||
|
||||
let project = await this.projectModel.getBaseInfo(project_id);
|
||||
if(!project){
|
||||
return ctx.body = yapi.commons.resReturn(null, 406, '不存在的项目');
|
||||
}
|
||||
if (project.project_type === 'private') {
|
||||
if (await this.checkAuth(project._id, 'project', 'view') !== true) {
|
||||
return ctx.body = yapi.commons.resReturn(null, 406, '没有权限');
|
||||
|
@ -181,7 +181,9 @@ class interfaceColController extends baseController{
|
||||
resultList = resultList.sort((a,b)=>{
|
||||
return a.index - b.index;
|
||||
});
|
||||
ctx.body = yapi.commons.resReturn(resultList);
|
||||
let ctxBody = yapi.commons.resReturn(resultList);
|
||||
ctxBody.colData = colData;
|
||||
ctx.body = ctxBody;
|
||||
} catch (e) {
|
||||
ctx.body = yapi.commons.resReturn(null, 402, e.message);
|
||||
}
|
||||
@ -361,10 +363,6 @@ class interfaceColController extends baseController{
|
||||
return ctx.body = yapi.commons.resReturn(null, 400, '用例id不能为空');
|
||||
}
|
||||
|
||||
// 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');
|
||||
if (!auth) {
|
||||
@ -373,9 +371,9 @@ class interfaceColController extends baseController{
|
||||
|
||||
params.uid = this.getUid();
|
||||
|
||||
//不允许修改接口id和项目id
|
||||
delete params.interface_id;
|
||||
delete params.project_id;
|
||||
// delete params.col_id;
|
||||
|
||||
let result = await this.caseModel.up(params.id, params);
|
||||
let username = this.getUsername();
|
||||
@ -481,20 +479,22 @@ class interfaceColController extends baseController{
|
||||
try{
|
||||
let params = ctx.request.body;
|
||||
let id = params.col_id;
|
||||
if(!id){
|
||||
return ctx.body = yapi.commons.resReturn(null, 400, '缺少 col_id 参数');
|
||||
}
|
||||
let colData = await this.colModel.get(id);
|
||||
if(!colData){
|
||||
return ctx.body = yapi.commons.resReturn(null, 400, '不存在');
|
||||
}
|
||||
let auth = await this.checkAuth(colData.project_id, 'project', 'edit')
|
||||
if (!auth) {
|
||||
return ctx.body = yapi.commons.resReturn(null, 400, '没有权限');
|
||||
}
|
||||
|
||||
let result = await this.colModel.up(params.col_id, {
|
||||
name: params.name,
|
||||
desc: params.desc,
|
||||
up_time: yapi.commons.time()
|
||||
});
|
||||
delete params.col_id;
|
||||
let result = await this.colModel.up(id, params);
|
||||
let username = this.getUsername();
|
||||
yapi.commons.saveLog({
|
||||
contnet: `<a href="/user/profile/${this.getUid()}">${username}</a> 更新了接口集 <a href="/project/${colData.project_id}/interface/col/${params.col_id}">${params.name}</a> 的信息`,
|
||||
content: `<a href="/user/profile/${this.getUid()}">${username}</a> 更新了接口集 <a href="/project/${colData.project_id}/interface/col/${id}">${colData.name}</a> 的信息`,
|
||||
type: 'project',
|
||||
uid: this.getUid(),
|
||||
username: username,
|
||||
@ -622,6 +622,28 @@ class interfaceColController extends baseController{
|
||||
}
|
||||
}
|
||||
|
||||
async runCaseScript(ctx){
|
||||
let params = ctx.request.body;
|
||||
let script = params.script;
|
||||
if(!script){
|
||||
return ctx.body = yapi.commons.resReturn('ok');
|
||||
}
|
||||
|
||||
try{
|
||||
let a = yapi.commons.sandbox({
|
||||
assert: require('assert'),
|
||||
status: params.response.status,
|
||||
body: params.response.body,
|
||||
header: params.response.header,
|
||||
records: params.records
|
||||
}, script);
|
||||
return ctx.body = yapi.commons.resReturn('ok');
|
||||
}catch(err){
|
||||
let errArr = err.stack.split("\n");
|
||||
return ctx.body = yapi.commons.resReturn(errArr, 400, err.message)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -8,6 +8,18 @@ body, h1, h2, h3, h4, h5, h6 {
|
||||
margin-top: 3em;
|
||||
}
|
||||
|
||||
.doc-img-wrapper {
|
||||
background: #f7f7f7;
|
||||
padding: 16px;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
width: 80%;
|
||||
position: relative;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.doc-img-r {
|
||||
display: block;
|
||||
}
|
||||
.doc-img {
|
||||
width: 50%;
|
||||
display: block;
|
||||
|
204
test/json-schema-mockjs.test.js
Normal file
@ -0,0 +1,204 @@
|
||||
import test from 'ava';
|
||||
const jsm = require('../common/json-schema-mockjs.js');
|
||||
|
||||
test('jsmBase', t=>{
|
||||
let json1 = {
|
||||
"title": "Person",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"firstName": {
|
||||
"type": "string"
|
||||
},
|
||||
"lastName": {
|
||||
"type": "string"
|
||||
},
|
||||
"age": {
|
||||
"description": "Age in years",
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
}
|
||||
},
|
||||
"required": ["firstName", "lastName"]
|
||||
};
|
||||
t.deepEqual(jsm(json1), {
|
||||
firstName: '@string',
|
||||
lastName: '@string',
|
||||
age: "@integer"
|
||||
});
|
||||
})
|
||||
|
||||
test('jsmRef', t=>{
|
||||
let json2 = {
|
||||
"$ref": "#/definitions/Pet",
|
||||
"definitions": {
|
||||
"Order": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"petId": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"quantity": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"shipDate": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"status": {
|
||||
"type": "string",
|
||||
"description": "Order Status",
|
||||
"enum": [
|
||||
"placed",
|
||||
"approved",
|
||||
"delivered"
|
||||
]
|
||||
},
|
||||
"complete": {
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
}
|
||||
},
|
||||
"xml": {
|
||||
"name": "Order"
|
||||
}
|
||||
},
|
||||
"Category": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"xml": {
|
||||
"name": "Category"
|
||||
}
|
||||
},
|
||||
"User": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"username": {
|
||||
"type": "string"
|
||||
},
|
||||
"firstName": {
|
||||
"type": "string"
|
||||
},
|
||||
"lastName": {
|
||||
"type": "string"
|
||||
},
|
||||
"email": {
|
||||
"type": "string"
|
||||
},
|
||||
"password": {
|
||||
"type": "string"
|
||||
},
|
||||
"phone": {
|
||||
"type": "string"
|
||||
},
|
||||
"userStatus": {
|
||||
"type": "integer",
|
||||
"format": "int32",
|
||||
"description": "User Status"
|
||||
}
|
||||
},
|
||||
"xml": {
|
||||
"name": "User"
|
||||
}
|
||||
},
|
||||
"Tag": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"xml": {
|
||||
"name": "Tag"
|
||||
}
|
||||
},
|
||||
"Pet": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"name",
|
||||
"photoUrls"
|
||||
],
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"category": {
|
||||
"$ref": "#/definitions/Category"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"example": "doggie"
|
||||
},
|
||||
"photoUrls": {
|
||||
"type": "array",
|
||||
"xml": {
|
||||
"name": "photoUrl",
|
||||
"wrapped": true
|
||||
},
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"type": "string",
|
||||
"description": "pet status in the store"
|
||||
}
|
||||
},
|
||||
"xml": {
|
||||
"name": "Pet"
|
||||
}
|
||||
},
|
||||
"ApiResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"code": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"type": {
|
||||
"type": "string"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const destJson2 = {
|
||||
id: '@integer',
|
||||
category: {
|
||||
id: '@integer',
|
||||
name: '@string'
|
||||
},
|
||||
name: '@string',
|
||||
photoUrls: ['@string'],
|
||||
status: '@string'
|
||||
}
|
||||
t.deepEqual(jsm(json2), destJson2);
|
||||
|
||||
|
||||
})
|
143
test/lib.test.js
Normal file
@ -0,0 +1,143 @@
|
||||
import test from 'ava';
|
||||
|
||||
const rewire = require("rewire");
|
||||
const lib = rewire('../common/lib.js');
|
||||
const initPlugins = lib.initPlugins;
|
||||
|
||||
|
||||
test('initPlugins', t=>{
|
||||
lib.__set__("getPluginConfig", function(){
|
||||
return {
|
||||
server: true,
|
||||
client: true
|
||||
}
|
||||
})
|
||||
let configs = initPlugins(['a', 'b'], 'exts');
|
||||
t.deepEqual(configs, [{
|
||||
name: 'a',
|
||||
enable: true,
|
||||
server: true,
|
||||
client: true
|
||||
}, {
|
||||
name: 'b',
|
||||
enable: true,
|
||||
server: true,
|
||||
client: true
|
||||
}])
|
||||
})
|
||||
|
||||
test('initPlugins2', t=>{
|
||||
lib.__set__("getPluginConfig", function(){
|
||||
return {
|
||||
server: true,
|
||||
client: false
|
||||
}
|
||||
})
|
||||
let configs = initPlugins(['a', 'b'], 'exts');
|
||||
t.deepEqual(configs, [{
|
||||
name: 'a',
|
||||
enable: true,
|
||||
server: true,
|
||||
client: false
|
||||
}, {
|
||||
name: 'b',
|
||||
enable: true,
|
||||
server: true,
|
||||
client: false
|
||||
}])
|
||||
})
|
||||
|
||||
test('initPlugins3', t=>{
|
||||
lib.__set__("getPluginConfig", function(){
|
||||
return {
|
||||
server: false,
|
||||
client: true
|
||||
}
|
||||
})
|
||||
let configs = initPlugins(['a', {name: 'a'}], 'exts');
|
||||
t.deepEqual(configs, [{
|
||||
name: 'a',
|
||||
enable: true,
|
||||
server: false,
|
||||
client: true
|
||||
}])
|
||||
})
|
||||
|
||||
test('initPlugins3', t=>{
|
||||
lib.__set__("getPluginConfig", function(){
|
||||
return {
|
||||
server: false,
|
||||
client: true
|
||||
}
|
||||
})
|
||||
let configs = initPlugins([{
|
||||
name: 'a',
|
||||
options: {
|
||||
a:1,
|
||||
t:{
|
||||
c:3
|
||||
}
|
||||
}
|
||||
}], 'exts');
|
||||
t.deepEqual(configs, [{
|
||||
name: 'a',
|
||||
enable: true,
|
||||
server: false,
|
||||
client: true,
|
||||
options: {
|
||||
a:1,
|
||||
t:{
|
||||
c:3
|
||||
}
|
||||
}
|
||||
}])
|
||||
})
|
||||
|
||||
test('initPlugins3', t=>{
|
||||
lib.__set__("getPluginConfig", function(){
|
||||
return {
|
||||
server: false,
|
||||
client: false
|
||||
}
|
||||
})
|
||||
let configs = initPlugins(['a', 'b'], 'exts');
|
||||
t.deepEqual(configs, [])
|
||||
})
|
||||
|
||||
test('testJsonEqual', t=>{
|
||||
let json1 = {
|
||||
a:"1",
|
||||
b:2,
|
||||
c:{
|
||||
t:3,
|
||||
x: [11,22]
|
||||
}
|
||||
};
|
||||
|
||||
let json2 = {
|
||||
c:{
|
||||
x: [11,22],
|
||||
t:3
|
||||
},
|
||||
b:2,
|
||||
a:"1"
|
||||
}
|
||||
t.true(lib.jsonEqual(json1, json1));
|
||||
})
|
||||
|
||||
test('testJsonEqualBase', t=>{
|
||||
t.true(lib.jsonEqual(1,1));
|
||||
})
|
||||
|
||||
test('testJsonEqualBaseString', t=>{
|
||||
t.true(lib.jsonEqual('2', '2'));
|
||||
})
|
||||
|
||||
|
||||
test('isDeepMatch', t=>{
|
||||
t.true(lib.isDeepMatch({a:'aaaaa', b:2}, {a:'aaaaa'}))
|
||||
})
|
||||
|
||||
test('isDeepMatch', t=>{
|
||||
t.true(lib.isDeepMatch({a:1, b:2, c: {t:'ttt'}}, {c: {t:'ttt'}}))
|
||||
})
|
107
test/mock-extra.test.js
Normal file
@ -0,0 +1,107 @@
|
||||
import test from 'ava';
|
||||
const mockExtra = require('../common/mock-extra.js');
|
||||
|
||||
|
||||
test('mock-extra', t=>{
|
||||
let data = '@string ${body.a}';
|
||||
t.is(mockExtra(data), '@string ${body.a}');
|
||||
let data2 = {
|
||||
a:'@string',
|
||||
b:{
|
||||
t:'${body.a}'
|
||||
}
|
||||
}
|
||||
t.deepEqual(mockExtra(data2,{
|
||||
body: {
|
||||
a: 3
|
||||
}
|
||||
}), {
|
||||
a:'@string',
|
||||
b:{
|
||||
t:3
|
||||
}
|
||||
}, 'message');
|
||||
|
||||
//test object
|
||||
let data3 = {
|
||||
a:'@string',
|
||||
b:{
|
||||
t:'${body}'
|
||||
}
|
||||
}
|
||||
t.deepEqual(mockExtra(data3,{
|
||||
body: {
|
||||
a: 3,
|
||||
t: 5
|
||||
}
|
||||
}), {
|
||||
a:'@string',
|
||||
b:{
|
||||
t:{
|
||||
a: 3,
|
||||
t: 5
|
||||
}
|
||||
}
|
||||
}, 'message');
|
||||
|
||||
//test array
|
||||
let data4 = {
|
||||
a:'@string',
|
||||
b:{
|
||||
t:'${query.arr}'
|
||||
}
|
||||
}
|
||||
|
||||
t.deepEqual(mockExtra(data4, {query: {
|
||||
arr: [1,2,3]
|
||||
}}), {
|
||||
a: '@string',
|
||||
b:{
|
||||
t: [1,2,3]
|
||||
}
|
||||
|
||||
}, 'message');
|
||||
|
||||
//test var
|
||||
let data5 = {
|
||||
a:'@string',
|
||||
b:{
|
||||
t:'${ttt.arr}'
|
||||
}
|
||||
}
|
||||
|
||||
t.deepEqual(mockExtra(data5, {ttt: {
|
||||
arr: [1,2,3]
|
||||
}}), {
|
||||
a: '@string',
|
||||
b:{
|
||||
t: [1,2,3]
|
||||
}
|
||||
|
||||
}, 'message');
|
||||
|
||||
//test var
|
||||
let data6 = {
|
||||
a:'@string',
|
||||
b:{
|
||||
"ttt|regexp":'a|b'
|
||||
}
|
||||
}
|
||||
|
||||
//test regexp
|
||||
t.deepEqual(mockExtra(data6, {ttt: {
|
||||
arr: [1,2,3]
|
||||
}}), {
|
||||
a: '@string',
|
||||
b:{
|
||||
ttt: /a|b/
|
||||
}
|
||||
|
||||
}, 'message');
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
})
|
||||
|
@ -2,6 +2,7 @@ import test from 'ava';
|
||||
const rewire = require("rewire");
|
||||
const mockServer = rewire('../../server/middleware/mockServer.js');
|
||||
const matchApi = mockServer.__get__('matchApi');
|
||||
const mockExtra = require('../../common/mock-extra.js');
|
||||
|
||||
test('matchApi', t => {
|
||||
const apiRule = '/user/:username';
|
||||
@ -20,4 +21,4 @@ test('matchApi', t => {
|
||||
t.false(matchApi('/user/a/ttt2/b', apiRule_3))
|
||||
|
||||
|
||||
});
|
||||
});
|
||||
|
@ -49,9 +49,13 @@
|
||||
"index": "interface",
|
||||
"content": "./doc/page/usage/api.md"
|
||||
},{
|
||||
"name": "使用mock功能",
|
||||
"name": "普通 Mock",
|
||||
"index": "mock",
|
||||
"content": "./doc/page/usage/mock.md"
|
||||
},{
|
||||
"name": "高级 Mock",
|
||||
"index": "adv_mock",
|
||||
"content": "./doc/page/usage/adv_mock.md"
|
||||
},{
|
||||
"name": "使用测试集",
|
||||
"index": "case",
|
||||
|