mm_api 1.1.0 → 1.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/examples/README.md +39 -0
- package/examples/basic.js +37 -0
- package/examples/hello_api/api.json +18 -0
- package/examples/hello_api/index.js +14 -0
- package/examples/helper.js +28 -0
- package/examples/param_api/api.json +17 -0
- package/examples/param_api/index.js +14 -0
- package/examples/param_api/param.json +19 -0
- package/examples/param_api/run.js +38 -0
- package/examples/sql_api/api.json +16 -0
- package/examples/sql_api/index.js +49 -0
- package/examples/sql_api/run.js +33 -0
- package/index.js +2 -2
- package/lib/{drive.js → api.js} +74 -131
- package/lib/manager.js +73 -42
- package/lib/oauth.js +3 -56
- package/package.json +33 -27
- package/script.tpl.js +1 -1
- package/tests/api.test.js +167 -0
- package/tests/fixtures/echo.js +14 -0
- package/tests/fixtures/error.js +13 -0
- package/tests/fixtures/hello.js +13 -0
- package/tests/fixtures/param.json +19 -0
- package/tests/helper.js +57 -0
- package/tests/index.test.js +45 -0
- package/tests/manager.test.js +184 -0
- package/tests/oauth.test.js +107 -0
package/lib/manager.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
const Manager = require(
|
|
2
|
-
const
|
|
1
|
+
const Manager = require('mm_machine').Manager;
|
|
2
|
+
const Api = require("./api");
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
5
|
+
* ApiManager接口类(Web API 网关管理器,基于 mm_corejs,无全局/原型扩展依赖)
|
|
6
6
|
* @class
|
|
7
7
|
* @augments {Manager}
|
|
8
8
|
*/
|
|
9
|
-
class
|
|
9
|
+
class ApiManager extends Manager {
|
|
10
10
|
/**
|
|
11
11
|
* 配置参数
|
|
12
12
|
* @type {object}
|
|
@@ -55,25 +55,50 @@ class Api extends Manager {
|
|
|
55
55
|
* @param {object} parent 父级模块(仅作兼容传入,不使用)
|
|
56
56
|
*/
|
|
57
57
|
constructor(config, parent) {
|
|
58
|
-
super({ ...
|
|
59
|
-
// 插件注册表(方案B:手动注册,供驱动 plugin() 查找)
|
|
60
|
-
this.plugins = {};
|
|
58
|
+
super({ ...ApiManager.config, ...config }, parent);
|
|
61
59
|
}
|
|
62
60
|
}
|
|
63
61
|
|
|
64
62
|
/**
|
|
65
|
-
*
|
|
63
|
+
* ApiManager驱动类
|
|
66
64
|
* @type {Drive}
|
|
67
65
|
*/
|
|
68
|
-
|
|
66
|
+
ApiManager.prototype.Drive = Api;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* 初始化桥接(生命周期汇聚点:init() 与 do('init') 均调用,触发 onInit)
|
|
70
|
+
* @returns {Promise<void>} 初始化完成
|
|
71
|
+
* @private
|
|
72
|
+
*/
|
|
73
|
+
ApiManager.prototype._init = async function () {
|
|
74
|
+
await this.onInit();
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* 启动桥接(生命周期汇聚点,触发 onStart)
|
|
79
|
+
* @returns {Promise<void>} 启动完成
|
|
80
|
+
* @private
|
|
81
|
+
*/
|
|
82
|
+
ApiManager.prototype._start = async function () {
|
|
83
|
+
await this.onStart();
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* 停止桥接(生命周期汇聚点,触发 onStop)
|
|
88
|
+
* @returns {Promise<void>} 停止完成
|
|
89
|
+
* @private
|
|
90
|
+
*/
|
|
91
|
+
ApiManager.prototype._stop = async function () {
|
|
92
|
+
await this.onStop();
|
|
93
|
+
};
|
|
69
94
|
|
|
70
95
|
/**
|
|
71
96
|
* 注册一个 api 驱动(方案B:手动注册,不扫描目录)
|
|
72
97
|
* @param {object} config 驱动配置(必须包含 name)
|
|
73
98
|
* @returns {object} 驱动实例
|
|
74
99
|
*/
|
|
75
|
-
|
|
76
|
-
var drive = new Drive(
|
|
100
|
+
ApiManager.prototype.add = function (config) {
|
|
101
|
+
var drive = new this.Drive(config, this);
|
|
77
102
|
this.set(drive.config.name, drive);
|
|
78
103
|
return drive;
|
|
79
104
|
};
|
|
@@ -83,7 +108,7 @@ Api.prototype.add = function (config) {
|
|
|
83
108
|
* @param {string} name 驱动名称
|
|
84
109
|
* @returns {Promise<boolean>} 是否删除成功
|
|
85
110
|
*/
|
|
86
|
-
|
|
111
|
+
ApiManager.prototype.del = async function (name) {
|
|
87
112
|
var drive = this.get(name);
|
|
88
113
|
if (drive) {
|
|
89
114
|
await drive.stop();
|
|
@@ -97,7 +122,7 @@ Api.prototype.del = async function (name) {
|
|
|
97
122
|
* @param {string} name 驱动名称
|
|
98
123
|
* @returns {Promise<boolean>} 是否执行成功
|
|
99
124
|
*/
|
|
100
|
-
|
|
125
|
+
ApiManager.prototype.reload = async function (name) {
|
|
101
126
|
var drive = this.get(name);
|
|
102
127
|
if (!drive) {
|
|
103
128
|
this.log("warn", "驱动不存在,无法重启", name);
|
|
@@ -115,26 +140,13 @@ Api.prototype.reload = async function (name) {
|
|
|
115
140
|
};
|
|
116
141
|
|
|
117
142
|
/**
|
|
118
|
-
*
|
|
119
|
-
* @param {string} name 插件作用域名称(app 名)
|
|
120
|
-
* @param {object} obj 插件集合对象(需含 get 方法)
|
|
121
|
-
* @returns {object} 当前管理器实例
|
|
122
|
-
*/
|
|
123
|
-
Api.prototype.usePlugin = function (name, obj) {
|
|
124
|
-
this.plugins[name] = obj;
|
|
125
|
-
return this;
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* 初始化驱动实例(仅未初始化时执行生命周期 init,重复调用安全)
|
|
143
|
+
* 初始化驱动实例(执行生命周期 init)
|
|
130
144
|
* @param {object} drive 驱动实例
|
|
131
145
|
* @returns {Promise<void>} 初始化完成
|
|
132
146
|
* @private
|
|
133
147
|
*/
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
await drive.init();
|
|
137
|
-
}
|
|
148
|
+
ApiManager.prototype._initDrive = async function (drive) {
|
|
149
|
+
await drive.init();
|
|
138
150
|
};
|
|
139
151
|
|
|
140
152
|
/**
|
|
@@ -142,7 +154,7 @@ Api.prototype._initDrive = async function (drive) {
|
|
|
142
154
|
* @returns {Array} 驱动配置数组
|
|
143
155
|
* @private
|
|
144
156
|
*/
|
|
145
|
-
|
|
157
|
+
ApiManager.prototype._getInfos = function () {
|
|
146
158
|
return this.values().map((d) => d.config);
|
|
147
159
|
};
|
|
148
160
|
|
|
@@ -152,7 +164,7 @@ Api.prototype._getInfos = function () {
|
|
|
152
164
|
* @returns {Array} 驱动实例数组
|
|
153
165
|
* @private
|
|
154
166
|
*/
|
|
155
|
-
|
|
167
|
+
ApiManager.prototype._sortedDrives = function () {
|
|
156
168
|
var sort_key = this.config.sort_key || "sort";
|
|
157
169
|
var drives = this.values().filter((d) => d.config.state === 1);
|
|
158
170
|
drives.sort((m1, m2) => {
|
|
@@ -174,7 +186,7 @@ Api.prototype._sortedDrives = function () {
|
|
|
174
186
|
* @param {string} pattern 模式
|
|
175
187
|
* @returns {boolean} 是否匹配
|
|
176
188
|
*/
|
|
177
|
-
|
|
189
|
+
ApiManager.prototype.isMatch = function (str, pattern) {
|
|
178
190
|
if (str === pattern) {
|
|
179
191
|
return true;
|
|
180
192
|
} else if (pattern.startsWith("*")) {
|
|
@@ -193,32 +205,51 @@ Api.prototype.isMatch = function (str, pattern) {
|
|
|
193
205
|
};
|
|
194
206
|
|
|
195
207
|
/**
|
|
196
|
-
*
|
|
197
|
-
* @param {object}
|
|
198
|
-
* @param {object}
|
|
208
|
+
* 获取运行参数:兼容 run(ctx, options) 与历史写法 run(params, ctx, options)
|
|
209
|
+
* @param {object} first 请求上下文或参数对象
|
|
210
|
+
* @param {object} second 数据管理器或请求上下文
|
|
211
|
+
* @param {object} third 数据管理器
|
|
212
|
+
* @returns {object} 含 params、ctx、options 的参数集合
|
|
213
|
+
*/
|
|
214
|
+
ApiManager.prototype._getRunArgs = function (first, second, third) {
|
|
215
|
+
if (first && first.request) {
|
|
216
|
+
return { params: first.params || {}, ctx: first, options: second || {} };
|
|
217
|
+
}
|
|
218
|
+
return { params: first || {}, ctx: second || {}, options: third || {} };
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* 执行ApiManager
|
|
223
|
+
* @param {object} ctx 请求上下文,或历史写法中的参数对象
|
|
224
|
+
* @param {object} options 数据管理器,如: { next: async function{}, ret: {} }
|
|
199
225
|
* @returns {object | string} 返回一个对象或字符串
|
|
200
226
|
*/
|
|
201
|
-
|
|
202
|
-
let
|
|
227
|
+
ApiManager.prototype.run = async function (ctx, options = {}) {
|
|
228
|
+
let args = this._getRunArgs(ctx, options, arguments[2]);
|
|
229
|
+
let path = args.ctx.request.path;
|
|
203
230
|
let drives = this._sortedDrives();
|
|
231
|
+
let result = null;
|
|
204
232
|
|
|
205
233
|
for (var i = 0; i < drives.length; i++) {
|
|
206
234
|
var mod = drives[i];
|
|
207
235
|
if (this.isMatch(path, mod.config.path)) {
|
|
208
|
-
if (!mod.isLoaded()) {
|
|
236
|
+
if (!(await mod.isLoaded())) {
|
|
209
237
|
await mod.load();
|
|
210
238
|
}
|
|
211
|
-
var ret = await mod.run(ctx, options);
|
|
239
|
+
var ret = await mod.run(args.params, args.ctx, args.options);
|
|
212
240
|
if (ret) {
|
|
213
|
-
|
|
241
|
+
// 同时写入上下文与数据管理器,兼容两种结果读取方式
|
|
242
|
+
args.ctx.result = ret;
|
|
243
|
+
args.options.ret = ret;
|
|
244
|
+
result = ret;
|
|
214
245
|
break;
|
|
215
246
|
}
|
|
216
247
|
}
|
|
217
248
|
}
|
|
218
|
-
return
|
|
249
|
+
return result;
|
|
219
250
|
};
|
|
220
251
|
|
|
221
252
|
/**
|
|
222
253
|
* @module 导出API管理器类
|
|
223
254
|
*/
|
|
224
|
-
module.exports =
|
|
255
|
+
module.exports = ApiManager;
|
package/lib/oauth.js
CHANGED
|
@@ -1,59 +1,6 @@
|
|
|
1
1
|
const fs = require("fs");
|
|
2
2
|
const path = require("path");
|
|
3
|
-
const { string } = require("mm_helper");
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* 转换值类型(等价复刻 mm_expand 的 convertValue)
|
|
7
|
-
* @param {any} target_val 目标值
|
|
8
|
-
* @param {any} source_val 源值
|
|
9
|
-
* @returns {any} 转换后的值
|
|
10
|
-
*/
|
|
11
|
-
function _convertValue(target_val, source_val) {
|
|
12
|
-
var target_type = typeof target_val;
|
|
13
|
-
if (target_type === "number") {
|
|
14
|
-
var num = Number(source_val);
|
|
15
|
-
return isNaN(num) ? 0 : num;
|
|
16
|
-
} else if (target_type === "boolean") {
|
|
17
|
-
if (source_val === "0" || source_val === "false" || source_val === "False") {
|
|
18
|
-
return false;
|
|
19
|
-
} else if (source_val === "1" || source_val === "true" || source_val === "True") {
|
|
20
|
-
return true;
|
|
21
|
-
} else {
|
|
22
|
-
return Boolean(source_val);
|
|
23
|
-
}
|
|
24
|
-
} else if (target_type === "string") {
|
|
25
|
-
return String(source_val);
|
|
26
|
-
} else {
|
|
27
|
-
return source_val;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* 添加对象属性(替代原全局 push,等价复刻 mm_expand 的 push)
|
|
33
|
-
* @param {object} target 被添加的对象
|
|
34
|
-
* @param {object} source 用作添加的对象
|
|
35
|
-
* @param {boolean} supplement 是否补充没有的对象
|
|
36
|
-
* @returns {object} 修改后的目标对象
|
|
37
|
-
*/
|
|
38
|
-
function _push(target, source, supplement = false) {
|
|
39
|
-
if (!target || !source || typeof target !== "object" || typeof source !== "object") {
|
|
40
|
-
return target;
|
|
41
|
-
}
|
|
42
|
-
for (var key in source) {
|
|
43
|
-
if (target.hasOwnProperty(key)) {
|
|
44
|
-
var target_val = target[key];
|
|
45
|
-
var source_val = source[key];
|
|
46
|
-
if (typeof target_val === "object" && typeof source_val === "object") {
|
|
47
|
-
_push(target_val, source_val, supplement);
|
|
48
|
-
} else if (typeof target_val === typeof source_val) {
|
|
49
|
-
target[key] = _convertValue(target_val, source_val);
|
|
50
|
-
}
|
|
51
|
-
} else if (supplement) {
|
|
52
|
-
target[key] = source[key];
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
return target;
|
|
56
|
-
}
|
|
3
|
+
const { string, object } = require("mm_helper");
|
|
57
4
|
|
|
58
5
|
/**
|
|
59
6
|
* Oauth身份验证驱动类
|
|
@@ -108,14 +55,14 @@ Oauth.prototype.getDir = function () {
|
|
|
108
55
|
* @param {object} obj 配置对象
|
|
109
56
|
*/
|
|
110
57
|
Oauth.prototype.loadObj = function (obj) {
|
|
111
|
-
|
|
58
|
+
object.push(this.config, obj);
|
|
112
59
|
var f = this.config.main;
|
|
113
60
|
if (f) {
|
|
114
61
|
var file = path.isAbsolute(f) ? f : path.resolve(this.getDir(), f);
|
|
115
62
|
if (fs.existsSync(file)) {
|
|
116
63
|
var cs = require(file);
|
|
117
64
|
if (cs) {
|
|
118
|
-
|
|
65
|
+
object.push(this, cs, true);
|
|
119
66
|
}
|
|
120
67
|
}
|
|
121
68
|
}
|
package/package.json
CHANGED
|
@@ -1,29 +1,35 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
"
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
2
|
+
"name": "mm_api",
|
|
3
|
+
"version": "1.1.2",
|
|
4
|
+
"description": "API框架,用于实现web api和mvc模式路由请求,负责配置路由路径、访问权限、校验机制;基于 mm_corejs,内置管理器池,无全局/原型扩展依赖",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"mm_mod",
|
|
8
|
+
"mm_port",
|
|
9
|
+
"mm_in_port",
|
|
10
|
+
"api",
|
|
11
|
+
"mvc",
|
|
12
|
+
"router",
|
|
13
|
+
"oauth",
|
|
14
|
+
"API管理器"
|
|
15
|
+
],
|
|
16
|
+
"layer": "port",
|
|
17
|
+
"group": "web",
|
|
18
|
+
"author": "邱文武",
|
|
19
|
+
"license": "ISC",
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://gitee.com/qiuwenwu91/mm_api.git"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"mm_corejs": "^1.1.4",
|
|
26
|
+
"mm_helper": "^1.0.0",
|
|
27
|
+
"mm_machine": "^3.5.4",
|
|
28
|
+
"mm_param": "^1.1.0",
|
|
29
|
+
"mm_ret": "^1.4.8",
|
|
30
|
+
"mm_sql": "^1.8.0"
|
|
31
|
+
},
|
|
32
|
+
"scripts": {
|
|
33
|
+
"test": "node test.js && node --test tests/*.test.js"
|
|
34
|
+
}
|
|
29
35
|
}
|
package/script.tpl.js
CHANGED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Api 接口驱动测试
|
|
5
|
+
* @description 覆盖脚本加载、请求执行、方法过滤、参数校验与异常路径
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const test = require('node:test');
|
|
9
|
+
const assert = require('node:assert/strict');
|
|
10
|
+
const Api = require('../lib/api.js');
|
|
11
|
+
const { makeCtx, makeOptions } = require('./helper.js');
|
|
12
|
+
|
|
13
|
+
test('load 加载脚本并挂载 main', () => {
|
|
14
|
+
const api = new Api({
|
|
15
|
+
name: 'hello',
|
|
16
|
+
path: '/hello',
|
|
17
|
+
dir: __dirname,
|
|
18
|
+
main: './fixtures/hello.js'
|
|
19
|
+
});
|
|
20
|
+
assert.strictEqual(api.isLoaded(), false, '初始未加载');
|
|
21
|
+
const mod = api.load();
|
|
22
|
+
assert.ok(mod, 'load 应返回脚本模块');
|
|
23
|
+
assert.strictEqual(api.isLoaded(), true, '加载后应标记已加载');
|
|
24
|
+
assert.strictEqual(typeof api.main, 'function', 'main 应挂载为函数');
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('load 不存在的脚本返回 null', () => {
|
|
28
|
+
const api = new Api({
|
|
29
|
+
name: 'miss',
|
|
30
|
+
path: '/miss',
|
|
31
|
+
dir: __dirname,
|
|
32
|
+
main: './fixtures/not_exist.js'
|
|
33
|
+
});
|
|
34
|
+
const mod = api.load();
|
|
35
|
+
assert.strictEqual(mod, null);
|
|
36
|
+
assert.strictEqual(api.isLoaded(), false);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test('run 执行脚本返回 JSON 字符串', async () => {
|
|
40
|
+
const api = new Api({
|
|
41
|
+
name: 'hello',
|
|
42
|
+
path: '/hello',
|
|
43
|
+
method: 'ALL',
|
|
44
|
+
dir: __dirname,
|
|
45
|
+
main: './fixtures/hello.js'
|
|
46
|
+
});
|
|
47
|
+
assert.ok(api.load(), '应先加载脚本');
|
|
48
|
+
const ctx = makeCtx({ path: '/hello', method: 'GET' });
|
|
49
|
+
const ret = await api.run({}, ctx, makeOptions());
|
|
50
|
+
assert.ok(ret, 'run 应返回结果');
|
|
51
|
+
assert.deepStrictEqual(JSON.parse(ret), {
|
|
52
|
+
code: 0,
|
|
53
|
+
message: '成功',
|
|
54
|
+
data: { hello: 'world' }
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test('run 回显查询参数与正文', async () => {
|
|
59
|
+
const api = new Api({
|
|
60
|
+
name: 'echo',
|
|
61
|
+
path: '/echo',
|
|
62
|
+
method: 'ALL',
|
|
63
|
+
dir: __dirname,
|
|
64
|
+
main: './fixtures/echo.js'
|
|
65
|
+
});
|
|
66
|
+
assert.ok(api.load(), '应先加载脚本');
|
|
67
|
+
const ctx = makeCtx({
|
|
68
|
+
path: '/echo',
|
|
69
|
+
method: 'POST',
|
|
70
|
+
query: { a: '1' },
|
|
71
|
+
body: { b: '2' }
|
|
72
|
+
});
|
|
73
|
+
const ret = await api.run({}, ctx, makeOptions());
|
|
74
|
+
assert.deepStrictEqual(JSON.parse(ret), {
|
|
75
|
+
query: { a: '1' },
|
|
76
|
+
body: { b: '2' }
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test('run 方法不匹配返回 null', async () => {
|
|
81
|
+
const api = new Api({
|
|
82
|
+
name: 'hello',
|
|
83
|
+
path: '/hello',
|
|
84
|
+
method: 'POST',
|
|
85
|
+
dir: __dirname,
|
|
86
|
+
main: './fixtures/hello.js'
|
|
87
|
+
});
|
|
88
|
+
const ctx = makeCtx({ path: '/hello', method: 'GET' });
|
|
89
|
+
const ret = await api.run({}, ctx, makeOptions());
|
|
90
|
+
assert.strictEqual(ret, null);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('run 脚本抛错返回错误响应', async () => {
|
|
94
|
+
const api = new Api({
|
|
95
|
+
name: 'error',
|
|
96
|
+
path: '/error',
|
|
97
|
+
method: 'ALL',
|
|
98
|
+
dir: __dirname,
|
|
99
|
+
main: './fixtures/error.js'
|
|
100
|
+
});
|
|
101
|
+
const ctx = makeCtx({ path: '/error', method: 'GET' });
|
|
102
|
+
const ret = await api.run({}, ctx, makeOptions());
|
|
103
|
+
assert.ok(ret, 'run 应返回结果');
|
|
104
|
+
const obj = JSON.parse(ret);
|
|
105
|
+
assert.ok(obj.error, '应返回错误对象');
|
|
106
|
+
assert.strictEqual(obj.error.code, 10000);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test('main 未定义脚本返回错误响应', async () => {
|
|
110
|
+
const api = new Api({
|
|
111
|
+
name: 'hello',
|
|
112
|
+
path: '/hello',
|
|
113
|
+
method: 'ALL',
|
|
114
|
+
dir: __dirname,
|
|
115
|
+
main: './fixtures/hello.js'
|
|
116
|
+
});
|
|
117
|
+
// 覆盖 main 为 null,模拟脚本无主函数
|
|
118
|
+
api.main = null;
|
|
119
|
+
const ctx = makeCtx({ path: '/hello', method: 'GET' });
|
|
120
|
+
const ret = await api.run({}, ctx, makeOptions());
|
|
121
|
+
assert.ok(ret);
|
|
122
|
+
const obj = JSON.parse(ret);
|
|
123
|
+
assert.ok(obj.error, '应返回错误对象');
|
|
124
|
+
assert.strictEqual(obj.error.code, 10000);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test('加载参数配置并校验通过', () => {
|
|
128
|
+
const api = new Api({
|
|
129
|
+
name: 'param',
|
|
130
|
+
path: '/param',
|
|
131
|
+
method: 'ALL',
|
|
132
|
+
dir: __dirname,
|
|
133
|
+
param_path: './fixtures/param.json'
|
|
134
|
+
});
|
|
135
|
+
assert.ok(api.param, '应加载参数驱动');
|
|
136
|
+
const error = api.checkParam({ name: '张三' }, {}, 'GET');
|
|
137
|
+
assert.strictEqual(error, null, '参数正确时校验应通过');
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
test('参数校验失败返回错误', () => {
|
|
141
|
+
const api = new Api({
|
|
142
|
+
name: 'param',
|
|
143
|
+
path: '/param',
|
|
144
|
+
method: 'ALL',
|
|
145
|
+
dir: __dirname,
|
|
146
|
+
param_path: './fixtures/param.json'
|
|
147
|
+
});
|
|
148
|
+
assert.ok(api.param, '应加载参数驱动');
|
|
149
|
+
const error = api.checkParam({}, {}, 'GET');
|
|
150
|
+
assert.ok(error, '缺少必填参数应返回错误');
|
|
151
|
+
assert.ok(error.error, '错误应为响应格式');
|
|
152
|
+
assert.strictEqual(error.error.code, 30001, '必填缺失错误码应为 30001');
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test('check 包含参数校验与身份校验', async () => {
|
|
156
|
+
const api = new Api({
|
|
157
|
+
name: 'hello',
|
|
158
|
+
path: '/hello',
|
|
159
|
+
method: 'ALL',
|
|
160
|
+
dir: __dirname,
|
|
161
|
+
main: './fixtures/hello.js',
|
|
162
|
+
check_param: false
|
|
163
|
+
});
|
|
164
|
+
const ctx = makeCtx({ path: '/hello', method: 'GET' });
|
|
165
|
+
const error = await api.check({}, ctx, makeOptions());
|
|
166
|
+
assert.strictEqual(error, null, '无需登录且无参数校验时应通过');
|
|
167
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
/**
|
|
5
|
+
* 接口主函数
|
|
6
|
+
* @param {object} ctx HTTP上下文
|
|
7
|
+
* @param {object} options 数据管理器
|
|
8
|
+
* @returns {object} 执行结果
|
|
9
|
+
*/
|
|
10
|
+
async main(params, ctx, options) {
|
|
11
|
+
const req = ctx.request;
|
|
12
|
+
return { query: req.query, body: req.body };
|
|
13
|
+
}
|
|
14
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
/**
|
|
5
|
+
* 接口主函数
|
|
6
|
+
* @param {object} ctx HTTP上下文
|
|
7
|
+
* @param {object} options 数据管理器
|
|
8
|
+
* @returns {object} 执行结果
|
|
9
|
+
*/
|
|
10
|
+
async main(params, ctx, options) {
|
|
11
|
+
return { code: 0, message: '成功', data: { hello: 'world' } };
|
|
12
|
+
}
|
|
13
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "param",
|
|
3
|
+
"title": "参数配置",
|
|
4
|
+
"get": {
|
|
5
|
+
"query": ["name"],
|
|
6
|
+
"query_required": ["name"]
|
|
7
|
+
},
|
|
8
|
+
"list": [
|
|
9
|
+
{
|
|
10
|
+
"name": "name",
|
|
11
|
+
"title": "名称",
|
|
12
|
+
"type": "string",
|
|
13
|
+
"string": {
|
|
14
|
+
"not_empty": true,
|
|
15
|
+
"max": 10
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
]
|
|
19
|
+
}
|
package/tests/helper.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 构造模拟 HTTP 请求上下文
|
|
5
|
+
* @param {object} cfg 配置项
|
|
6
|
+
* @param {string} cfg.path 请求路径
|
|
7
|
+
* @param {string} cfg.method 请求方法
|
|
8
|
+
* @param {object} cfg.query URL 参数
|
|
9
|
+
* @param {object} cfg.body 正文参数
|
|
10
|
+
* @param {object} cfg.headers 请求头
|
|
11
|
+
* @param {string} cfg.type 请求类型
|
|
12
|
+
* @returns {object} 请求上下文
|
|
13
|
+
*/
|
|
14
|
+
function makeCtx(cfg = {}) {
|
|
15
|
+
const req = {
|
|
16
|
+
path: cfg.path || '/',
|
|
17
|
+
method: cfg.method || 'GET',
|
|
18
|
+
query: cfg.query || {},
|
|
19
|
+
body: cfg.body || {},
|
|
20
|
+
headers: cfg.headers || {},
|
|
21
|
+
type: cfg.type || 'json',
|
|
22
|
+
url: cfg.url || (cfg.path || '/')
|
|
23
|
+
};
|
|
24
|
+
return {
|
|
25
|
+
request: req,
|
|
26
|
+
response: { type: '' },
|
|
27
|
+
session: {},
|
|
28
|
+
headers: req.headers,
|
|
29
|
+
set(key, value) {
|
|
30
|
+
this.response[key] = value;
|
|
31
|
+
},
|
|
32
|
+
get status() {
|
|
33
|
+
return this.response.status;
|
|
34
|
+
},
|
|
35
|
+
set status(value) {
|
|
36
|
+
this.response.status = value;
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* 构造数据管理器
|
|
43
|
+
* @returns {object} 数据管理器
|
|
44
|
+
*/
|
|
45
|
+
function makeOptions() {
|
|
46
|
+
return {
|
|
47
|
+
ret: null,
|
|
48
|
+
new() {
|
|
49
|
+
return this;
|
|
50
|
+
},
|
|
51
|
+
getObj() {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
module.exports = { makeCtx, makeOptions };
|