rapnc
Version:
Node client for RAP 1.0
271 lines (242 loc) • 8.77 kB
JavaScript
/*
校验接口的正确性
编写json是一种开发测试用例的方式,另一种方式是在rap的图形界面上编辑
这种方式只能模拟一些数据,不能测试真实数据的合法性,真实数据还是要靠在插件里加check来完成
向自己机器上发起脚本自动化测试,对RAP服务器来说毫无压力!赞!
- 要求必须是offline启动,才允许脚本自动测试
- cases都可以是数组或对象
- method为POST时body作为POST body,method为GET时body作为url params
- 需要能够指定测试用例的目录
*/
var async = require('async');
var querystring = require('querystring');
var urlmodule = require('url');
var fs = require('fs');
var path = require('path');
var program = require('commander');
var helper = require('../helpers');
var logger = helper.logger;
var isObject = helper.types.isObject;
var ajax = helper.ajax;
var toQueryString = helper.toQueryString;
var appendToUrl = helper.appendToUrl;
var config = require('../config.json');
var CONSTS = require('../constants');
var SEPERATOR = CONSTS.SEPERATOR;
var REAL_DATA_KEY = CONSTS.REAL_DATA_KEY;
var checkHost = config.pull_host || CONSTS.PULL_HOST;
var checkPort = config.pull_port || CONSTS.PULL_PORT;
var URL_PREFIX = config.urlprefix || CONSTS.URL_PREFIX;
function sendRealDataToRAP(projectId, url, testcase, real, callback) {
url = URL_PREFIX + '/check/' + projectId + '/' + url.replace(/^\//g, '');
url = appendToUrl(url, CONSTS.URL_SEPERATOR);
var condition = testcase.condition || {};
if (condition.params) {
url = appendToUrl(url, condition.params);
}
if (condition.body) {
url = appendToUrl(url, querystring.stringify(condition.body));
}
url = appendToUrl(url, REAL_DATA_KEY + '=' + JSON.stringify(real));
// 使用post方法,real作为post的data发送
// querystring.stringify 处理嵌套的object时不是很给力,暂用get
ajax({
host: checkHost,
port: checkPort,
method: 'get',
path: url,
agent: false
}, function(err, data) {
if (err) {
callback('HOST: ' + checkHost + ', PORT: ' + checkPort + ', MSG: ' + err);
return;
}
data = data.trim();
callback(null, data);
});
}
function getDataFromDataHost(dataHost, originUrl, condition, callback) {
var result = urlmodule.parse(dataHost);
var options = {
host: result.hostname,
port: result.port || 80,
path: appendToUrl(originUrl, condition.params),
body: condition.body,
method: condition.method
}
ajax(options, callback)
}
function checkDataByRAP(projectId, url, testcase, callback) {
logger.log('[RAPNC check] checking [PROJECT: ' + projectId + ', ACTION: ' + url + ', CASE: ' + testcase.index + ']')
var originUrl = url;
var real = testcase.realData;
if (real === undefined) {
var dataHost = testcase.dataHost;
if (!dataHost) {
callback('no real data defined in testcase. PROJECT: ' + projectId + ', ACTION: ' + originUrl + ', CASE: ' + testcase.index);
return;
}
getDataFromDataHost(dataHost, originUrl, testcase.condition, function(err, real) {
if (err) {
callback('get real data from host "' + dataHost + '" error: ' + err);
return;
}
sendRealDataToRAP(projectId, url, testcase, JSON.parse(real), callback)
});
} else {
sendRealDataToRAP(projectId, url, testcase, real, callback)
}
}
function key(projectId, url, index) {
var result = projectId + SEPERATOR + url;
if (index !== undefined) {
result += SEPERATOR + index;
}
return result;
}
function dekey(str) {
var result = {};
var splited = str.split(SEPERATOR);
if (splited.length >= 1) {
result.projectId = splited[0];
}
if (splited.length >= 2) {
result.url = splited[1];
}
if (splited.length >= 3) {
result.index = splited[2];
}
return result;
}
function fillAsyncTask(tasks, projectId, json, cache) {
var url = json.url;
var cases = json.cases;
if (!cases) {
return;
}
if (isObject(cases)) {
cases = [cases];
}
var index = 0;
cases.forEach(function(testcase) {
index++;
var keygened = key(projectId, url, index);
testcase.index = index;
tasks[keygened] = function(next) {
checkDataByRAP(projectId, url, testcase, next);
}
cache[keygened] = testcase;
})
}
function handle(argv) {
program
.usage('check PROJECT_ID|all [options]')
.option('-d, --test-dir [testDir]', 'testcases dir, relative to current dir')
.option('-c, --current', 'current dir as testcases dir')
.option('--host [host]', 'host of Check Service Server')
.option('--port [port]', 'port of Check Service Server')
.parse(argv);
if (argv.length < 4) {
program.help();
process.exit();
}
if (argv.indexOf('-h') != -1 || argv.indexOf('--help') != -1) {
program.help();
process.exit(0);
}
var projectId = argv[3];
if (projectId != 'all' && !/^\d+$/.test(projectId)) {
logger.error('[RAPNC check] PROJECT_ID should be integer or "all"', 'check');
return;
}
if (projectId == 'all') {
logger.warn('o... all is not supported yet.', 'check')
program.help();
return;
}
checkHost = program.host || checkHost;
checkPort = program.port || checkPort;
var cacheDir;
if (program.current) {
cacheDir = path.join(process.cwd(), '.').replace(/\/$/g, '') + '/' + projectId;
} else if (program.testDir) {
cacheDir = path.join(process.cwd(), program.testDir).replace(/\/$/g, '') + '/' + projectId;
} else {
cacheDir = path.join(__dirname, '../cache/tests/') + projectId;
}
var displayDir = cacheDir + '/';
if (!fs.existsSync(cacheDir)) {
logger.error('[RAPNC check] Testcase dir: ' + cacheDir + ' does not exist');
return;
}
logger.log('')
logger.ok('[RAPNC check] Test dir: ' + cacheDir);
logger.log('')
var files = fs.readdirSync(cacheDir);
var tasks = {};
var cache = {};
files.forEach(function(f) {
var json;
try {
json = require(path.relative(__dirname, path.join(cacheDir, f)));
} catch (e) {
logger.log('')
logger.error('[RAPNC check] can\'t require ' + displayDir + f + ' as a Nodejs module');
logger.log('')
return;
}
fillAsyncTask(tasks, projectId, json, cache);
});
async.auto(tasks, function(err, results) {
if (err) {
logger.log('')
logger.error('[RAPNC check] ' + err);
logger.log('')
return;
}
var checkFlag = true;
var errorMsg = '';
for(var prop in results) {
var props = dekey(prop);
var projectId = props.projectId,
url = props.url,
index = props.index;
var cached = cache[prop].expected || {};
var returned = JSON.parse(results[prop]);
if (returned._RAPNC_STATUS_) {
checkFlag = false;
logger.log();
logger.error('ACTION: ' + url + ', CASE: ' + index + '\n\tERROR: ' + returned.msg);
continue;
}
for(var propCached in cached) {
if (cached[propCached] != returned[propCached]) {
checkFlag = false;
errorMsg += '\n- ACTION ' + url + ' | CASE ' + index + ' | ERROR \n\t' + propCached +
' : [expected:' + cached[propCached] +
' --> actural:' + returned[propCached] + ']\n';
}
}
for(var propCached in returned) {
if (!(propCached in cached)) {
checkFlag = false;
errorMsg += '\n- ACTION ' + url + ' | CASE ' + index + ' | ERROR(unexpected) \n\t' + propCached +
': [' + returned[propCached] + ']\n';
}
}
}
if (errorMsg) {
logger.raw('\nERROR of [PROJECT_ID: ' + projectId + ']\n' + errorMsg);
}
if (!checkFlag) {
logger.log('');
logger.error('[RAPNC check] something wrong.');
logger.log('');
} else {
logger.log('');
logger.ok('[RAPNC check] nothing wrong, nice job!');
logger.log('');
}
})
}
exports.handle = handle;