├── .eslintignore ├── .eslintrc ├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── docs.sh ├── docs ├── README.md ├── _config.yml └── api.md ├── examples ├── example.js ├── group.js └── interceptor.js ├── lib ├── application.js ├── di.js ├── interceptor.js └── routergroup.js ├── package-lock.json ├── package.json └── test ├── app.test.js ├── di.test.js └── router.test.js /.eslintignore: -------------------------------------------------------------------------------- 1 | /docs/ 2 | /coverage/ 3 | /node_modules/ 4 | /test/ 5 | /examples/ 6 | /dist/ 7 | test.js -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "sactive" 3 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | .idea 63 | *.lock 64 | .nyc* 65 | coverage 66 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /docs/ 2 | /node_modules/ 3 | /test/ 4 | /log/ 5 | /examples/ 6 | /.nyc_output/ 7 | /coverage/ 8 | .idea 9 | *.env 10 | *.log 11 | package-lock.json 12 | *.lock -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - 8 5 | - 9 6 | - 10 7 | - 11 8 | cache: 9 | directories: 10 | - node_modules 11 | before_script: 12 | - npm prune 13 | script: 14 | - npm run lint 15 | - npm run test:ci 16 | after_script: 17 | - npm install codecov 18 | - ./node_modules/.bin/codecov -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 SActive 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
Expose Application
class.
6 | Inherits from Koa
.
RouterGroup
](#RouterGroup)
22 | * [.USE](#Application+USE)
23 | * [.get|put|post|patch|delete|del|all](#Application+get|put|post|patch|delete|del|all) ⇒ [RouterGroup
](#RouterGroup)
24 | * [.use(fn)](#Application+use)
25 | * [.Listen()](#Application+Listen)
26 | * [.listen(...args)](#Application+listen)
27 | * [.listenTLS(options, ...args)](#Application+listenTLS)
28 | * [.group(prefix)](#Application+group) ⇒ [RouterGroup
](#RouterGroup)
29 | * [.parse(any)](#Application+parse) ⇒ [ 'Array' ].<String>
30 | * [.bindClass(name, instance)](#Application+bindClass)
31 | * [.bindFunction(name, instance)](#Application+bindFunction)
32 | * [.bindAny(name, instance)](#Application+bindAny)
33 | * [.getInstance(name)](#Application+getInstance) ⇒ \*
34 | * [.getInstances(names)](#Application+getInstances) ⇒ Array
35 | * [.getInstancesMap(names)](#Application+getInstancesMap) ⇒ Object
36 | * [.deleteInstance(name)](#Application+deleteInstance)
37 | * [.deleteInstances(names)](#Application+deleteInstances)
38 | * [.reset()](#Application+reset)
39 |
40 |
41 |
42 | ### new Application([options])
43 | Initialize a new `Application`.
Inherits from [`Koa`](https://koajs.com/).
44 |
45 |
46 | | Param | Type | Default | Description |
47 | | --- | --- | --- | --- |
48 | | [options] | Object
| | Application options. |
49 | | [options.env] | String
| 'development'
| Environment |
50 | | [options.keys] | [ 'Array' ].<String>
| | Signed cookie keys |
51 | | [options.proxy] | Boolean
| | Trust proxy headers |
52 | | [options.subdomainOffset] | Number
| | Subdomain offset |
53 | | [options.proxyIpHeader] | Boolean
| | proxy ip header, default to X-Forwarded-For |
54 | | [options.maxIpsCount] | Boolean
| | max ips read from proxy ip header, default to 0 (means infinity) |
55 | | [options.prefix] | String
| | prefix router paths |
56 |
57 |
58 |
59 | ### application.GET\|PUT\|POST\|PATCH\|DELETE\|DEL\|ALL ⇒ [RouterGroup
](#RouterGroup)
60 | Alias for koa-router verbs methods.
61 |
62 | **Kind**: instance property of [Application
](#Application)
63 |
64 | | Param | Type | Description |
65 | | --- | --- | --- |
66 | | path | String
| |
67 | | [middleware] | function
| route middleware(s) |
68 | | callback | function
| route callback |
69 |
70 |
71 |
72 | ### application.USE
73 | Alias for koa `use()`.
74 |
75 | **Kind**: instance property of [Application
](#Application)
76 |
77 | | Param | Type |
78 | | --- | --- |
79 | | fn | function
|
80 |
81 |
82 |
83 | ### application.get\|put\|post\|patch\|delete\|del\|all ⇒ [RouterGroup
](#RouterGroup)
84 | Create router verbs methods, where *verb* is one of the HTTP verbs such
as `app.get()` or `app.post()`.
85 |
86 | **Kind**: instance property of [Application
](#Application)
87 |
88 | | Param | Type | Description |
89 | | --- | --- | --- |
90 | | path | String
| |
91 | | [middleware] | function
| route middleware(s) |
92 | | callback | function
| route callback |
93 |
94 |
95 |
96 | ### application.use(fn)
97 | Register application level middleware.
98 |
99 | **Kind**: instance method of [Application
](#Application)
100 | **Access**: public
101 |
102 | | Param | Type | Description |
103 | | --- | --- | --- |
104 | | fn | function
| middleware |
105 |
106 |
107 |
108 | ### application.Listen()
109 | Alias for:
Koa app.listen()
110 |
111 | **Kind**: instance method of [Application
](#Application)
112 | **Access**: public
113 |
114 |
115 | ### application.listen(...args)
116 | Overwrite Koa app.listen()
117 |
118 | **Kind**: instance method of [Application
](#Application)
119 | **Access**: public
120 |
121 | | Param | Type | Description |
122 | | --- | --- | --- |
123 | | ...args | Mixed
| ... |
124 |
125 |
126 |
127 | ### application.listenTLS(options, ...args)
128 | Shorthand for:
https.createServer(options, app.callback()).listen(...)
129 |
130 | **Kind**: instance method of [Application
](#Application)
131 | **Access**: public
132 |
133 | | Param | Type | Description |
134 | | --- | --- | --- |
135 | | options | Object
| |
136 | | ...args | Mixed
| ... |
137 |
138 |
139 |
140 | ### application.group(prefix) ⇒ [RouterGroup
](#RouterGroup)
141 | Group router.
142 |
143 | **Kind**: instance method of [Application
](#Application)
144 | **Access**: public
145 |
146 | | Param | Type | Description |
147 | | --- | --- | --- |
148 | | prefix | String
| prefix router paths |
149 |
150 |
151 |
152 | ### application.parse(any) ⇒ [ 'Array' ].<String>
153 | Parse function|class parameters.
154 |
155 | **Kind**: instance method of [Application
](#Application)
156 | **Access**: public
157 |
158 | | Param | Type |
159 | | --- | --- |
160 | | any | Class
\| function
|
161 |
162 |
163 |
164 | ### application.bindClass(name, instance)
165 | Bind Class.
166 |
167 | **Kind**: instance method of [Application
](#Application)
168 | **Access**: public
169 |
170 | | Param | Type | Description |
171 | | --- | --- | --- |
172 | | name | String
| The name of the injected class. |
173 | | instance | Class
| Injected class. |
174 | | singleton. | Boolean
| |
175 |
176 |
177 |
178 | ### application.bindFunction(name, instance)
179 | Bind function.
180 |
181 | **Kind**: instance method of [Application
](#Application)
182 | **Access**: public
183 |
184 | | Param | Type | Description |
185 | | --- | --- | --- |
186 | | name | String
| The name of the injected function. |
187 | | instance | function
| Injected function. |
188 | | singleton. | Boolean
| |
189 |
190 |
191 |
192 | ### application.bindAny(name, instance)
193 | Bind Any.
194 |
195 | **Kind**: instance method of [Application
](#Application)
196 | **Access**: public
197 |
198 | | Param | Type | Description |
199 | | --- | --- | --- |
200 | | name | String
| The name of the injected function. |
201 | | instance | \*
| Injected instance. |
202 | | singleton. | Boolean
| |
203 |
204 |
205 |
206 | ### application.getInstance(name) ⇒ \*
207 | Get Instance.
208 |
209 | **Kind**: instance method of [Application
](#Application)
210 | **Returns**: \*
- - instance.
211 | **Access**: public
212 |
213 | | Param | Type | Description |
214 | | --- | --- | --- |
215 | | name | String
| The name of the injected instance. |
216 |
217 |
218 |
219 | ### application.getInstances(names) ⇒ Array
220 | Get Instances.
221 |
222 | **Kind**: instance method of [Application
](#Application)
223 | **Returns**: Array
- - instances.
224 | **Access**: public
225 |
226 | | Param | Type | Description |
227 | | --- | --- | --- |
228 | | names | [ 'Array' ].<String>
| The names of the injected instances. |
229 |
230 |
231 |
232 | ### application.getInstancesMap(names) ⇒ Object
233 | Get Instances map.
234 |
235 | **Kind**: instance method of [Application
](#Application)
236 | **Returns**: Object
- - instances.
237 | **Access**: public
238 |
239 | | Param | Type | Description |
240 | | --- | --- | --- |
241 | | names | [ 'Array' ].<String>
| The names of the injected instances. |
242 |
243 |
244 |
245 | ### application.deleteInstance(name)
246 | Delete Instance.
247 |
248 | **Kind**: instance method of [Application
](#Application)
249 | **Access**: public
250 |
251 | | Param | Type | Description |
252 | | --- | --- | --- |
253 | | name | String
| The name of the injected instance. |
254 |
255 |
256 |
257 | ### application.deleteInstances(names)
258 | Delete Instances.
259 |
260 | **Kind**: instance method of [Application
](#Application)
261 | **Access**: public
262 |
263 | | Param | Type | Description |
264 | | --- | --- | --- |
265 | | names | [ 'Array' ].<String>
| The names of the injected instances. |
266 |
267 |
268 |
269 | ### application.reset()
270 | Reset instance pool.
271 |
272 | **Kind**: instance method of [Application
](#Application)
273 | **Access**: public
274 |
275 |
276 | ## RouterGroup
277 | **Kind**: global class
278 | **Access**: public
279 |
280 | * [RouterGroup](#RouterGroup)
281 | * [new RouterGroup([options], app)](#new_RouterGroup_new)
282 | * [.GET|PUT|POST|PATCH|DELETE|DEL|ALL](#RouterGroup+GET|PUT|POST|PATCH|DELETE|DEL|ALL) ⇒ [RouterGroup
](#RouterGroup)
283 | * [.USE](#RouterGroup+USE) ⇒ [RouterGroup
](#RouterGroup)
284 | * [.get|put|post|patch|delete|del|all](#RouterGroup+get|put|post|patch|delete|del|all) ⇒ [RouterGroup
](#RouterGroup)
285 | * [.use(fn)](#RouterGroup+use) ⇒ [RouterGroup
](#RouterGroup)
286 |
287 |
288 |
289 | ### new RouterGroup([options], app)
290 | Initialize a new `RouterGroup`.
Inherits from [`koa-router`](https://github.com/ZijianHe/koa-router).
291 |
292 |
293 | | Param | Type | Description |
294 | | --- | --- | --- |
295 | | [options] | Object
| Application options. |
296 | | [options.prefix] | String
| prefix router paths. |
297 | | app | [Application
](#Application) | |
298 |
299 |
300 |
301 | ### routerGroup.GET\|PUT\|POST\|PATCH\|DELETE\|DEL\|ALL ⇒ [RouterGroup
](#RouterGroup)
302 | Alias for koa-router verbs methods.
303 |
304 | **Kind**: instance property of [RouterGroup
](#RouterGroup)
305 |
306 | | Param | Type | Description |
307 | | --- | --- | --- |
308 | | path | String
| |
309 | | [middleware] | function
| route middleware(s) |
310 | | callback | function
| route callback |
311 |
312 |
313 |
314 | ### routerGroup.USE ⇒ [RouterGroup
](#RouterGroup)
315 | Alias for koa-router `use()`.
316 |
317 | **Kind**: instance property of [RouterGroup
](#RouterGroup)
318 |
319 | | Param | Type |
320 | | --- | --- |
321 | | [path] | String
|
322 | | middleware | function
|
323 | | [...] | function
|
324 |
325 |
326 |
327 | ### routerGroup.get\|put\|post\|patch\|delete\|del\|all ⇒ [RouterGroup
](#RouterGroup)
328 | Overwrite all router verbs methods of [`Router`](https://github.com/ZijianHe/koa-router).
329 |
330 | **Kind**: instance property of [RouterGroup
](#RouterGroup)
331 |
332 | | Param | Type | Description |
333 | | --- | --- | --- |
334 | | path | String
| |
335 | | [middleware] | function
| route middleware(s) |
336 | | callback | function
| route callback |
337 |
338 |
339 |
340 | ### routerGroup.use(fn) ⇒ [RouterGroup
](#RouterGroup)
341 | Overwrite use.
342 |
343 | **Kind**: instance method of [RouterGroup
](#RouterGroup)
344 | **Access**: public
345 |
346 | | Param | Type |
347 | | --- | --- |
348 | | fn | function
|
349 |
350 |
--------------------------------------------------------------------------------
/examples/example.js:
--------------------------------------------------------------------------------
1 | const App = require('..');
2 |
3 | function run() {
4 | let app = new App();
5 | app.bindAny('age', 18);
6 | app.use(($age, $ctx, $next) => {
7 | console.log('app middleware1');
8 | console.log('age' + $age);
9 | $next();
10 | });
11 | app.use(($ctx, $age, $next, $address) => {
12 | console.log('app middleware2');
13 | console.log('age' + $age);
14 | console.log('address', $address);
15 | $next();
16 | });
17 | app.get('/users/:name', ($age, $ctx, $next, $address) => {
18 | console.log('users/name middleware1');
19 | console.log($ctx.params.name);
20 | console.log('age' + $age);
21 | console.log('address', $address);
22 | $ctx.params.id = 'user1';
23 | $next();
24 | }, ($age, $ctx, $next, $address) => {
25 | console.log('users/name middleware2');
26 | console.log($ctx.params.name, $ctx.params.id);
27 | console.log('age' + $age);
28 | console.log('address', $address);
29 | $ctx.body = 'hello, ' + $ctx.path;
30 | });
31 | app.get('/users/', ($age, $ctx, $next, $address) => {
32 | console.log('users middleware1');
33 | console.log('age' + $age);
34 | console.log('address', $address);
35 | $ctx.params.id = 'user1';
36 | $next();
37 | }, ($age, $ctx, $next, $address) => {
38 | console.log('users middleware2');
39 | console.log($ctx.params.id);
40 | console.log('age' + $age);
41 | $ctx.body = 'hello, ' + $ctx.path;
42 | });
43 | app.use(($address, $ctx, $next, $age, $getAddress) => {
44 | console.log('app middleware3');
45 | console.log('age' + $age);
46 | console.log('address', $address);
47 | console.log('getAddress', $getAddress);
48 | $next();
49 | });
50 | app.bindAny('address', 'shanghai');
51 | app.bindFunction('getAddress', $address => {
52 | return $address;
53 | });
54 | app.listen(8080);
55 | }
56 |
57 | run();
--------------------------------------------------------------------------------
/examples/group.js:
--------------------------------------------------------------------------------
1 | const App = require('..');
2 |
3 | const app = new App();
4 | app.bindAny('name', 'pooky');
5 |
6 | app.use(($ctx, $name, $next) => {
7 | $ctx.testname1 = $name;
8 | $next();
9 | });
10 |
11 | app.group('v1')
12 | .get('/users/:name', ($ctx, $next, $name) => {
13 | $ctx.body = {'name': $ctx.params.name, 'testname1': $ctx.testname1, 'testname2': $name};
14 | });
15 | app.group('v2/')
16 | .get('/users/:name', ($name, $ctx, $next) => {
17 | $ctx.response.body = {'name': $ctx.params.name, 'testname1': $ctx.testname1, 'testname2': $name};
18 | });
19 | app.group('/v3/')
20 | .get('/users/:name', ($ctx, $name, $next) => {
21 | $ctx.body = {'name': $ctx.params.name, 'testname1': $ctx.testname1, 'testname2': $name};
22 | });
23 | app.group('/v4/')
24 | .del('/users/:name', ($ctx, $name, $next) => {
25 | $ctx.body = `v4 delete user ${$ctx.params.name} success`;
26 | })
27 | .delete('/products/:name', ($ctx, $name, $next) => {
28 | $ctx.body = `v4 delete product ${$ctx.params.name} success`;
29 | });
30 | app.delete('/users/:name', ($ctx, $name, $next) => {
31 | $ctx.body = `app delete user ${$ctx.params.name} success`;
32 | })
33 | app.del('/products/:name', ($ctx, $name, $next) => {
34 | $ctx.body = `app delete product ${$ctx.params.name} success`;
35 | })
36 |
37 | app.listen(8080);
--------------------------------------------------------------------------------
/examples/interceptor.js:
--------------------------------------------------------------------------------
1 | const App = require('..');
2 | const app = new App();
3 |
4 | app.bindAny('name1', 'name' + 1);
5 |
6 | app.use(($ctx, $name1, $next) => {
7 | $ctx.name1 = $name1;
8 | $next();
9 | });
10 |
11 | app.bindAny('name2', 'name' + 2);
12 |
13 | app.group('v1')
14 | .get('/users/:name', ($ctx, $next, $name2) => {
15 | $ctx.body = {'name': $ctx.params.name, 'name1': $ctx.name1, 'name2': $name2};
16 | $next();
17 | }, ($ctx, $next, $name3) => {
18 | $ctx.body.name3 = $name3;
19 | $next();
20 | }, ($ctx, $next, $name4) => {
21 | $ctx.name4 = $name4;
22 | if ($ctx.params.name === CONSTANT_MOCK.URL_NAME) {
23 | throw new Error($ctx.params.name);
24 | }
25 | $next();
26 | }, ($ctx, $next, $name5) => {
27 | $ctx.body.name4 = $ctx.name4;
28 | $ctx.body.name5 = $name5;
29 | if ($ctx.params.name === 'name') {
30 | throw new Error($ctx.params.name);
31 | }
32 | });
33 |
34 | app.bindAny('name3', 'name' + 3);
35 | app.bindAny('name4', 'name' + 4);
36 | app.bindAny('name5', 'name' + 5);
37 |
38 | app.interceptors.errors.use((err, ctx) => {
39 | ctx.body = {
40 | code: 500,
41 | data: {},
42 | msg: err.message
43 | };
44 | });
45 |
46 | app.interceptors.response.use(ctx => {
47 | let data = ctx.body;
48 | ctx.body = {
49 | code: 200,
50 | data: data,
51 | msg: 'ok'
52 | };
53 | });
54 |
55 | app.listen(8080);
--------------------------------------------------------------------------------
/lib/application.js:
--------------------------------------------------------------------------------
1 | const debug = require('debug')('active:application');
2 | const https = require('https');
3 | const Koa = require('koa');
4 | const methods = require('methods');
5 | const Di = require('./di');
6 | const RouterGroup = require('./routergroup');
7 | const Interceptor = require('./interceptor');
8 |
9 | /**
10 | * Expose `Application` class.
11 | * Inherits from [`Koa`]{@link https://koajs.com/}.
12 | */
13 | class Application extends Koa {
14 | /**
15 | * Initialize a new `Application`.
16 | * Inherits from [`Koa`]{@link https://koajs.com/}.
17 | * @example
18 | *
19 | * Basic usage:
20 | *
21 | * ```javascript
22 | * const App = require('sactive-web');
23 | *
24 | * var app = new App();
25 | *
26 | * app.get('/', (ctx, next) => {
27 | * // ctx.router available
28 | * });
29 | *
30 | * app.listen(8080);
31 | * ```
32 | *
33 | * @class
34 | * @public
35 | * @param {Object} [options] - Application options.
36 | * @param {String} [options.env='development'] Environment
37 | * @param {String[]} [options.keys] Signed cookie keys
38 | * @param {Boolean} [options.proxy] Trust proxy headers
39 | * @param {Number} [options.subdomainOffset] Subdomain offset
40 | * @param {Boolean} [options.proxyIpHeader] proxy ip header, default to X-Forwarded-For
41 | * @param {Boolean} [options.maxIpsCount] max ips read from proxy ip header, default to 0 (means infinity)
42 | * @param {String} [options.prefix] prefix router paths
43 | */
44 | constructor(options = {}) {
45 | super(options);
46 | // see https://github.com/koajs/koa/issues/652
47 | this.context.ErrorIntercepted = false;
48 | this.mstack = [];
49 | this.interceptors = {
50 | response: new Interceptor(),
51 | errors: new Interceptor()
52 | };
53 | this.i = new Di();
54 | this.router = new RouterGroup({prefix: this.calculatePrefixPath(options.prefix)}, this);
55 | this.routergroup = [this.router];
56 | this.init();
57 | this.on('ErrorIntercepted', this.errorInterceptor);
58 | }
59 |
60 | /**
61 | * Alias for koa-router verbs methods.
62 | *
63 | * @name GET|PUT|POST|PATCH|DELETE|DEL|ALL
64 | * @memberof Application.prototype
65 | * @param {String} path
66 | * @param {Function=} middleware route middleware(s)
67 | * @param {Function} callback route callback
68 | * @returns {RouterGroup}
69 | */
70 |
71 | /**
72 | * Alias for koa `use()`.
73 | *
74 | * @name USE
75 | * @memberof Application.prototype
76 | * @param {Function} fn
77 | */
78 |
79 | /**
80 | * Create router verbs methods, where *verb* is one of the HTTP verbs such
81 | * as `app.get()` or `app.post()`.
82 | *
83 | * @name get|put|post|patch|delete|del|all
84 | * @memberof Application.prototype
85 | * @param {String} path
86 | * @param {Function=} middleware route middleware(s)
87 | * @param {Function} callback route callback
88 | * @returns {RouterGroup}
89 | */
90 |
91 | /**
92 | * Initialize.
93 | *
94 | * @private
95 | */
96 | init() {
97 | methods.push('all', 'del'); // `app.del()` alias for `router.delete()`
98 | methods.forEach(method => {
99 | this[method] = (path, ...middleware) => {
100 | return this.router[method](path, ...middleware);
101 | };
102 | this[method.toUpperCase()] = (path, ...middleware) => {
103 | return this.router[method.toUpperCase()](path, ...middleware);
104 | };
105 | });
106 | // Alias for koa `app.use()`
107 | this['USE'] = super['use'];
108 | }
109 |
110 | /**
111 | * Register application level middleware.
112 | *
113 | * @param {Function} fn middleware
114 | * @override
115 | * @public
116 | */
117 | use(fn) {
118 | if (typeof fn !== 'function') throw new TypeError('middleware must be a function!');
119 | debug('use %s', fn._name || fn.name || '-');
120 | this.mstack.push(fn);
121 | return this;
122 | }
123 |
124 | /**
125 | * Hack middleware.
126 | *
127 | * @override
128 | * @private
129 | */
130 | hack(fn) {
131 | let params = this.i.parse(fn);
132 | let instances = this.i.getInstancesMap(params);
133 | super.use(async (ctx, next) => {
134 | instances.$ctx = ctx;
135 | instances.$next = next;
136 | try {
137 | await fn(...Object.values(instances));
138 | } catch (e) {
139 | // set ctx.ErrorIntercepted=true, avoid response interceptor handle the error
140 | ctx.ErrorIntercepted = true;
141 | return this.emit('ErrorIntercepted', e, ctx);
142 | }
143 | });
144 | debug('hack %s', fn._name || fn.name || '-');
145 | return this;
146 | }
147 |
148 | /**
149 | * Alias for:
150 | * Koa app.listen()
151 | *
152 | * @public
153 | */
154 | Listen(...args) {
155 | debug('koa listen');
156 | return super.listen(...args);
157 | }
158 |
159 | /**
160 | * Overwrite Koa app.listen()
161 | *
162 | * @param {Mixed} args ...
163 | * @override
164 | * @public
165 | */
166 | listen(...args) {
167 | debug('listen');
168 | // register intercept middleware
169 | super.use(this.responseInterceptor());
170 | // hack all routers
171 | this.mstack.forEach(m => {
172 | this.hack(m);
173 | });
174 | this.routergroup.forEach(rg => {
175 | super.use(rg.routes());
176 | });
177 | return super.listen(...args);
178 | }
179 |
180 | /**
181 | * Shorthand for:
182 | * https.createServer(options, app.callback()).listen(...)
183 | *
184 | * @param {Object} options
185 | * @param {Mixed} args ...
186 | * @public
187 | */
188 | listenTLS(options, ...args) {
189 | debug('listenTLS');
190 | this.routergroup.forEach(rg => {
191 | super.use(rg.routes());
192 | });
193 | const server = https.createServer(options, this.callback());
194 | return server.listen(...args);
195 | }
196 |
197 | /**
198 | * Group router.
199 | *
200 | * @param {String} prefix prefix router paths
201 | * @return {RouterGroup}
202 | * @public
203 | */
204 | group(prefix) {
205 | if (typeof prefix !== 'string') throw new TypeError('prefix must be a string!');
206 | if (prefix === '') throw new Error('prefix cannot be a empty!');
207 | debug('group path - %s', prefix);
208 | let rg = new RouterGroup({prefix: this.calculatePrefixPath(prefix)}, this);
209 | this.routergroup.push(rg);
210 | return rg;
211 | }
212 |
213 | /**
214 | * Response Interceptor.
215 | *
216 | * @return {Function}
217 | * @private
218 | */
219 | responseInterceptor() {
220 | return async (ctx, next) => {
221 | try {
222 | await next();
223 | if (!this.interceptors.response.enabled || ctx.ErrorIntercepted) {
224 | return;
225 | }
226 | return this.interceptors.response.do(ctx);
227 | } catch (e) {
228 | this.emit('ErrorIntercepted', e, ctx);
229 | }
230 | };
231 | }
232 |
233 | /**
234 | * Error Interceptor.
235 | *
236 | * @return {Function}
237 | * @private
238 | */
239 | errorInterceptor(e, ctx) {
240 | if (!this.interceptors.errors.enabled) {
241 | throw e;
242 | }
243 | this.interceptors.errors.do(e, ctx);
244 | }
245 |
246 | /**
247 | * Parse function|class parameters.
248 | *
249 | * @param {Class|Function} any
250 | * @return {String[]}
251 | * @public
252 | */
253 | parse(any) {
254 | return this.i.parse(any);
255 | }
256 |
257 | /**
258 | * Bind Class.
259 | *
260 | * @param {String} name - The name of the injected class.
261 | * @param {Class} instance - Injected class.
262 | * @param {Boolean} singleton.
263 | * @public
264 | */
265 | bindClass(name, instance, singleton) {
266 | return this.i.bindClass(name, instance, singleton);
267 | }
268 |
269 | /**
270 | * Bind function.
271 | *
272 | * @param {String} name - The name of the injected function.
273 | * @param {Function} instance - Injected function.
274 | * @param {Boolean} singleton.
275 | * @public
276 | */
277 | bindFunction(name, instance, singleton) {
278 | return this.i.bindFunction(name, instance, singleton);
279 | }
280 |
281 | /**
282 | * Bind Any.
283 | *
284 | * @param {String} name - The name of the injected function.
285 | * @param {*} instance - Injected instance.
286 | * @param {Boolean} singleton.
287 | * @public
288 | */
289 | bindAny(name, instance, singleton) {
290 | return this.i.bindAny(name, instance, singleton);
291 | }
292 |
293 | /**
294 | * Get Instance.
295 | *
296 | * @param {String} name - The name of the injected instance.
297 | * @return {*} - instance.
298 | * @public
299 | */
300 | getInstance(name) {
301 | return this.i.getInstance(name);
302 | }
303 |
304 | /**
305 | * Get Instances.
306 | *
307 | * @param {String[]} names - The names of the injected instances.
308 | * @return {Array} - instances.
309 | * @public
310 | */
311 | getInstances(names) {
312 | return this.i.getInstances(names);
313 | }
314 |
315 | /**
316 | * Get Instances map.
317 | *
318 | * @param {String[]} names - The names of the injected instances.
319 | * @return {Object} - instances.
320 | * @public
321 | */
322 | getInstancesMap(names) {
323 | return this.i.getInstancesMap(names);
324 | }
325 |
326 | /**
327 | * Delete Instance.
328 | *
329 | * @param {String} name - The name of the injected instance.
330 | * @public
331 | */
332 | deleteInstance(name) {
333 | this.i.deleteInstance(name);
334 | }
335 |
336 | /**
337 | * Delete Instances.
338 | *
339 | * @param {String[]} names - The names of the injected instances.
340 | * @public
341 | */
342 | deleteInstances(names) {
343 | this.i.deleteInstances(names);
344 | }
345 |
346 | /**
347 | * Reset instance pool.
348 | *
349 | * @public
350 | */
351 | reset() {
352 | this.i.reset();
353 | }
354 |
355 | /**
356 | * Calculate router prefix paths.
357 | *
358 | * @private
359 | */
360 | calculatePrefixPath(relativePath) {
361 | if (!relativePath || relativePath === '') {
362 | return '';
363 | }
364 | if (!relativePath.startsWith('/')) {
365 | relativePath = '/' + relativePath;
366 | }
367 | if (relativePath.endsWith('/')) {
368 | relativePath = relativePath.substr(0, relativePath.length - 1);
369 | }
370 | debug('router prefix - %s', relativePath);
371 | return relativePath;
372 | }
373 | }
374 |
375 | module.exports = Application;
--------------------------------------------------------------------------------
/lib/di.js:
--------------------------------------------------------------------------------
1 | const debug = require('debug')('active:di');
2 | const extend = require('extend');
3 | const getParameterNames = require('@captemulation/get-parameter-names');
4 |
5 | const BIND_TYPE = {
6 | CLASS: 0,
7 | FUNCTION: 1,
8 | ANY: 2
9 | };
10 |
11 | const INJECTOR_PREFIX = '$';
12 | const INJECTOR_NAME = 'injector';
13 |
14 | class Di {
15 | /**
16 | * Initialize a new `Di`.
17 | * Dependencies injector.
18 | *
19 | * @class
20 | * @private
21 | */
22 | constructor() {
23 | this.pool = new Map();
24 | this.cache = new Map();
25 | this.bindClass(INJECTOR_NAME, this);
26 | }
27 |
28 | parse(fn) {
29 | return getParameterNames(fn);
30 | }
31 |
32 | bind(name, any, opt) {
33 | if (typeof name !== 'string') {
34 | throw new Error('instance name must be a string.');
35 | }
36 | if (!any) {
37 | throw new Error('instance cannot be null.');
38 | }
39 | let instanceName = INJECTOR_PREFIX + name;
40 | if (this.pool.has(instanceName)) {
41 | throw new Error(`instance: ${name} has already bound.`);
42 | }
43 | this.pool.set(instanceName, {
44 | attribute: any,
45 | options: opt
46 | });
47 | }
48 |
49 | bindAny(name, any, singleton = true) {
50 | debug(`bind any: ${name}, singleton: ${singleton}`);
51 | return this.bind(name, any, {
52 | type: BIND_TYPE.ANY,
53 | singleton: singleton
54 | });
55 | }
56 |
57 | bindClass(name, any, singleton = true) {
58 | debug(`bind class: ${name}, singleton: ${singleton}`);
59 | return this.bind(name, any, {
60 | type: BIND_TYPE.CLASS,
61 | singleton: singleton
62 | });
63 | }
64 |
65 | bindFunction(name, any, singleton = true) {
66 | debug(`bind function: ${name}, singleton: ${singleton}`);
67 | if (typeof any !== 'function') throw new TypeError('instance must be a function!');
68 | return this.bind(name, any, {
69 | type: BIND_TYPE.FUNCTION,
70 | singleton: singleton
71 | });
72 | }
73 |
74 | getInstances(names) {
75 | if (!Array.isArray(names)) {
76 | throw new Error('instance names must be an array.');
77 | }
78 | let instances = [];
79 | for (let i = 0; i < names.length; i ++) {
80 | instances.push(this.getInstance(names[i]));
81 | }
82 | return instances;
83 | }
84 |
85 | getInstancesMap(names) {
86 | if (!Array.isArray(names)) {
87 | throw new Error('instance names must be an array.');
88 | }
89 | let instances = {};
90 | for (let i = 0; i < names.length; i ++) {
91 | instances[names[i]] = this.getInstance(names[i]);
92 | }
93 | return instances;
94 | }
95 |
96 | getInstance(name) {
97 | debug(`get instance: ${name}`);
98 | if (this.cache.has(name)) {
99 | return this.cache.get(name);
100 | }
101 | if (!this.pool.has(name)) {
102 | return null;
103 | }
104 | let instance = null;
105 | let copy = extend(true, {}, this.pool.get(name));
106 | if (copy.options.type === BIND_TYPE.FUNCTION) {
107 | instance = this.getInstanceAsFunction(copy);
108 | } else if (copy.options.type === BIND_TYPE.CLASS) {
109 | instance = this.getInstanceAsClass(copy);
110 | } else {
111 | instance = this.getInstanceAsAny(copy);
112 | }
113 | if (copy.options.singleton === true) {
114 | this.cache.set(name, instance);
115 | }
116 | return instance;
117 | }
118 |
119 | getInstanceAsAny(any) {
120 | return any.attribute;
121 | }
122 |
123 | getInstanceAsClass(any) {
124 | let all = this.parse(any.attribute);
125 | let matched = this.match(all);
126 | return new any.attribute(...matched);
127 | }
128 |
129 | getInstanceAsFunction(any) {
130 | let all = this.parse(any.attribute);
131 | let matched = this.match(all);
132 | return any.attribute.apply(null, matched);
133 | }
134 |
135 | deleteInstance(name) {
136 | if (typeof name !== 'string') {
137 | throw new Error('instance name must be a string.');
138 | }
139 | if (name === '') {
140 | return;
141 | }
142 | this.cache.delete(name);
143 | this.pool.delete(name);
144 | }
145 |
146 | deleteInstances(names) {
147 | if (!Array.isArray(names)) {
148 | throw new Error('instance names must be an array.');
149 | }
150 | for (let i = 0; i < names.length; i ++) {
151 | this.deleteInstance(names[i]);
152 | }
153 | }
154 |
155 | reset() {
156 | debug('reset pool');
157 | this.pool = new Map();
158 | this.cache = new Map();
159 | }
160 |
161 | match(all) {
162 | let matched = [];
163 | for (let i = 0; i < all.length; i ++) {
164 | if (all[i] && all[i].startsWith(INJECTOR_PREFIX)) {
165 | matched.push(this.getInstance(all[i]));
166 | } else {
167 | matched.push(null);
168 | }
169 | }
170 | return matched;
171 | }
172 | };
173 |
174 | module.exports = Di;
--------------------------------------------------------------------------------
/lib/interceptor.js:
--------------------------------------------------------------------------------
1 | module.exports = class Interceptor {
2 | /**
3 | * Initialize a new `Interceptor`.
4 | *
5 | * @class
6 | * @public
7 | */
8 | constructor() {
9 | this.fn = null;
10 | this.enabled = false;
11 | }
12 |
13 | /**
14 | * Register a interceptor.
15 | *
16 | * @public
17 | */
18 | use(fn) {
19 | if (typeof fn !== 'function') throw new TypeError('interceptor must be a function!');
20 | this.enabled = true;
21 | this.fn = fn;
22 | }
23 |
24 | /**
25 | * Execute a interceptor.
26 | *
27 | * @private
28 | */
29 | do(...arg) {
30 | this.fn(...arg);
31 | }
32 | };
--------------------------------------------------------------------------------
/lib/routergroup.js:
--------------------------------------------------------------------------------
1 | const debug = require('debug')('active:router');
2 | const Router = require('koa-router');
3 | const methods = require('methods');
4 | const compose = require('koa-compose');
5 |
6 | class RouterGroup extends Router {
7 | /**
8 | * Initialize a new `RouterGroup`.
9 | * Inherits from [`koa-router`]{@link https://github.com/ZijianHe/koa-router}.
10 | *
11 | * @class
12 | * @public
13 | * @param {Object} [options] - Application options.
14 | * @param {String} [options.prefix] prefix router paths.
15 | * @param {Application} app
16 | */
17 | constructor(options, app) {
18 | super(options);
19 | this.rstack = [];
20 | this.mstack = [];
21 | this.app = app;
22 | this.i = this.app.i;
23 | this.init();
24 | }
25 | /**
26 | * Alias for koa-router verbs methods.
27 | *
28 | * @name GET|PUT|POST|PATCH|DELETE|DEL|ALL
29 | * @memberof RouterGroup.prototype
30 | * @param {String} path
31 | * @param {Function=} middleware route middleware(s)
32 | * @param {Function} callback route callback
33 | * @returns {RouterGroup}
34 | */
35 |
36 | /**
37 | * Alias for koa-router `use()`.
38 | *
39 | * @name USE
40 | * @memberof RouterGroup.prototype
41 | * @param {String=} path
42 | * @param {Function} middleware
43 | * @param {Function=} ...
44 | * @returns {RouterGroup}
45 | */
46 |
47 | /**
48 | * Overwrite all router verbs methods of [`Router`]{@link https://github.com/ZijianHe/koa-router}.
49 | *
50 | * @name get|put|post|patch|delete|del|all
51 | * @memberof RouterGroup.prototype
52 | * @param {String} path
53 | * @param {Function=} middleware route middleware(s)
54 | * @param {Function} callback route callback
55 | * @returns {RouterGroup}
56 | */
57 |
58 | /**
59 | * Initialize, overwrite all router methods.
60 | *
61 | * @private
62 | */
63 | init() {
64 | methods.push('all');
65 | methods.forEach(method => {
66 | this[method] = (path, ...middleware) => {
67 | let stack = Array.isArray(middleware) ? middleware : [middleware];
68 | stack.forEach(fn => {
69 | let type = (typeof fn);
70 | if (type !== 'function') {
71 | throw new Error(
72 | method + ' `' + path + '`: `middleware` ' +
73 | 'must be a function, not `' + type + '`'
74 | );
75 | }
76 | });
77 | this.rstack.push({
78 | method: method,
79 | path: path,
80 | stack: stack
81 | });
82 | return this;
83 | };
84 | this[method.toUpperCase()] = super[method];
85 | });
86 |
87 | // Alias for `RouterGroup.delete()`
88 | this['del'] = this['delete'];
89 |
90 | // Alias for koa-router `use()` and `del()`
91 | this['USE'] = super['use'];
92 | this['DEL'] = super['delete'];
93 | }
94 |
95 | /**
96 | * Overwrite use.
97 | *
98 | * @param {Function} fn
99 | * @returns {RouterGroup}
100 | * @override
101 | * @public
102 | */
103 | use(fn) {
104 | if (typeof fn !== 'function') throw new TypeError('middleware must be a function!');
105 | debug('use %s', fn._name || fn.name || '-');
106 | this.mstack.push(fn);
107 | return this;
108 | }
109 |
110 | /**
111 | * Overwrite routes.
112 | *
113 | * @private
114 | */
115 | routes() {
116 | this.mstack.forEach(fn => {
117 | this.hackM(fn);
118 | });
119 | this.rstack.forEach(r => {
120 | this.hack(r.method, r.path, r.stack);
121 | });
122 | return super.routes();
123 | }
124 |
125 | /**
126 | * Hack middleware.
127 | *
128 | * @param {Function} fn
129 | * @private
130 | */
131 | hackM(fn) {
132 | this.mstack.forEach(fn => {
133 | let instances = this.i.getInstancesMap(this.i.parse(fn));
134 | super.use(async (ctx, next) => {
135 | instances.$ctx = ctx;
136 | instances.$next = next;
137 | try {
138 | await fn(...Object.values(instances));
139 | } catch (e) {
140 | // set ctx.ErrorIntercepted=true, avoid response interceptor handle the error
141 | ctx.ErrorIntercepted = true;
142 | return this.app.emit('ErrorIntercepted', e, ctx);
143 | }
144 | });
145 | debug('hack %s', fn._name || fn.name || '-');
146 | });
147 | return this;
148 | }
149 |
150 | /**
151 | * Hack router.
152 | *
153 | * @private
154 | */
155 | hack(method, path, stack) {
156 | let chain = [];
157 |
158 | let dispatch = (ctx, next) => {
159 | debug('%s %s', ctx.method, ctx.path);
160 |
161 | stack.forEach(fn => {
162 | let params = this.i.parse(fn);
163 | let instances = this.i.getInstancesMap(params);
164 | chain.push(async (ctx, next) => {
165 | instances.$ctx = ctx;
166 | instances.$next = next;
167 | try {
168 | await fn(...Object.values(instances));
169 | } catch (e) {
170 | // set ctx.ErrorIntercepted=true, avoid response interceptor handle the error
171 | ctx.ErrorIntercepted = true;
172 | return this.app.emit('ErrorIntercepted', e, ctx);
173 | }
174 | });
175 | }, this);
176 |
177 | return compose(chain)(ctx, next);
178 | };
179 |
180 | super[method](path, dispatch);
181 | return this;
182 | }
183 | }
184 |
185 | module.exports = RouterGroup;
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "sactive-web",
3 | "version": "3.0.0-rc6",
4 | "description": "A dependency injection web framework for Node.js",
5 | "main": "lib/application.js",
6 | "scripts": {
7 | "lint": "eslint .",
8 | "test": "mocha --require babel-register --check-leaks --bail --exit",
9 | "test:cov": "nyc --reporter=html --reporter=text mocha --require babel-register --check-leaks --bail --exit",
10 | "test:ci": "nyc --reporter=html --reporter=text-lcov > coverage.lcov mocha --require babel-register --check-leaks --bail --exit",
11 | "build:doc": "./docs.sh"
12 | },
13 | "repository": {
14 | "type": "git",
15 | "url": "git+https://github.com/shipengqi/sactive-web.git"
16 | },
17 | "keywords": [
18 | "web framework",
19 | "dependency injection",
20 | "koa2",
21 | "koa router"
22 | ],
23 | "author": "pooky",
24 | "license": "MIT",
25 | "bugs": {
26 | "url": "https://github.com/shipengqi/sactive-web/issues"
27 | },
28 | "homepage": "https://github.com/shipengqi/sactive-web#readme",
29 | "dependencies": {
30 | "@captemulation/get-parameter-names": "^1.2.0",
31 | "debug": "^4.1.1",
32 | "extend": "^3.0.1",
33 | "koa": "^2.12.0",
34 | "koa-compose": "^4.1.0",
35 | "koa-router": "^7.4.0",
36 | "methods": "^1.1.2"
37 | },
38 | "devDependencies": {
39 | "babel-register": "^6.26.0",
40 | "chai": "^4.1.2",
41 | "eslint": "^8.23.1",
42 | "eslint-config-sactive": "^2.0.0",
43 | "eslint-config-standard": "^11.0.0",
44 | "eslint-plugin-import": "^2.12.0",
45 | "eslint-plugin-node": "^6.0.1",
46 | "eslint-plugin-promise": "^3.8.0",
47 | "eslint-plugin-standard": "^3.1.0",
48 | "jsdoc-to-markdown": "^7.1.1",
49 | "koa-body": "^4.1.2",
50 | "mocha": "^7.1.2",
51 | "nyc": "^15.0.1",
52 | "supertest": "^4.0.2"
53 | },
54 | "engines": {
55 | "node": "^6.10.1 || ^7.10.1 || >= 8.1.4"
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/test/app.test.js:
--------------------------------------------------------------------------------
1 | const App = require('..');
2 | const koaBody = require('koa-body');
3 | const request = require('supertest');
4 | const {expect} = require('chai');
5 |
6 | let server = null;
7 | describe('Application tests', () => {
8 | before(() => {
9 | const app = new App();
10 | app.bindAny('age', 18);
11 | app.USE(koaBody());
12 | app.use(($age, $ctx, $next) => {
13 | $next();
14 | });
15 | app.use(($ctx, $age, $next, $address) => {
16 | $next();
17 | });
18 | app.get('/users/:name', ($ctx, $next) => {
19 | if ($ctx.params.name === 'xiaoming') {
20 | $ctx.response.body = {'name': 'xiaoming'};
21 | return;
22 | }
23 | $ctx.response.body = {'name': 'unknown'};
24 | });
25 | app.group('/v1')
26 | .get('/users/:name', ($ctx, $next) => {
27 | if ($ctx.params.name === 'xiaoming') {
28 | $ctx.response.body = {'name': 'xiaoming'};
29 | return;
30 | }
31 | $ctx.response.body = {'name': 'unknown'};
32 | });
33 | app.use(($address, $ctx, $next, $age, $getAddress) => {
34 | $next();
35 | });
36 | app.GET('/apps/:name', (ctx, next) => {
37 | ctx.body = ctx.params.name;
38 | });
39 | app.POST('/apps/:name', (ctx, next) => {
40 | ctx.testbody = ctx.request.body;
41 | next();
42 | }, (ctx, next) => {
43 | ctx.body = {name: ctx.params.name, testbody: ctx.testbody};
44 | });
45 | app.DEL('/apps/:name', (ctx, next) => {
46 | ctx.body = 'ok';
47 | });
48 | app.DELETE('/users/:name', (ctx, next) => {
49 | ctx.body = 'ok';
50 | });
51 | app.bindAny('address', 'shanghai');
52 | app.bindFunction('getAddress', $address => {
53 | return $address;
54 | });
55 | server = app.listen(9000);
56 | });
57 | it('Should get response: {name: xiaoming}, url: /users/xiaoming', done => {
58 | request(server)
59 | .get('/users/xiaoming')
60 | .expect(200)
61 | .end((err, res) => {
62 | expect(res.body).to.eql({'name': 'xiaoming'});
63 | done();
64 | });
65 | });
66 | it('Should get response: {name: unknown}, url: /users/xiaoqiang', done => {
67 | request(server)
68 | .get('/users/xiaoqiang')
69 | .expect(200)
70 | .end((err, res) => {
71 | expect(res.body).to.eql({'name': 'unknown'});
72 | done();
73 | });
74 | });
75 | it('Should get response: {name: xiaoming}, url: /v1/users/xiaoming', done => {
76 | request(server)
77 | .get('/v1/users/xiaoming')
78 | .expect(200)
79 | .end((err, res) => {
80 | expect(res.body).to.eql({'name': 'xiaoming'});
81 | done();
82 | });
83 | });
84 | it('Should get response: pooky, url: /apps/pooky', done => {
85 | request(server)
86 | .get('/apps/pooky')
87 | .expect(200)
88 | .end((err, res) => {
89 | expect(res.text).to.eql('pooky');
90 | done();
91 | });
92 | });
93 | it('Should get response: ok, url: /apps/pooky', done => {
94 | request(server)
95 | .del('/apps/pooky')
96 | .expect(200)
97 | .end((err, res) => {
98 | expect(res.text).to.eql('ok');
99 | done();
100 | });
101 | });
102 | it('Should get response: {name: pooky, testbody: {name: koa-body}}, url: /apps/pooky', done => {
103 | request(server)
104 | .post('/apps/pooky')
105 | .send({name: 'koa-body'})
106 | .expect(200)
107 | .end((err, res) => {
108 | expect(res.body).to.eql({name: 'pooky', testbody: {name: 'koa-body'}});
109 | done();
110 | });
111 | });
112 | });
--------------------------------------------------------------------------------
/test/di.test.js:
--------------------------------------------------------------------------------
1 | const Di = require('../lib/di');
2 | const {expect} = require('chai');
3 |
4 | class Router {
5 | constructor($logger) {
6 | this.logger = $logger;
7 | }
8 | test() {
9 | return 'router test';
10 | }
11 | }
12 |
13 | class Logger {
14 | test() {
15 | return 'log test';
16 | }
17 | }
18 |
19 | class Utils {
20 | constructor($logger, $router, $async) {
21 | this.logger = $logger;
22 | this.router = $router;
23 | this.$async = $async;
24 | }
25 | logTest() {
26 | return this.logger.test();
27 | }
28 | routerTest() {
29 | return this.router.test();
30 | }
31 | test() {
32 | return 'utils test';
33 | }
34 | }
35 |
36 | function unInjectFunc(unknown) {
37 | return unknown;
38 | }
39 |
40 | function testFunc($logger) {
41 | return $logger.test() + ' function test';
42 | }
43 |
44 | async function asyncFunc($logger) {
45 | return $logger.test() + ' async function test';
46 | }
47 |
48 | const arrowFunc = $logger => {
49 | return $logger.test() + ' arrow function test';
50 | };
51 |
52 | let person = {
53 | name: 'pooky',
54 | age: 18,
55 | address: {
56 | city: 'shanghai'
57 | }
58 | };
59 |
60 | let name = 'pooky';
61 |
62 | describe('Dependency injector tests', function() {
63 | beforeEach(function() {
64 | this.injector = new Di();
65 | this.injector.bindAny('person', person);
66 | this.injector.bindAny('name', name);
67 | this.injector.bindClass('util', Utils);
68 | this.injector.bindClass('router', Router);
69 | this.injector.bindClass('logger', Logger);
70 | this.injector.bindFunction('test', testFunc);
71 | this.injector.bindFunction('async', asyncFunc);
72 | this.injector.bindFunction('arrow', arrowFunc);
73 | this.injector.bindFunction('unInject', unInjectFunc);
74 | });
75 | afterEach(function() {
76 | this.injector.reset();
77 | });
78 | it('BindFunction error with string', function() {
79 | try {
80 | this.injector.bindFunction('func', 'func');
81 | } catch (e) {
82 | expect(e.message).to.eql('instance must be a function!');
83 | }
84 | });
85 | it('Bind error with object name', function() {
86 | try {
87 | this.injector.bindAny({}, 'object');
88 | } catch (e) {
89 | expect(e.message).to.eql('instance name must be a string.');
90 | }
91 | });
92 | it('Bind error with null', function() {
93 | try {
94 | this.injector.bindAny('nullobject', null);
95 | } catch (e) {
96 | expect(e.message).to.eql('instance cannot be null.');
97 | }
98 | });
99 | it('Repeat binding error', function() {
100 | try {
101 | this.injector.bindAny('test', 'test');
102 | this.injector.bindAny('test', 'test');
103 | } catch (e) {
104 | expect(e.message).to.eql(`instance: test has already bound.`);
105 | }
106 | });
107 | it('Inject object test', function() {
108 | expect(this.injector.getInstance('$person')).to.eql(person);
109 | });
110 | it('Inject string test', function() {
111 | expect(this.injector.getInstance('$name')).to.eql(name);
112 | });
113 | it('Inject function test', function() {
114 | expect(this.injector.getInstance('$test')).to.eql('log test function test');
115 | });
116 | it('Inject async function test', function(done) {
117 | this.injector.getInstance('$async').then(function(got) {
118 | expect(got).to.eql('log test async function test');
119 | done();
120 | });
121 | });
122 | it('Inject arrow function test', function() {
123 | expect(this.injector.getInstance('$arrow')).to.eql('log test arrow function test');
124 | });
125 | it('Inject class test', function(done) {
126 | let util = this.injector.getInstance('$util');
127 | expect(util.test()).to.eql('utils test');
128 | expect(util.logTest()).to.eql('log test');
129 | expect(util.routerTest()).to.eql('router test');
130 | util.$async.then(function(res) {
131 | expect(res).to.eql('log test async function test');
132 | done();
133 | });
134 | });
135 | it('Get instances test', function(done) {
136 | let res = this.injector.getInstances(['$async', '$test']);
137 | expect(res[1]).to.eql('log test function test');
138 | res[0].then(function (got) {
139 | expect(got).to.eql('log test async function test');
140 | done();
141 | });
142 | });
143 | it('Get instances null without $', function() {
144 | let got = this.injector.getInstance('unInject');
145 | expect(got).to.eql(null);
146 | });
147 | it('Get instances error with object', function() {
148 | try {
149 | this.injector.getInstances({});
150 | } catch (e) {
151 | expect(e.message).to.eql('instance names must be an array.');
152 | }
153 | });
154 | it('Get instances map error with object', function() {
155 | try {
156 | this.injector.getInstancesMap({});
157 | } catch (e) {
158 | expect(e.message).to.eql('instance names must be an array.');
159 | }
160 | });
161 | it('Get instances map test', function() {
162 | let got = this.injector.getInstancesMap(['$name', '$person']);
163 | expect(got.$name).to.eql(name);
164 | expect(got.$person).to.eql(person);
165 | });
166 | it('Delete instance test', function() {
167 | this.injector.deleteInstance('$async');
168 | expect(this.injector.getInstance('$async')).to.eql(null);
169 | });
170 | it('Delete instance error with object', function() {
171 | try {
172 | this.injector.deleteInstance({});
173 | } catch (e) {
174 | expect(e.message).to.eql('instance name must be a string.');
175 | }
176 | });
177 | it('Delete instances test', function() {
178 | this.injector.deleteInstances(['$async', '$test', '']);
179 | expect(this.injector.getInstance('$async')).to.eql(null);
180 | expect(this.injector.getInstance('$test')).to.eql(null);
181 | });
182 | it('Delete instances error', function() {
183 | try {
184 | this.injector.deleteInstances('test');
185 | } catch (e) {
186 | expect(e.message).to.eql('instance names must be an array.');
187 | }
188 | });
189 | it('Reset test', function() {
190 | this.injector.reset();
191 | expect(this.injector.getInstance('$async')).to.eql(null);
192 | expect(this.injector.getInstance('$test')).to.eql(null);
193 | });
194 | });
--------------------------------------------------------------------------------
/test/router.test.js:
--------------------------------------------------------------------------------
1 | const App = require('..');
2 | const koaBody = require('koa-body');
3 | const request = require('supertest');
4 | const {expect} = require('chai');
5 |
6 | class Person {
7 | constructor($name) {
8 | this.name = $name;
9 | }
10 | SayHello() {
11 | return `Hi, I'm ${this.name}`;
12 | }
13 | }
14 |
15 | const CONSTANT_MOCK = {
16 | PORT: 9001,
17 | INJECT_NAME: 'pooky',
18 | URL_NAME: 'xiaoming'
19 | };
20 | let server = null;
21 | let unhandledRejection = '';
22 | describe('Router tests', () => {
23 | describe('Bind instance tests', () => {
24 | it('Bind anywhere, get response: {name: pooky, namefortest: pooky, getName: pooky}', done => {
25 | const app = new App();
26 | app.bindClass('person', Person);
27 | app.use(($ctx, $name, $next) => {
28 | $ctx.namefortest = $name;
29 | $next();
30 | });
31 | app.bindAny('name', 'pooky');
32 | app.get('/users/:name', ($ctx, $name, $getName) => {
33 | if ($ctx.params.name === CONSTANT_MOCK.URL_NAME) {
34 | return $ctx.response.body = {'name': $ctx.params.name, 'namefortest': $ctx.namefortest, 'getName': $getName};
35 | }
36 | });
37 | app.bindFunction('getName', $name => {
38 | return $name;
39 | });
40 | server = app.listen(CONSTANT_MOCK.PORT);
41 | request(server)
42 | .get('/users/xiaoming')
43 | .expect(200)
44 | .end((err, res) => {
45 | expect(res.body).to.eql({'name': 'xiaoming', 'namefortest': 'pooky', 'getName': 'pooky'});
46 | done();
47 | });
48 | });
49 | });
50 | describe('Router group register tests', () => {
51 | it('Throw error prefix cannot be a empty', () => {
52 | try {
53 | const app = new App();
54 | app.group('');
55 | } catch (e) {
56 | expect(e.message).to.eql('prefix cannot be a empty!');
57 | }
58 | });
59 | it('Throw error prefix must be a string!', () => {
60 | try {
61 | const app = new App();
62 | app.group({});
63 | } catch (e) {
64 | expect(e.message).to.eql('prefix must be a string!');
65 | }
66 | });
67 | it('Throw error middleware must be a function', () => {
68 | try {
69 | const app = new App();
70 | let g = app.group('/v1');
71 | g.get('/test', {});
72 | } catch (e) {
73 | console.log(e.message);
74 | expect(e.message).to.eql('get `/test`: `middleware` must be a function, not `object`');
75 | }
76 | });
77 | });
78 | describe('Router group tests', () => {
79 | before(() => {
80 | const app = new App();
81 | app.USE(koaBody());
82 | app.bindAny('name', CONSTANT_MOCK.INJECT_NAME);
83 | app.use(($ctx, $name, $next) => {
84 | $ctx.testname1 = $name;
85 | $next();
86 | });
87 | app.group('v1')
88 | .get('/users/:name', ($ctx, $next, $name) => {
89 | $ctx.body = {'name': $ctx.params.name, 'testname1': $ctx.testname1, 'testname2': $name};
90 | })
91 | .post('/users/:name', ($ctx, $next, $name) => {
92 | $ctx.body = $ctx.request.body;
93 | });
94 | app.group('v2/')
95 | .get('/users/:name', ($name, $ctx, $next) => {
96 | $ctx.response.body = {'name': $ctx.params.name, 'testname1': $ctx.testname1, 'testname2': $name};
97 | })
98 | .post('/users/:name', ($ctx, $next, $name) => {
99 | $ctx.body = $ctx.request.body;
100 | });
101 | app.group('/v3/')
102 | .get('/users/:name', ($ctx, $name, $next) => {
103 | $ctx.body = {'name': $ctx.params.name, 'testname1': $ctx.testname1, 'testname2': $name};
104 | })
105 | .POST('/users/:name', (ctx, next) => {
106 | ctx.body = ctx.request.body;
107 | })
108 | .GET('/products/:name', (ctx, next) => {
109 | ctx.body = {'name': ctx.params.name, 'testname1': ctx.testname1};
110 | })
111 | .POST('/products/:name', (ctx, next) => {
112 | ctx.testproduct = ctx.params.name + 1;
113 | next();
114 | },(ctx, next) => {
115 | ctx.body = {'name': ctx.params.name, 'testproduct': ctx.testproduct};
116 | })
117 | .DEL('/users/:name', (ctx, next) => {
118 | ctx.body = 'ok';
119 | })
120 | .DELETE('/products/:name', (ctx, next) => {
121 | ctx.body = 'ok';
122 | });
123 | server = app.listen(CONSTANT_MOCK.PORT + 1);
124 | });
125 | it('Group v1, get response: {name: xiaoming, testname1: pooky, testname2: pooky}', done => {
126 | request(server)
127 | .get('/v1/users/xiaoming')
128 | .expect(200)
129 | .end((err, res) => {
130 | expect(res.body).to.eql({'name': 'xiaoming', 'testname1': 'pooky', 'testname2': 'pooky'});
131 | done();
132 | });
133 | });
134 | it('Group v1 POST, get response: {name: koa-body}', done => {
135 | request(server)
136 | .post('/v1/users/xiaoming')
137 | .send({name: 'koa-body'})
138 | .expect(200)
139 | .end((err, res) => {
140 | expect(res.body).to.eql({name: 'koa-body'});
141 | done();
142 | });
143 | });
144 | it('Group v2, get response: {name: xiaoliang, testname1: pooky, testname2: pooky}', done => {
145 | request(server)
146 | .get('/v2/users/xiaoliang')
147 | .expect(200)
148 | .end((err, res) => {
149 | expect(res.body).to.eql({'name': 'xiaoliang', 'testname1': 'pooky', 'testname2': 'pooky'});
150 | done();
151 | });
152 | });
153 | it('Group v2 POST, get response: {name: koa-body}', done => {
154 | request(server)
155 | .post('/v2/users/xiaoliang')
156 | .send({name: 'koa-body'})
157 | .expect(200)
158 | .end((err, res) => {
159 | expect(res.body).to.eql({name: 'koa-body'});
160 | done();
161 | });
162 | });
163 | it('Group v3, get response: {name: xiaoqiang, testname1: pooky, testname2: pooky}', done => {
164 | request(server)
165 | .get('/v3/users/xiaoqiang')
166 | .expect(200)
167 | .end((err, res) => {
168 | expect(res.body).to.eql({'name': 'xiaoqiang', 'testname1': 'pooky', 'testname2': 'pooky'});
169 | done();
170 | });
171 | });
172 | it('Group v4, get response: 404', done => {
173 | request(server)
174 | .get('/v4/users/notfound')
175 | .expect(404)
176 | .end((err, res) => {
177 | expect(res.text).to.eql('Not Found');
178 | done();
179 | });
180 | });
181 | it('Group v3 GET, get response: {name: phone, testname1: pooky}', done => {
182 | request(server)
183 | .get('/v3/products/phone')
184 | .expect(200)
185 | .end((err, res) => {
186 | expect(res.body).to.eql({ name: 'phone', testname1: 'pooky' });
187 | done();
188 | });
189 | });
190 | it('Group v3 POST, get response: {name: phone, testproduct: phone1}', done => {
191 | request(server)
192 | .post('/v3/products/phone')
193 | .expect(200)
194 | .end((err, res) => {
195 | expect(res.body).to.eql({ name: 'phone', testproduct: 'phone1' });
196 | done();
197 | });
198 | });
199 | it('Group v3 POST, get response: {name: koa-body}', done => {
200 | request(server)
201 | .post('/v3/users/phone')
202 | .send({name: 'koa-body'})
203 | .expect(200)
204 | .end((err, res) => {
205 | expect(res.body).to.eql({name: 'koa-body'});
206 | done();
207 | });
208 | });
209 | it('Group v3 DEL, get response: {name: phone, testproduct: phone1}', done => {
210 | request(server)
211 | .del('/v3/users/pooky')
212 | .expect(200)
213 | .end((err, res) => {
214 | expect(res.text).to.eql('ok');
215 | done();
216 | });
217 | });
218 | it('Group v3 DELETE, get response: {name: phone, testproduct: phone1}', done => {
219 | request(server)
220 | .del('/v3/products/phone')
221 | .expect(200)
222 | .end((err, res) => {
223 | expect(res.text).to.eql('ok');
224 | done();
225 | });
226 | });
227 | });
228 | describe('Router delete tests', () => {
229 | before(() => {
230 | const app = new App();
231 | app.bindAny('name', CONSTANT_MOCK.INJECT_NAME);
232 | app.use(($ctx, $name, $next) => {
233 | $ctx.testname1 = $name;
234 | $next();
235 | });
236 | app.group('/v4/')
237 | .del('/users/:name', ($ctx, $name, $next) => {
238 | $ctx.body = `v4 delete user ${$ctx.params.name}, ${$name} success`;
239 | })
240 | .delete('/products/:name', ($ctx, $name, $next) => {
241 | $ctx.body = `v4 delete product ${$ctx.params.name}, ${$name} success`;
242 | });
243 | app.delete('/users/:name', ($ctx, $name, $next) => {
244 | $ctx.body = `app delete user ${$ctx.params.name}, ${$name} success`;
245 | });
246 | app.del('/products/:name', ($ctx, $name, $next) => {
247 | $ctx.body = `app delete product ${$ctx.params.name}, ${$name} success`;
248 | });
249 | app.get('/users/:name', ($ctx, $name, $next) => {
250 | $ctx.body = $ctx.params.name;
251 | });
252 | server = app.listen(CONSTANT_MOCK.PORT + 8);
253 | });
254 | it('App router, /users get response: xiaoqiang', done => {
255 | request(server)
256 | .get('/users/xiaoqiang')
257 | .set('Accept', 'text/plain; charset=utf-8')
258 | .expect('Content-Type', 'text/plain; charset=utf-8')
259 | .expect(200)
260 | .end((err, res) => {
261 | expect(res.text).to.eql('xiaoqiang');
262 | done();
263 | });
264 | });
265 | it('Group v4, /v4/users get response: v4 delete user xiaoqiang, pooky success', done => {
266 | request(server)
267 | .delete('/v4/users/xiaoqiang')
268 | .expect(200)
269 | .set('Accept', 'text/plain; charset=utf-8')
270 | .expect('Content-Type', 'text/plain; charset=utf-8')
271 | .end((err, res) => {
272 | expect(res.text).to.eql('v4 delete user xiaoqiang, pooky success');
273 | done();
274 | });
275 | });
276 | it('Group v4, /v4/products get response: v4 delete product, pooky phone success', done => {
277 | request(server)
278 | .del('/v4/products/phone')
279 | .set('Accept', 'text/plain; charset=utf-8')
280 | .expect('Content-Type', 'text/plain; charset=utf-8')
281 | .expect(200)
282 | .end((err, res) => {
283 | expect(res.text).to.eql('v4 delete product phone, pooky success');
284 | done();
285 | });
286 | });
287 | it('App router /users get response: app delete user xiaoqiang, pooky success', done => {
288 | request(server)
289 | .del('/users/xiaoqiang')
290 | .set('Accept', 'text/plain; charset=utf-8')
291 | .expect('Content-Type', 'text/plain; charset=utf-8')
292 | .end((err, res) => {
293 | expect(res.text).to.eql('app delete user xiaoqiang, pooky success');
294 | done();
295 | });
296 | });
297 | it('App router /products get response: app delete product phone, pooky success', done => {
298 | request(server)
299 | .del('/products/phone')
300 | .set('Accept', 'text/plain; charset=utf-8')
301 | .expect('Content-Type', 'text/plain; charset=utf-8')
302 | .expect(200)
303 | .end((err, res) => {
304 | expect(res.text).to.eql('app delete product phone, pooky success');
305 | done();
306 | });
307 | });
308 | });
309 | describe('Router multiple middleware tests', () => {
310 | before(() => {
311 | const app = new App();
312 | app.bindAny('name1', CONSTANT_MOCK.INJECT_NAME + 1);
313 | app.use(($ctx, $name1, $next) => {
314 | $ctx.name1 = $name1;
315 | $next();
316 | });
317 | app.bindAny('name2', CONSTANT_MOCK.INJECT_NAME + 2);
318 | let v1Router = app.group('v1')
319 | .use(($name6, $ctx, $next) => {
320 | $ctx.testname6 = $name6;
321 | $next();
322 | })
323 | .get('/users/:name', ($ctx, $next, $name2) => {
324 | $ctx.body = {'name': $ctx.params.name, 'name1': $ctx.name1, 'name2': $name2};
325 | if ($ctx.params.name === 'testname6') {
326 | $ctx.body.name6 = $ctx.testname6;
327 | }
328 | $next();
329 | }, ($ctx, $next, $name3) => {
330 | $ctx.body.name3 = $name3;
331 | $next();
332 | }, ($ctx, $next, $name4) => {
333 | $ctx.name4 = $name4;
334 | $next();
335 | }, ($ctx, $next, $name5) => {
336 | $ctx.body.name4 = $ctx.name4;
337 | $ctx.body.name5 = $name5;
338 | if ($ctx.params.name === 'testname7') {
339 | $ctx.body.name7 = $ctx.testname7;
340 | }
341 | });
342 | v1Router.use(($ctx, $next, $name7) => {
343 | $ctx.testname7 = $name7;
344 | $next();
345 | });
346 | app.bindAny('name3', CONSTANT_MOCK.INJECT_NAME + 3);
347 | app.bindAny('name4', CONSTANT_MOCK.INJECT_NAME + 4);
348 | app.bindAny('name5', CONSTANT_MOCK.INJECT_NAME + 5);
349 | app.bindAny('name6', CONSTANT_MOCK.INJECT_NAME + 6);
350 | app.bindAny('name7', CONSTANT_MOCK.INJECT_NAME + 7);
351 | server = app.listen(CONSTANT_MOCK.PORT + 2);
352 | });
353 | it('Get response: {name: xiaoming, name1: pooky1, name2: pooky1 ...}', done => {
354 | request(server)
355 | .get('/v1/users/xiaoming')
356 | .expect(200)
357 | .end((err, res) => {
358 | expect(res.body).to.eql({'name': 'xiaoming', 'name1': 'pooky1', 'name2': 'pooky2', 'name3': 'pooky3', 'name4': 'pooky4', 'name5': 'pooky5'});
359 | done();
360 | });
361 | });
362 | it('Get response: {name: testname6, ..., name6: pooky6}', done => {
363 | request(server)
364 | .get('/v1/users/testname6')
365 | .expect(200)
366 | .end((err, res) => {
367 | expect(res.body).to.eql({'name': 'testname6', 'name1': 'pooky1', 'name2': 'pooky2', 'name3': 'pooky3', 'name4': 'pooky4', 'name5': 'pooky5', 'name6': 'pooky6'});
368 | done();
369 | });
370 | });
371 | it('Get response: {name: testname7, ..., name7: pooky7}', done => {
372 | request(server)
373 | .get('/v1/users/testname7')
374 | .expect(200)
375 | .end((err, res) => {
376 | expect(res.body).to.eql({'name': 'testname7', 'name1': 'pooky1', 'name2': 'pooky2', 'name3': 'pooky3', 'name4': 'pooky4', 'name5': 'pooky5', 'name7': 'pooky7'});
377 | done();
378 | });
379 | });
380 | });
381 | describe('Interceptors tests', () => {
382 | before(() => {
383 | const app = new App();
384 | app.bindAny('name1', CONSTANT_MOCK.INJECT_NAME + 1);
385 | app.use(async ($ctx, $name1, $next) => {
386 | if ($ctx.path === '/v1/users/appmiddleware') {
387 | throw new Error('app middleware error');
388 | }
389 | $ctx.name1 = $name1;
390 | $next();
391 | });
392 | app.bindAny('name2', CONSTANT_MOCK.INJECT_NAME + 2);
393 | app.group('v1')
394 | .use(async ($ctx, $next, $name2) => {
395 | if ($ctx.path === '/v1/users/middleware') {
396 | throw new Error('router middleware error');
397 | }
398 | $next();
399 | })
400 | .get('/users/:name', ($ctx, $next, $name2) => {
401 | $ctx.body = {'name': $ctx.params.name, 'name1': $ctx.name1, 'name2': $name2};
402 | $next();
403 | }, async ($ctx, $next, $name3) => {
404 | $ctx.body.name3 = $name3;
405 | $next();
406 | }, async ($ctx, $next, $name4) => {
407 | $ctx.name4 = $name4;
408 | if ($ctx.params.name === CONSTANT_MOCK.URL_NAME) {
409 | throw new Error($ctx.params.name);
410 | }
411 | $next();
412 | }, ($ctx, $next, $name5) => {
413 | $ctx.body.name4 = $ctx.name4;
414 | $ctx.body.name5 = $name5;
415 | if ($ctx.params.name === CONSTANT_MOCK.INJECT_NAME) {
416 | throw new Error($ctx.params.name);
417 | }
418 | });
419 | app.bindAny('name3', CONSTANT_MOCK.INJECT_NAME + 3);
420 | app.bindAny('name4', CONSTANT_MOCK.INJECT_NAME + 4);
421 | app.bindAny('name5', CONSTANT_MOCK.INJECT_NAME + 5);
422 | app.interceptors.errors.use((err, ctx) => {
423 | ctx.body = {
424 | code: 500,
425 | data: {},
426 | msg: err.message
427 | };
428 | });
429 | app.interceptors.response.use(ctx => {
430 | let data = ctx.body;
431 | ctx.body = {
432 | code: 200,
433 | data: data,
434 | msg: 'ok'
435 | };
436 | });
437 | server = app.listen(CONSTANT_MOCK.PORT + 3);
438 | });
439 | it('Get response: {code: 200, data: {...}, msg: ok}', done => {
440 | request(server)
441 | .get('/v1/users/xiaoqiang')
442 | .expect(200)
443 | .end((err, res) => {
444 | expect(res.body).to.eql({
445 | code: 200,
446 | data: {
447 | name: 'xiaoqiang',
448 | name1: 'pooky1',
449 | name2: 'pooky2',
450 | name3: 'pooky3',
451 | name4: 'pooky4',
452 | name5: 'pooky5'
453 | },
454 | msg: 'ok'
455 | });
456 | done();
457 | });
458 | });
459 | it('Get response: {code: 500, data: {...}, msg: pooky}', done => {
460 | request(server)
461 | .get('/v1/users/pooky')
462 | .expect(200)
463 | .end((err, res) => {
464 | expect(res.body).to.eql({
465 | code: 500,
466 | data: {},
467 | msg: 'pooky'
468 | });
469 | done();
470 | });
471 | });
472 | it('Get response: {code: 500, data: {...}, msg: xiaoming}', done => {
473 | request(server)
474 | .get('/v1/users/xiaoming')
475 | .expect(200)
476 | .end((err, res) => {
477 | expect(res.body).to.eql({
478 | code: 500,
479 | data: {},
480 | msg: 'xiaoming'
481 | });
482 | done();
483 | });
484 | });
485 | it('Get response: {code: 500, data: {}, msg: router middleware error}', done => {
486 | request(server)
487 | .get('/v1/users/middleware')
488 | .expect(200)
489 | .end((err, res) => {
490 | expect(res.body).to.eql({
491 | code: 500,
492 | data: {},
493 | msg: 'router middleware error'
494 | });
495 | done();
496 | });
497 | });
498 | it('Get response: {code: 500, data: {}, msg: app middleware error}', done => {
499 | request(server)
500 | .get('/v1/users/appmiddleware')
501 | .expect(200)
502 | .end((err, res) => {
503 | expect(res.body).to.eql({
504 | code: 500,
505 | data: {},
506 | msg: 'app middleware error'
507 | });
508 | done();
509 | });
510 | });
511 | });
512 | describe('Interceptors without errors interceptor tests', () => {
513 | before(() => {
514 | const app = new App();
515 | app.bindAny('name1', CONSTANT_MOCK.INJECT_NAME + 1);
516 | app.use(($ctx, $name1, $next) => {
517 | $ctx.name1 = $name1;
518 | $next();
519 | });
520 | app.bindAny('name2', CONSTANT_MOCK.INJECT_NAME + 2);
521 | app.group('v1')
522 | .get('/users/:name', ($ctx, $next, $name2) => {
523 | $ctx.body = {'name': $ctx.params.name, 'name1': $ctx.name1, 'name2': $name2};
524 | $next();
525 | }, ($ctx, $next, $name3) => {
526 | $ctx.body.name3 = $name3;
527 | $next();
528 | }, ($ctx, $next, $name4) => {
529 | $ctx.name4 = $name4;
530 | if ($ctx.params.name === CONSTANT_MOCK.URL_NAME) {
531 | throw new Error($ctx.params.name);
532 | }
533 | $next();
534 | }, ($ctx, $next, $name5) => {
535 | $ctx.body.name4 = $ctx.name4;
536 | $ctx.body.name5 = $name5;
537 | if ($ctx.params.name === CONSTANT_MOCK.INJECT_NAME) {
538 | throw new Error($ctx.params.name);
539 | }
540 | });
541 | app.bindAny('name3', CONSTANT_MOCK.INJECT_NAME + 3);
542 | app.bindAny('name4', CONSTANT_MOCK.INJECT_NAME + 4);
543 | app.bindAny('name5', CONSTANT_MOCK.INJECT_NAME + 5);
544 | app.interceptors.response.use(ctx => {
545 | let data = ctx.body;
546 | ctx.body = {
547 | code: 200,
548 | data: data,
549 | msg: 'ok'
550 | };
551 | });
552 | server = app.listen(CONSTANT_MOCK.PORT + 5);
553 | process.on('unhandledRejection', (reason, promise) => {
554 | unhandledRejection = reason.message;
555 | });
556 | });
557 | it('Get response: {code: 200, data: {...}, msg: ok}', done => {
558 | request(server)
559 | .get('/v1/users/xiaoqiang')
560 | .expect(200)
561 | .end((err, res) => {
562 | expect(res.body).to.eql({
563 | code: 200,
564 | data: {
565 | name: 'xiaoqiang',
566 | name1: 'pooky1',
567 | name2: 'pooky2',
568 | name3: 'pooky3',
569 | name4: 'pooky4',
570 | name5: 'pooky5'
571 | },
572 | msg: 'ok'
573 | });
574 | done();
575 | });
576 | });
577 | it('Get response: {code: 500, data: {...}, msg: pooky}', done => {
578 | request(server)
579 | .get('/v1/users/pooky')
580 | .expect(200)
581 | .end((err, res) => {
582 | expect(unhandledRejection).to.eql(CONSTANT_MOCK.INJECT_NAME);
583 | done();
584 | });
585 | });
586 | it('Get response: {code: 500, data: {...}, msg: xiaoming}', done => {
587 | request(server)
588 | .get('/v1/users/xiaoming')
589 | .expect(200)
590 | .end((err, res) => {
591 | expect(unhandledRejection).to.eql(CONSTANT_MOCK.URL_NAME);
592 | done();
593 | });
594 | });
595 | });
596 | });
--------------------------------------------------------------------------------