diff --git a/.eslintignore b/.eslintignore
index 5886583c02..eb984b683c 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -1,2 +1,4 @@
test/fixtures
-test/benchmark
+examples/**/app/public
+logs
+run
diff --git a/.gitignore b/.gitignore
index 8f4403e745..e20c0b1535 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,3 +12,5 @@ run
.tmp
docs/CONTRIBUTING.md
docs/README.md
+
+!test/fixtures/apps/loader-plugin/node_modules
diff --git a/.travis.yml b/.travis.yml
index 0fd64bfdc6..03736ab24c 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -8,4 +8,4 @@ install:
script:
- npm run ci
after_script:
- - npm i codecov && codecov
+ - npminstall codecov && codecov
diff --git a/appveyor.yml b/appveyor.yml
index 7dff837c3d..e4893768a1 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -5,7 +5,7 @@ environment:
install:
- ps: Install-Product node $env:nodejs_version
- - npm i npminstall && npminstall
+ - npm i npminstall && node_modules\.bin\npminstall
test_script:
- node --version
diff --git a/docs/plugins.puml b/docs/plugins.puml
index 447d465877..a19b2ca8cd 100644
--- a/docs/plugins.puml
+++ b/docs/plugins.puml
@@ -2,19 +2,6 @@
@startuml
digraph world {
"onerror";
- "userservice";
- "userrole";
"session";
- "i18n";
- "validate";
- "watcher";
- "multipart";
- "security" -> "session";
- "development" -> "watcher";
- "rest";
- "static";
- "cors" -> "security";
- "logrotater";
- "schedule";
}
@enduml
diff --git a/examples/cookie/test/index.test.js b/examples/cookie/test/index.test.js
new file mode 100644
index 0000000000..a8ffb6ba5c
--- /dev/null
+++ b/examples/cookie/test/index.test.js
@@ -0,0 +1,47 @@
+'use strict';
+
+const path = require('path');
+const request = require('supertest-as-promised');
+const mm = require('egg-mock');
+
+describe('example cookie test', () => {
+ let app;
+
+ before(() => {
+ const baseDir = path.dirname(__dirname);
+ const customEgg = path.join(baseDir, '../..');
+ app = mm.app({
+ baseDir,
+ customEgg,
+ });
+ return app.ready();
+ });
+
+ after(() => app.close());
+
+ it('should GET / show "remember me" checkbox when cookie.remember not exists', () => {
+ return request(app.callback())
+ .get('/')
+ .expect(200)
+ .expect(/ remember me<\/label>/);
+ });
+
+ it('should POST /remember to set cookie.remember = 1', () => {
+ return request(app.callback())
+ .post('/remember')
+ .send({
+ remember: 'true',
+ })
+ .expect(302)
+ .expect('Location', '/')
+ .expect('Set-Cookie', /^remember=1; path=\/; expires=[^;]+; httponly,remember\.sig=[^;]+; path=\/; expires=[^;]+; httponly$/);
+ });
+
+ it('should GET /forget to delete cookie.remember', () => {
+ return request(app.callback())
+ .get('/forget')
+ .expect(302)
+ .expect('Location', '/')
+ .expect('Set-Cookie', /^remember=; path=\/; expires=[^;]+; httponly$/);
+ });
+});
diff --git a/examples/cookie_session/test/index.test.js b/examples/cookie_session/test/index.test.js
new file mode 100644
index 0000000000..a6e1ae3c6a
--- /dev/null
+++ b/examples/cookie_session/test/index.test.js
@@ -0,0 +1,43 @@
+'use strict';
+
+const path = require('path');
+const request = require('supertest-as-promised');
+const mm = require('egg-mock');
+
+describe('example cookie_session test', () => {
+ let app;
+ let cookie;
+
+ before(() => {
+ const baseDir = path.dirname(__dirname);
+ const customEgg = path.join(baseDir, '../..');
+ app = mm.app({
+ baseDir,
+ customEgg,
+ });
+ return app.ready();
+ });
+
+ after(() => app.close());
+
+ it('should GET / first time', () => {
+ return request(app.callback())
+ .get('/')
+ .expect(200)
+ .expect(/^1 times/)
+ .expect('Set-Cookie', /^EGG_SESS=[^;]+; path=\/; expires=[^;]+; httponly$/)
+ .expect(res => {
+ cookie = res.headers['set-cookie'][0].split(';')[0];
+ });
+ });
+
+ it('should GET / second time', () => {
+ return request(app.callback())
+ .get('/')
+ .set('Cookie', cookie)
+ .expect(200)
+ .expect(/^2 times/)
+ // session.count change
+ .expect('Set-Cookie', /^EGG_SESS=[^;]+; path=\/; expires=[^;]+; httponly$/);
+ });
+});
diff --git a/examples/helloworld/test/index.test.js b/examples/helloworld/test/index.test.js
new file mode 100644
index 0000000000..b56326ad15
--- /dev/null
+++ b/examples/helloworld/test/index.test.js
@@ -0,0 +1,35 @@
+'use strict';
+
+const path = require('path');
+const request = require('supertest-as-promised');
+const mm = require('egg-mock');
+
+describe('example helloworld test', () => {
+ let app;
+
+ before(() => {
+ const baseDir = path.dirname(__dirname);
+ const customEgg = path.join(baseDir, '../..');
+ app = mm.app({
+ baseDir,
+ customEgg,
+ });
+ return app.ready();
+ });
+
+ after(() => app.close());
+
+ it('should GET / 200', () => {
+ return request(app.callback())
+ .get('/')
+ .expect(200)
+ .expect('Hello World');
+ });
+
+ it('should GET /foo', () => {
+ return request(app.callback())
+ .get('/foo')
+ .expect(200)
+ .expect('Hello foo');
+ });
+});
diff --git a/examples/multipart/config/config.default.js b/examples/multipart/config/config.default.js
new file mode 100644
index 0000000000..6c4b8d15f5
--- /dev/null
+++ b/examples/multipart/config/config.default.js
@@ -0,0 +1,3 @@
+'use strict';
+
+exports.keys = 'my keys';
diff --git a/examples/multipart/test/index.test.js b/examples/multipart/test/index.test.js
new file mode 100644
index 0000000000..5da26c82f9
--- /dev/null
+++ b/examples/multipart/test/index.test.js
@@ -0,0 +1,63 @@
+'use strict';
+
+const assert = require('assert');
+const path = require('path');
+const request = require('supertest-as-promised');
+const mm = require('egg-mock');
+const formstream = require('formstream');
+const urllib = require('urllib');
+
+describe.skip('example multipart test', () => {
+ let app;
+ let csrfToken;
+ let cookies;
+ let host;
+ let server;
+
+ before(() => {
+ const baseDir = path.dirname(__dirname);
+ const customEgg = path.join(baseDir, '../..');
+ app = mm.app({
+ baseDir,
+ customEgg,
+ });
+ server = app.listen();
+ });
+
+ after(() => app.close());
+
+ it('should GET / show upload form', () => {
+ return request(server)
+ .get('/')
+ .expect(200)
+ .expect(/
Image: <\/p>/)
+ .expect(res => {
+ console.log(res.headers, res.text);
+ csrfToken = res.headers['x-csrf'];
+ cookies = res.headers['set-cookie'].join(';');
+ host = `http://127.0.0.1:${server.address().port}`;
+ });
+ });
+
+ it('should POST /upload success', done => {
+ const form = formstream();
+ form.file('file', __filename);
+ // other form fields
+ form.field('title', 'fengmk2 test title')
+ .field('love', 'egg');
+
+ const headers = form.headers();
+ headers.Cookie = cookies;
+ urllib.request(`${host}/upload?_csrf=${csrfToken}`, {
+ method: 'POST',
+ headers,
+ stream: form,
+ dataType: 'json',
+ }, (err, data, res) => {
+ assert(!err, err && err.message);
+ assert.equal(res.statusCode, 200);
+ console.log(data);
+ done();
+ });
+ });
+});
diff --git a/examples/static/app/router.js b/examples/static/app/router.js
index e28728a8a1..b3e62cf0b3 100644
--- a/examples/static/app/router.js
+++ b/examples/static/app/router.js
@@ -1,5 +1,5 @@
'use strict';
module.exports = app => {
- app.get('/', 'home');
+ app.get('/', app.controller.home);
};
diff --git a/examples/static/config/config.default.js b/examples/static/config/config.default.js
new file mode 100644
index 0000000000..6c4b8d15f5
--- /dev/null
+++ b/examples/static/config/config.default.js
@@ -0,0 +1,3 @@
+'use strict';
+
+exports.keys = 'my keys';
diff --git a/examples/static/test/index.test.js b/examples/static/test/index.test.js
new file mode 100644
index 0000000000..0c0eccb629
--- /dev/null
+++ b/examples/static/test/index.test.js
@@ -0,0 +1,35 @@
+'use strict';
+
+const path = require('path');
+const request = require('supertest-as-promised');
+const mm = require('egg-mock');
+
+describe('example static test', () => {
+ let app;
+
+ before(() => {
+ const baseDir = path.dirname(__dirname);
+ const customEgg = path.join(baseDir, '../..');
+ app = mm.app({
+ baseDir,
+ customEgg,
+ });
+ return app.ready();
+ });
+
+ after(() => app.close());
+
+ it('should GET / 200', () => {
+ return request(app.callback())
+ .get('/')
+ .expect(200)
+ .expect(/
Download hi\.txt<\/a>\.<\/li>/);
+ });
+
+ it('should GET /public/hi.txt', () => {
+ return request(app.callback())
+ .get('/public/hi.txt')
+ .expect(200)
+ .expect('hi egg.\n你好,蛋蛋。\n');
+ });
+});
diff --git a/lib/core/app/middleware/meta.js b/lib/core/app/middleware/meta.js
index 4a44e34997..f2047ca602 100644
--- a/lib/core/app/middleware/meta.js
+++ b/lib/core/app/middleware/meta.js
@@ -1,19 +1,19 @@
/**
- * meta 中间件,放在最前面
+ * meta middleware, should be the first middleware
*/
'use strict';
-module.exports = function() {
+module.exports = () => {
let serverId = process.env.HOSTNAME || '';
if (serverId.indexOf('-') > 0) {
- // appname-90-1 => 90-1
+ // appname-1-1 => 1-1
serverId = serverId.split('-').slice(1).join('-');
}
- return function* (next) {
+ return function* meta(next) {
/**
- * 开始处理当前请求的时间戳,单位 `ms`,方便做一些时间计算。
+ * Request start time
* @member {Number} Context#starttime
*/
this.starttime = Date.now();
@@ -28,7 +28,7 @@ module.exports = function() {
this.set('X-Server-Id', serverId);
}
- // 设置一个 x-readtime 头, 供 nginx access log 使用, 也方便调试
+ // total response time header
this.set('X-Readtime', Date.now() - this.starttime);
};
};
diff --git a/lib/core/app/middleware/notfound.js b/lib/core/app/middleware/notfound.js
index 6080ec6827..1057931f81 100644
--- a/lib/core/app/middleware/notfound.js
+++ b/lib/core/app/middleware/notfound.js
@@ -1,6 +1,6 @@
'use strict';
-module.exports = function(options) {
+module.exports = options => {
return function* notfound(next) {
yield next;
diff --git a/lib/core/app/middleware/site_file.js b/lib/core/app/middleware/site_file.js
index 2d763a5e6a..ea8f4d607b 100644
--- a/lib/core/app/middleware/site_file.js
+++ b/lib/core/app/middleware/site_file.js
@@ -5,8 +5,8 @@
const path = require('path');
const MAX_AGE = 30 * 24 * 68 * 60;
-module.exports = function fixture(options) {
- return function* (next) {
+module.exports = options => {
+ return function* siteFile(next) {
if (this.method !== 'HEAD' && this.method !== 'GET') return yield next;
if (!options.hasOwnProperty(this.path)) return yield next;
diff --git a/lib/core/config/plugin.js b/lib/core/config/plugin.js
index 4b87cb9504..0039123d76 100644
--- a/lib/core/config/plugin.js
+++ b/lib/core/config/plugin.js
@@ -27,13 +27,13 @@ module.exports = {
/**
* userrole
* @member {Object} Plugin#userrole
- * @property {Boolean} enable - 默认 true
+ * @property {Boolean} enable - `true` by default
* @since 1.0.0
*/
- // userrole: {
- // enable: true,
- // package: 'egg-userrole',
- // },
+ userrole: {
+ enable: true,
+ package: 'egg-userrole',
+ },
/**
* session
@@ -63,10 +63,10 @@ module.exports = {
* @property {Boolean} enable - 默认 true
* @since 1.0.0
*/
- // validate: {
- // enable: true,
- // package: 'egg-validate',
- // },
+ validate: {
+ enable: true,
+ package: 'egg-validate',
+ },
/**
* file and dir watcher
@@ -87,7 +87,7 @@ module.exports = {
*/
// multipart: {
// enable: true,
- // package: '@ali/egg-multipart',
+ // package: 'egg-multipart',
// },
/**
@@ -128,13 +128,13 @@ module.exports = {
/**
* `app/public` dir static serve
* @member {Object} Plugin#static
- * @property {Boolean} enable - 默认 false
+ * @property {Boolean} enable - `false` by default
* @since 1.0.0
*/
- // static: {
- // enable: false,
- // package: 'egg-static',
- // },
+ static: {
+ enable: false,
+ package: 'egg-static',
+ },
/**
* CORS
@@ -142,10 +142,10 @@ module.exports = {
* @property {Boolean} enable - 默认 false
* @since 1.0.0
*/
- // cors: {
- // enable: false,
- // package: 'egg-cors',
- // },
+ cors: {
+ enable: false,
+ package: 'egg-cors',
+ },
/**
* logger file rotater
diff --git a/package.json b/package.json
index c5070e7a45..887b2b3de8 100644
--- a/package.json
+++ b/package.json
@@ -3,18 +3,22 @@
"version": "0.0.4",
"description": "A web framework's framework for Node.js",
"dependencies": {
- "egg-cluster": "*",
- "egg-loader": "*",
"accepts": "^1.3.3",
"agentkeepalive": "^2.1.1",
"co": "^4.6.0",
"debug": "^2.2.0",
"delegates": "^1.0.0",
"depd": "^1.1.0",
+ "egg-cluster": "*",
"egg-cookies": "^1.0.0",
+ "egg-cors": "^0.0.2",
+ "egg-loader": "*",
"egg-logger": "^1.0.1",
- "egg-onerror": "~0.0.2",
- "egg-session": "~0.0.2",
+ "egg-onerror": "*",
+ "egg-session": "*",
+ "egg-static": "*",
+ "egg-userrole": "*",
+ "egg-validate": "*",
"graceful": "^1.0.0",
"humanize-ms": "^1.2.0",
"inflection": "^1.10.0",
@@ -39,7 +43,8 @@
"co-sleep": "^0.0.1",
"coffee": "^3.2.2",
"egg-bin": "1",
- "egg-ci": "^1.0.0",
+ "egg-ci": "1",
+ "egg-mock": "*",
"egg-plugin-puml": "1",
"eslint": "^3.0.0",
"eslint-config-egg": "^3.1.0",
@@ -48,7 +53,6 @@
"glob": "^7.0.3",
"merge-descriptors": "^1.0.1",
"moment": "^2.13.0",
- "node-uuid": "^1.4.7",
"once": "^1.3.3",
"pedding": "^1.0.0",
"rds": "^0.1.0",
@@ -56,9 +60,7 @@
"should": "^6.0.3",
"stream-wormhole": "^1.0.0",
"supertest": "^1.2.0",
- "supertest-as-promised": "^3.2.0",
- "uuid": "^2.0.2",
- "uuid-js": "^0.7.5"
+ "supertest-as-promised": "^3.2.0"
},
"main": "index.js",
"files": [
@@ -67,11 +69,12 @@
"index.js"
],
"scripts": {
- "lint": "eslint --fix lib test *.js",
+ "lint": "eslint --fix lib test examples *.js",
"test": "npm run lint && npm run test-local",
"test-local": "egg-bin test",
+ "test-examples": "TESTS=examples/**/test/**/*.test.js egg-bin test",
"cov": "egg-bin cov",
- "ci": "node -v && npm run lint && npm run cov",
+ "ci": "npm run lint && npm run test-examples && npm run cov",
"autod": "autod",
"doc": "./scripts/doc.sh",
"puml": "puml . --dest ./docs",
diff --git a/test/benchmark/assign.js b/test/benchmark/assign.js
new file mode 100644
index 0000000000..ea60e750f5
--- /dev/null
+++ b/test/benchmark/assign.js
@@ -0,0 +1,54 @@
+'use strict';
+
+const Benchmark = require('benchmark');
+const benchmarks = require('beautify-benchmark');
+const utils = require('../../lib/util');
+
+new Benchmark.Suite()
+.add('Object.assign', () => {
+ const a = {};
+ const b = { a: 1, b: 2, c: 3, d: 4, e: 5 };
+ Object.assign(a, b);
+})
+.add('for in', () => {
+ const a = {};
+ const b = { a: 1, b: 2, c: 3, d: 4, e: 5 };
+ for (const key in b) {
+ a[key] = b[key];
+ }
+})
+.add('Object.keys', () => {
+ const a = {};
+ const b = { a: 1, b: 2, c: 3, d: 4, e: 5 };
+ const keys = Object.keys(b);
+ for (let i = 0; i < keys.length; i++) {
+ const key = keys[i];
+ a[key] = b[key];
+ }
+})
+.add('utils.assign', () => {
+ const a = {};
+ const b = { a: 1, b: 2, c: 3, d: 4, e: 5 };
+ utils.assign(a, b);
+})
+.on('cycle', event => {
+ benchmarks.add(event.target);
+})
+.on('start', () => {
+ console.log('\n node version: %s, date: %s\n Starting...',
+ process.version, Date());
+})
+.on('complete', () => {
+ benchmarks.log();
+ process.exit(0);
+})
+.run({ async: false });
+
+// node version: v4.2.3, date: Tue Jan 26 2016 16:52:46 GMT+0800 (CST)
+// Starting...
+// 4 tests completed.
+//
+// Object.assign x 646,034 ops/sec ±1.61% (86 runs sampled)
+// for in x 2,754,639 ops/sec ±1.21% (88 runs sampled)
+// Object.keys x 3,590,226 ops/sec ±1.04% (93 runs sampled)
+// utils.assign x 3,192,343 ops/sec ±0.69% (93 runs sampled)
diff --git a/test/fixtures/apps/agent-app-sync/agent.js b/test/fixtures/apps/agent-app-sync/agent.js
new file mode 100644
index 0000000000..ac817ef910
--- /dev/null
+++ b/test/fixtures/apps/agent-app-sync/agent.js
@@ -0,0 +1,9 @@
+'use strict';
+
+module.exports = agent => {
+ agent.startAgent({
+ name: 'test',
+ client: { ready: cb => cb() },
+ subscribe: (info, cb) => cb('test'),
+ });
+};
diff --git a/test/fixtures/apps/agent-app-sync/app.js b/test/fixtures/apps/agent-app-sync/app.js
new file mode 100644
index 0000000000..17eef8182b
--- /dev/null
+++ b/test/fixtures/apps/agent-app-sync/app.js
@@ -0,0 +1,14 @@
+'use strict';
+
+module.exports = app => {
+ const done = app.readyCallback();
+ const test = app.createAppWorkerClient('test', {
+ listen(cb) {
+ this._subscribe('listening', cb);
+ },
+ });
+ test.listen(arg => {
+ app.arg = arg;
+ done();
+ })
+};
diff --git a/test/fixtures/apps/agent-app-sync/app/router.js b/test/fixtures/apps/agent-app-sync/app/router.js
new file mode 100644
index 0000000000..de9f8095a0
--- /dev/null
+++ b/test/fixtures/apps/agent-app-sync/app/router.js
@@ -0,0 +1,7 @@
+'use strict';
+
+module.exports = app => {
+ app.get('/', function*() {
+ this.body = this.app.arg;
+ })
+};
diff --git a/test/fixtures/apps/agent-app-sync/package.json b/test/fixtures/apps/agent-app-sync/package.json
new file mode 100644
index 0000000000..06c4ed021a
--- /dev/null
+++ b/test/fixtures/apps/agent-app-sync/package.json
@@ -0,0 +1,3 @@
+{
+ "name": "agent-app-sync"
+}
diff --git a/test/fixtures/apps/agent-app/app.js b/test/fixtures/apps/agent-app/app.js
new file mode 100644
index 0000000000..6f0ad1bc02
--- /dev/null
+++ b/test/fixtures/apps/agent-app/app.js
@@ -0,0 +1,11 @@
+'use strict';
+
+module.exports = app => {
+ const done = app.readyCallback('foo');
+ app.mockClient.subscribe({
+ id: 'foo'
+ }, value => {
+ app.foo = value;
+ done();
+ });
+};
diff --git a/test/fixtures/apps/agent-app/app/router.js b/test/fixtures/apps/agent-app/app/router.js
new file mode 100644
index 0000000000..efd9f79fbb
--- /dev/null
+++ b/test/fixtures/apps/agent-app/app/router.js
@@ -0,0 +1,25 @@
+module.exports = app => {
+ app.get('/', function*() {
+ this.body = 'ok';
+ });
+
+ app.get('/getData', function*() {
+ this.body = yield app.mockClient.getData();
+ });
+
+ app.get('/getError', function*() {
+ try {
+ yield app.mockClient.getError();
+ } catch (err) {
+ this.body = err.message;
+ }
+ });
+
+ app.get('/getDataGenerator', function* () {
+ this.body = yield app.mockClient.getDataGenerator();
+ })
+
+ app.get('/sub', function*() {
+ this.body = app.foo;
+ });
+};
diff --git a/test/fixtures/apps/agent-app/config/plugin.js b/test/fixtures/apps/agent-app/config/plugin.js
new file mode 100644
index 0000000000..98e5bed6c1
--- /dev/null
+++ b/test/fixtures/apps/agent-app/config/plugin.js
@@ -0,0 +1,10 @@
+'use strict';
+
+const path = require('path');
+
+module.exports = {
+ mock: {
+ enable: true,
+ path: path.join(__dirname, '../plugins/mock-client'),
+ },
+};
diff --git a/test/fixtures/apps/agent-app/package.json b/test/fixtures/apps/agent-app/package.json
new file mode 100644
index 0000000000..34a8314b9c
--- /dev/null
+++ b/test/fixtures/apps/agent-app/package.json
@@ -0,0 +1,3 @@
+{
+ "name": "agent-app"
+}
diff --git a/test/fixtures/apps/agent-app/plugins/mock-client/agent.js b/test/fixtures/apps/agent-app/plugins/mock-client/agent.js
new file mode 100644
index 0000000000..748a316bf6
--- /dev/null
+++ b/test/fixtures/apps/agent-app/plugins/mock-client/agent.js
@@ -0,0 +1,29 @@
+'use strict';
+
+const MockClient = require('./mock_client');
+
+module.exports = agent => {
+ const done = agent.readyCallback('agent_configclient');
+ const options = agent.config.mock;
+
+ agent.mockClient = new MockClient();
+
+ // 启动 agent 任务
+ agent.startAgent({
+ client: agent.mockClient,
+ name: 'mock',
+ subscribe: function(info, listener) {
+ agent.mockClient.on(info.id, listener);
+ if (info.id === 'foo') {
+ setTimeout(function() {
+ agent.mockClient.emit('foo', 'bar');
+ }, 100);
+ }
+ },
+ });
+
+ agent.mockClient.ready(() => {
+ agent.logger.info('[agent] %s started mockClient', agent.config.name);
+ done();
+ });
+};
diff --git a/test/fixtures/apps/agent-app/plugins/mock-client/app.js b/test/fixtures/apps/agent-app/plugins/mock-client/app.js
new file mode 100644
index 0000000000..bcf9655c72
--- /dev/null
+++ b/test/fixtures/apps/agent-app/plugins/mock-client/app.js
@@ -0,0 +1,28 @@
+'use strict';
+
+module.exports = app => {
+ const options = app.config.mock;
+
+ app.mockClient = app.createAppWorkerClient('mock', {
+ subscribe: function(reg, listner) {
+ this._subscribe(reg, listner);
+ return this;
+ },
+
+ * getData(id) {
+ return yield this._invoke('getData', [id]);
+ },
+
+ * getError() {
+ return yield this._invoke('getError', []);
+ },
+
+ * getDataGenerator(id) {
+ return yield this._invoke('getDataGenerator', [id]);
+ },
+ }, options);
+
+ app.mockClient.ready(app.readyCallback('worker_mock_client'), {
+ isWeakDep: app.config.runMode === 0,
+ });
+};
diff --git a/test/fixtures/apps/agent-app/plugins/mock-client/config/config.default.js b/test/fixtures/apps/agent-app/plugins/mock-client/config/config.default.js
new file mode 100644
index 0000000000..8d01395106
--- /dev/null
+++ b/test/fixtures/apps/agent-app/plugins/mock-client/config/config.default.js
@@ -0,0 +1,9 @@
+'use strict';
+
+module.exports = () => {
+ return {
+ mock: {
+ name: 'mock',
+ },
+ };
+}
diff --git a/test/fixtures/apps/agent-app/plugins/mock-client/mock_client.js b/test/fixtures/apps/agent-app/plugins/mock-client/mock_client.js
new file mode 100644
index 0000000000..0f74f147c4
--- /dev/null
+++ b/test/fixtures/apps/agent-app/plugins/mock-client/mock_client.js
@@ -0,0 +1,65 @@
+'use strict';
+
+const EventEmitter = require('events').EventEmitter;
+const sleep = require('co-sleep');
+
+class MockClient extends EventEmitter {
+ constructor(options) {
+ super();
+
+ setImmediate(function() {
+ this.ready(true);
+ }.bind(this));
+ }
+
+ ready(flagOrFunction) {
+ this._ready = !!this._ready;
+ this._readyCallbacks = this._readyCallbacks || [];
+
+ if (typeof flagOrFunction === 'function') {
+ this._readyCallbacks.push(flagOrFunction);
+ } else {
+ this._ready = !!flagOrFunction;
+ }
+
+ if (this._ready) {
+ this._readyCallbacks.splice(0, Infinity).forEach(function(callback) {
+ process.nextTick(callback);
+ });
+ }
+ return this;
+ }
+
+ getCallback(id, callback) {
+ setTimeout(function() {
+ if (id === 'error') {
+ callback(new Error('mock error'));
+ } else {
+ callback(null, 'mock data');
+ }
+ }, 100);
+ }
+
+ getData() {
+ return new Promise(function(resolve, reject) {
+ setTimeout(function() {
+ resolve('mock data');
+ }, 100);
+ });
+ }
+
+ * getDataGenerator() {
+ yield sleep(100);
+ return 'mock data';
+ }
+
+ getError() {
+ return new Promise(function(resolve, reject) {
+ setTimeout(function() {
+ reject(new Error('mock error'));
+ }, 100);
+ });
+ }
+}
+
+module.exports = MockClient;
diff --git a/test/fixtures/apps/agent-die/agent.js b/test/fixtures/apps/agent-die/agent.js
new file mode 100644
index 0000000000..609a3d444d
--- /dev/null
+++ b/test/fixtures/apps/agent-die/agent.js
@@ -0,0 +1,4 @@
+
+setTimeout(() => {
+ throw new Error('app worker throw');
+}, 5000);
diff --git a/test/fixtures/apps/agent-die/package.json b/test/fixtures/apps/agent-die/package.json
new file mode 100644
index 0000000000..a973c60f38
--- /dev/null
+++ b/test/fixtures/apps/agent-die/package.json
@@ -0,0 +1,3 @@
+{
+ "name": "agent-die"
+}
diff --git a/test/fixtures/apps/agent-die/start.js b/test/fixtures/apps/agent-die/start.js
new file mode 100644
index 0000000000..06fa6ce672
--- /dev/null
+++ b/test/fixtures/apps/agent-die/start.js
@@ -0,0 +1,13 @@
+'use strict';
+
+const utils = require('../../../utils');
+
+require('../../../../index').startCluster({
+ baseDir: __dirname,
+ workers: 1
+})
+
+setTimeout(() => {
+ process.exit();
+// coverage will be slow
+}, 5000);
diff --git a/test/fixtures/apps/agent-instrument/agent.js b/test/fixtures/apps/agent-instrument/agent.js
new file mode 100644
index 0000000000..f3a9bd15a3
--- /dev/null
+++ b/test/fixtures/apps/agent-instrument/agent.js
@@ -0,0 +1,10 @@
+'use strict';
+
+module.exports = agent => {
+ const done = agent.readyCallback('custom-agent-ready')
+ const ins = agent.instrument('http', `/hello`);
+ setTimeout(() => {
+ ins.end();
+ done();
+ }, 500);
+}
diff --git a/test/fixtures/apps/agent-instrument/package.json b/test/fixtures/apps/agent-instrument/package.json
new file mode 100644
index 0000000000..b342c35e61
--- /dev/null
+++ b/test/fixtures/apps/agent-instrument/package.json
@@ -0,0 +1,3 @@
+{
+ "name": "agent-instrument"
+}
diff --git a/test/fixtures/apps/agent-restart/agent.js b/test/fixtures/apps/agent-restart/agent.js
new file mode 100644
index 0000000000..775c2e68a0
--- /dev/null
+++ b/test/fixtures/apps/agent-restart/agent.js
@@ -0,0 +1,17 @@
+'use strict';
+
+const client = require('./client');
+
+module.exports = agent => {
+ agent.startAgent({
+ name: 'mock',
+ client: client,
+ subscribe: function(reg, listener) {
+ console.log('agent subscribe', reg);
+ },
+ });
+
+ agent.messenger.on('die', () => {
+ process.exit(1);
+ });
+};
diff --git a/test/fixtures/apps/agent-restart/app.js b/test/fixtures/apps/agent-restart/app.js
new file mode 100644
index 0000000000..fd40dddc8e
--- /dev/null
+++ b/test/fixtures/apps/agent-restart/app.js
@@ -0,0 +1,14 @@
+'use strict';
+
+const client = require('./client');
+
+module.exports = app => {
+ const mock = app.createAppWorkerClient('mock', {
+ subscribe: function(info, listener) {
+ this._subscribe(info, listener);
+ return this;
+ },
+ });
+
+ mock.subscribe('aaa', data => console.log(data));
+};
diff --git a/test/fixtures/apps/agent-restart/client.js b/test/fixtures/apps/agent-restart/client.js
new file mode 100644
index 0000000000..230c6a2157
--- /dev/null
+++ b/test/fixtures/apps/agent-restart/client.js
@@ -0,0 +1,3 @@
+module.exports = {
+ ready: cb => cb(),
+};
diff --git a/test/fixtures/apps/agent-restart/package.json b/test/fixtures/apps/agent-restart/package.json
new file mode 100644
index 0000000000..7b80f612cf
--- /dev/null
+++ b/test/fixtures/apps/agent-restart/package.json
@@ -0,0 +1,3 @@
+{
+ "name": "agent-restart"
+}
diff --git a/test/fixtures/apps/agent-throw/agent.js b/test/fixtures/apps/agent-throw/agent.js
new file mode 100644
index 0000000000..e1857e7638
--- /dev/null
+++ b/test/fixtures/apps/agent-throw/agent.js
@@ -0,0 +1,7 @@
+'use strict';
+
+module.exports = agent => {
+ agent.messenger.on('agent-throw', () => {
+ throw new Error('agent error');
+ });
+};
diff --git a/test/fixtures/apps/agent-throw/app/router.js b/test/fixtures/apps/agent-throw/app/router.js
new file mode 100644
index 0000000000..7377d3f8f6
--- /dev/null
+++ b/test/fixtures/apps/agent-throw/app/router.js
@@ -0,0 +1,8 @@
+'use strict';
+
+module.exports = app => {
+ app.get('/agent-throw', function*() {
+ app.messenger.broadcast('agent-throw');
+ this.body = 'done';
+ });
+};
diff --git a/test/fixtures/apps/agent-throw/config/config.default.js b/test/fixtures/apps/agent-throw/config/config.default.js
new file mode 100644
index 0000000000..b6cba897c8
--- /dev/null
+++ b/test/fixtures/apps/agent-throw/config/config.default.js
@@ -0,0 +1 @@
+exports.keys = 'foo';
diff --git a/test/fixtures/apps/agent-throw/package.json b/test/fixtures/apps/agent-throw/package.json
new file mode 100644
index 0000000000..6c6327b13d
--- /dev/null
+++ b/test/fixtures/apps/agent-throw/package.json
@@ -0,0 +1,3 @@
+{
+ "name": "agent-throw"
+}
diff --git a/test/fixtures/apps/aliyun-egg-app/app/controller/home.js b/test/fixtures/apps/aliyun-egg-app/app/controller/home.js
new file mode 100644
index 0000000000..5d71f1230f
--- /dev/null
+++ b/test/fixtures/apps/aliyun-egg-app/app/controller/home.js
@@ -0,0 +1,9 @@
+'use strict';
+
+module.exports = function*() {
+ this.body = {
+ 'aliyun-egg-core': !!this.app['aliyun-egg'],
+ 'aliyun-egg-plugin': !!this.app.custom,
+ 'aliyun-egg-agent': !!this.app.agent,
+ }
+};
diff --git a/test/fixtures/apps/aliyun-egg-app/app/router.js b/test/fixtures/apps/aliyun-egg-app/app/router.js
new file mode 100644
index 0000000000..b3e62cf0b3
--- /dev/null
+++ b/test/fixtures/apps/aliyun-egg-app/app/router.js
@@ -0,0 +1,5 @@
+'use strict';
+
+module.exports = app => {
+ app.get('/', app.controller.home);
+};
diff --git a/test/fixtures/apps/aliyun-egg-app/config/config.default.js b/test/fixtures/apps/aliyun-egg-app/config/config.default.js
new file mode 100644
index 0000000000..b6cba897c8
--- /dev/null
+++ b/test/fixtures/apps/aliyun-egg-app/config/config.default.js
@@ -0,0 +1 @@
+exports.keys = 'foo';
diff --git a/test/fixtures/apps/aliyun-egg-app/package.json b/test/fixtures/apps/aliyun-egg-app/package.json
new file mode 100644
index 0000000000..067fc6a132
--- /dev/null
+++ b/test/fixtures/apps/aliyun-egg-app/package.json
@@ -0,0 +1,3 @@
+{
+ "name": "aliyun-egg-app"
+}
diff --git a/test/fixtures/apps/aliyun-egg-biz/index.js b/test/fixtures/apps/aliyun-egg-biz/index.js
new file mode 100644
index 0000000000..b8fc326fdf
--- /dev/null
+++ b/test/fixtures/apps/aliyun-egg-biz/index.js
@@ -0,0 +1,3 @@
+'use strict';
+
+module.exports = require('../aliyun-egg');
diff --git a/test/fixtures/apps/aliyun-egg-biz/package.json b/test/fixtures/apps/aliyun-egg-biz/package.json
new file mode 100644
index 0000000000..6e4660adf5
--- /dev/null
+++ b/test/fixtures/apps/aliyun-egg-biz/package.json
@@ -0,0 +1,3 @@
+{
+ "name": "aliyun-egg-biz"
+}
diff --git a/test/fixtures/apps/aliyun-egg/index.js b/test/fixtures/apps/aliyun-egg/index.js
new file mode 100644
index 0000000000..4f5c71e9c8
--- /dev/null
+++ b/test/fixtures/apps/aliyun-egg/index.js
@@ -0,0 +1,7 @@
+'use strict';
+
+const egg = require('../../../..');
+
+module.exports = egg;
+module.exports.Application = require('./lib/aliyun-egg');
+module.exports.Agent = require('./lib/agent');
diff --git a/test/fixtures/apps/aliyun-egg/lib/agent.js b/test/fixtures/apps/aliyun-egg/lib/agent.js
new file mode 100644
index 0000000000..f42593b700
--- /dev/null
+++ b/test/fixtures/apps/aliyun-egg/lib/agent.js
@@ -0,0 +1,19 @@
+'use strict';
+
+const path = require('path');
+const egg = require('../../../../..');
+const Agent = egg.Agent;
+const AppWorkerLoader = egg.AppWorkerLoader;
+
+class MyAgent extends Agent {
+
+ constructor(options) {
+ super(options);
+ }
+
+ get [Symbol.for('egg#eggPath')]() {
+ return path.join(__dirname, '..');
+ }
+}
+
+module.exports = MyAgent;
diff --git a/test/fixtures/apps/aliyun-egg/lib/aliyun-egg.js b/test/fixtures/apps/aliyun-egg/lib/aliyun-egg.js
new file mode 100644
index 0000000000..efd2234bae
--- /dev/null
+++ b/test/fixtures/apps/aliyun-egg/lib/aliyun-egg.js
@@ -0,0 +1,37 @@
+'use strict';
+
+const path = require('path');
+const egg = require('../../../../..');
+const Application = egg.Application;
+const AppWorkerLoader = egg.AppWorkerLoader;
+
+class Loader extends AppWorkerLoader {
+
+ constructor(options) {
+ super(options);
+ }
+
+ loadConfig() {
+ this.loadServerConf();
+ super.loadConfig();
+ }
+
+ loadServerConf() {}
+}
+
+class ChairApplication extends Application {
+
+ constructor(options) {
+ super(options);
+ }
+
+ get [Symbol.for('egg#eggPath')]() {
+ return path.join(__dirname, '..');
+ }
+
+ get [Symbol.for('egg#loader')]() {
+ return Loader;
+ }
+}
+
+module.exports = ChairApplication;
diff --git a/test/fixtures/apps/aliyun-egg/lib/core/agent.js b/test/fixtures/apps/aliyun-egg/lib/core/agent.js
new file mode 100644
index 0000000000..ce90514512
--- /dev/null
+++ b/test/fixtures/apps/aliyun-egg/lib/core/agent.js
@@ -0,0 +1,5 @@
+'use strict';
+
+module.exports = agent => {
+
+};
diff --git a/test/fixtures/apps/aliyun-egg/lib/core/app.js b/test/fixtures/apps/aliyun-egg/lib/core/app.js
new file mode 100644
index 0000000000..46ce926d94
--- /dev/null
+++ b/test/fixtures/apps/aliyun-egg/lib/core/app.js
@@ -0,0 +1,5 @@
+'use strict';
+
+module.exports = app => {
+ app['aliyun-egg'] = {};
+};
diff --git a/test/fixtures/apps/aliyun-egg/lib/core/config/plugin.js b/test/fixtures/apps/aliyun-egg/lib/core/config/plugin.js
new file mode 100644
index 0000000000..f652a0dc33
--- /dev/null
+++ b/test/fixtures/apps/aliyun-egg/lib/core/config/plugin.js
@@ -0,0 +1,10 @@
+'use strict';
+
+const path = require('path');
+
+module.exports = {
+ custom: {
+ enable: true,
+ path: path.join(__dirname, '../../plugins/custom'),
+ },
+};
diff --git a/test/fixtures/apps/aliyun-egg/lib/plugins/custom/agent.js b/test/fixtures/apps/aliyun-egg/lib/plugins/custom/agent.js
new file mode 100644
index 0000000000..ecfd8a6790
--- /dev/null
+++ b/test/fixtures/apps/aliyun-egg/lib/plugins/custom/agent.js
@@ -0,0 +1,7 @@
+'use strict';
+
+module.exports = agent => {
+ agent.messenger.on('custom-aliyun-egg-worker', data => {
+ agent.messenger.broadcast('custom-aliyun-egg-agent', data);
+ })
+};
diff --git a/test/fixtures/apps/aliyun-egg/lib/plugins/custom/app.js b/test/fixtures/apps/aliyun-egg/lib/plugins/custom/app.js
new file mode 100644
index 0000000000..83a1f31d1c
--- /dev/null
+++ b/test/fixtures/apps/aliyun-egg/lib/plugins/custom/app.js
@@ -0,0 +1,9 @@
+'use strict';
+
+module.exports = app => {
+ app.custom = {};
+ app.messenger.broadcast('custom-aliyun-egg-worker', 123);
+ app.messenger.on('custom-aliyun-egg-agent', data => {
+ app.agent = data;
+ })
+};
diff --git a/test/fixtures/apps/aliyun-egg/package.json b/test/fixtures/apps/aliyun-egg/package.json
new file mode 100644
index 0000000000..a0caaf7885
--- /dev/null
+++ b/test/fixtures/apps/aliyun-egg/package.json
@@ -0,0 +1,3 @@
+{
+ "name": "aliyun-egg"
+}
diff --git a/test/fixtures/apps/app-die/app/router.js b/test/fixtures/apps/app-die/app/router.js
new file mode 100644
index 0000000000..85642afa6a
--- /dev/null
+++ b/test/fixtures/apps/app-die/app/router.js
@@ -0,0 +1,13 @@
+'use strict';
+
+module.exports = app => {
+ app.get('/exit', function*() {
+ process.exit(1);
+ });
+
+ app.get('/uncaughtException', function*() {
+ setTimeout(() => {
+ throw new Error('get uncaughtException');
+ }, 100);
+ });
+};
diff --git a/test/fixtures/apps/app-die/config/config.default.js b/test/fixtures/apps/app-die/config/config.default.js
new file mode 100644
index 0000000000..b6cba897c8
--- /dev/null
+++ b/test/fixtures/apps/app-die/config/config.default.js
@@ -0,0 +1 @@
+exports.keys = 'foo';
diff --git a/test/fixtures/apps/app-die/package.json b/test/fixtures/apps/app-die/package.json
new file mode 100644
index 0000000000..bd967656fb
--- /dev/null
+++ b/test/fixtures/apps/app-die/package.json
@@ -0,0 +1,3 @@
+{
+ "name": "app-die"
+}
diff --git a/test/fixtures/apps/app-router/app/controller/home.js b/test/fixtures/apps/app-router/app/controller/home.js
new file mode 100644
index 0000000000..24901f6eec
--- /dev/null
+++ b/test/fixtures/apps/app-router/app/controller/home.js
@@ -0,0 +1,3 @@
+module.exports = function* () {
+ this.body = 'hello';
+};
diff --git a/test/fixtures/apps/app-router/app/router.js b/test/fixtures/apps/app-router/app/router.js
new file mode 100644
index 0000000000..4f58faef63
--- /dev/null
+++ b/test/fixtures/apps/app-router/app/router.js
@@ -0,0 +1,4 @@
+module.exports = app => {
+ app.get('home', '/', 'home');
+ app.get('/home', app.controller.home);
+};
diff --git a/test/fixtures/apps/app-router/package.json b/test/fixtures/apps/app-router/package.json
new file mode 100644
index 0000000000..bffa1e7bdd
--- /dev/null
+++ b/test/fixtures/apps/app-router/package.json
@@ -0,0 +1,3 @@
+{
+ "name": "app-router"
+}
diff --git a/test/fixtures/apps/app-server/app.js b/test/fixtures/apps/app-server/app.js
new file mode 100644
index 0000000000..5344174c5e
--- /dev/null
+++ b/test/fixtures/apps/app-server/app.js
@@ -0,0 +1,7 @@
+'use strict';
+
+module.exports = app => {
+ app.on('server', server => {
+ app.serverEmit = true;
+ });
+};
diff --git a/test/fixtures/apps/app-server/app/router.js b/test/fixtures/apps/app-server/app/router.js
new file mode 100644
index 0000000000..2e7e66a3ed
--- /dev/null
+++ b/test/fixtures/apps/app-server/app/router.js
@@ -0,0 +1,5 @@
+module.exports = app => {
+ app.get('/', function* () {
+ this.body = this.app.serverEmit;
+ });
+};
diff --git a/test/fixtures/apps/app-server/config/config.default.js b/test/fixtures/apps/app-server/config/config.default.js
new file mode 100644
index 0000000000..ea93c9c13a
--- /dev/null
+++ b/test/fixtures/apps/app-server/config/config.default.js
@@ -0,0 +1 @@
+exports.keys = 'my keys';
diff --git a/test/fixtures/apps/app-server/package.json b/test/fixtures/apps/app-server/package.json
new file mode 100644
index 0000000000..bfdb7df8be
--- /dev/null
+++ b/test/fixtures/apps/app-server/package.json
@@ -0,0 +1,3 @@
+{
+ "name": "app-server"
+}
diff --git a/test/fixtures/apps/body_parser_testapp/app/router.js b/test/fixtures/apps/body_parser_testapp/app/router.js
new file mode 100644
index 0000000000..276433c1f8
--- /dev/null
+++ b/test/fixtures/apps/body_parser_testapp/app/router.js
@@ -0,0 +1,19 @@
+module.exports = app => {
+ app.get('/test/body_parser/user', function* () {
+ this.body = {
+ url: this.url,
+ csrf: this.csrf
+ };
+ });
+
+ app.post('/test/body_parser/user', function* () {
+ this.body = this.request.body;
+ });
+
+ app.post('/test/body_parser/foo.json', function* () {
+ this.body = this.request.body;
+ });
+ app.post('/test/body_parser/form.json', function* () {
+ this.body = this.request.body;
+ });
+};
diff --git a/test/fixtures/apps/body_parser_testapp/config/config.default.js b/test/fixtures/apps/body_parser_testapp/config/config.default.js
new file mode 100644
index 0000000000..2960d82751
--- /dev/null
+++ b/test/fixtures/apps/body_parser_testapp/config/config.default.js
@@ -0,0 +1,7 @@
+exports.bodyParser = {
+ queryString: {
+ arrayLimit: 5
+ }
+};
+
+exports.keys = 'foo';
diff --git a/test/fixtures/apps/body_parser_testapp/package.json b/test/fixtures/apps/body_parser_testapp/package.json
new file mode 100644
index 0000000000..6fa5c4721f
--- /dev/null
+++ b/test/fixtures/apps/body_parser_testapp/package.json
@@ -0,0 +1,3 @@
+{
+ "name": "body_parser_testapp"
+}
diff --git a/test/fixtures/apps/close-watcher-logrotator/config/config.default.js b/test/fixtures/apps/close-watcher-logrotator/config/config.default.js
new file mode 100644
index 0000000000..b6cba897c8
--- /dev/null
+++ b/test/fixtures/apps/close-watcher-logrotator/config/config.default.js
@@ -0,0 +1 @@
+exports.keys = 'foo';
diff --git a/test/fixtures/apps/close-watcher-logrotator/config/plugin.js b/test/fixtures/apps/close-watcher-logrotator/config/plugin.js
new file mode 100644
index 0000000000..01bdf67a1e
--- /dev/null
+++ b/test/fixtures/apps/close-watcher-logrotator/config/plugin.js
@@ -0,0 +1,2 @@
+exports.logrotater = false;
+exports.watcher = false;
diff --git a/test/fixtures/apps/close-watcher-logrotator/package.json b/test/fixtures/apps/close-watcher-logrotator/package.json
new file mode 100644
index 0000000000..d1d0107055
--- /dev/null
+++ b/test/fixtures/apps/close-watcher-logrotator/package.json
@@ -0,0 +1,3 @@
+{
+ "name": "close-watcher-logrotator"
+}
diff --git a/test/fixtures/apps/cluster_mod_app/app/controller/home.js b/test/fixtures/apps/cluster_mod_app/app/controller/home.js
new file mode 100644
index 0000000000..58a4ac160a
--- /dev/null
+++ b/test/fixtures/apps/cluster_mod_app/app/controller/home.js
@@ -0,0 +1,5 @@
+'use strict';
+
+exports.index = function* () {
+ this.body = 'hi cluster';
+};
diff --git a/test/fixtures/apps/cluster_mod_app/app/router.js b/test/fixtures/apps/cluster_mod_app/app/router.js
new file mode 100644
index 0000000000..095958ec4f
--- /dev/null
+++ b/test/fixtures/apps/cluster_mod_app/app/router.js
@@ -0,0 +1,5 @@
+'use strict';
+
+module.exports = app => {
+ app.get('/', app.controller.home.index);
+};
diff --git a/test/fixtures/apps/cluster_mod_app/config/config.default.js b/test/fixtures/apps/cluster_mod_app/config/config.default.js
new file mode 100644
index 0000000000..b6cba897c8
--- /dev/null
+++ b/test/fixtures/apps/cluster_mod_app/config/config.default.js
@@ -0,0 +1 @@
+exports.keys = 'foo';
diff --git a/test/fixtures/apps/cluster_mod_app/package.json b/test/fixtures/apps/cluster_mod_app/package.json
new file mode 100644
index 0000000000..cf745b625a
--- /dev/null
+++ b/test/fixtures/apps/cluster_mod_app/package.json
@@ -0,0 +1,3 @@
+{
+ "name": "cluster_mod_app"
+}
diff --git a/test/fixtures/apps/context-config-app/app/context.js b/test/fixtures/apps/context-config-app/app/context.js
new file mode 100644
index 0000000000..aca4298022
--- /dev/null
+++ b/test/fixtures/apps/context-config-app/app/context.js
@@ -0,0 +1,6 @@
+module.exports = {
+ foo: 1,
+ bar: function() {
+ return 2;
+ }
+};
\ No newline at end of file
diff --git a/test/fixtures/apps/context-config-app/app/controller/home.js b/test/fixtures/apps/context-config-app/app/controller/home.js
new file mode 100644
index 0000000000..5a114e9013
--- /dev/null
+++ b/test/fixtures/apps/context-config-app/app/controller/home.js
@@ -0,0 +1,18 @@
+'use strict';
+
+exports.index = function* () {
+ this.body = {
+ path: this.router.pathFor('home'),
+ foo: this.foo,
+ bar: this.bar()
+ };
+};
+
+exports.runtime = function* () {
+ this.runtime.mysql = 10;
+ this.runtime.foo = 11;
+ this.body = {
+ mysql: this.runtime.mysql,
+ foo: this.runtime.foo
+ };
+};
diff --git a/test/fixtures/apps/context-config-app/app/router.js b/test/fixtures/apps/context-config-app/app/router.js
new file mode 100644
index 0000000000..8deaf7cc89
--- /dev/null
+++ b/test/fixtures/apps/context-config-app/app/router.js
@@ -0,0 +1,4 @@
+module.exports = app => {
+ app.get('home', '/', 'home.index');
+ app.get('runtime', '/runtime', 'home.runtime');
+};
diff --git a/test/fixtures/apps/context-config-app/config/config.js b/test/fixtures/apps/context-config-app/config/config.js
new file mode 100644
index 0000000000..31f997dc14
--- /dev/null
+++ b/test/fixtures/apps/context-config-app/config/config.js
@@ -0,0 +1,5 @@
+exports.security = {
+ csrf: false
+}
+
+exports.keys = 'foo';
diff --git a/test/fixtures/apps/context-config-app/config/config.local.js b/test/fixtures/apps/context-config-app/config/config.local.js
new file mode 100644
index 0000000000..927ed4b05f
--- /dev/null
+++ b/test/fixtures/apps/context-config-app/config/config.local.js
@@ -0,0 +1,5 @@
+'use stirct';
+
+exports.logger = {
+ stdoutLevel: 'NONE',
+};
diff --git a/test/fixtures/apps/context-config-app/package.json b/test/fixtures/apps/context-config-app/package.json
new file mode 100644
index 0000000000..ef64023eba
--- /dev/null
+++ b/test/fixtures/apps/context-config-app/package.json
@@ -0,0 +1,3 @@
+{
+ "name": "context-config-app"
+}
diff --git a/test/fixtures/apps/cors/app/router.js b/test/fixtures/apps/cors/app/router.js
new file mode 100644
index 0000000000..2222e47507
--- /dev/null
+++ b/test/fixtures/apps/cors/app/router.js
@@ -0,0 +1,13 @@
+module.exports = app => {
+ app.get('/', function*() {
+ this.body = {
+ foo: 'bar'
+ };
+ });
+
+ app.post('/', function*() {
+ this.body = {
+ foo: 'bar'
+ };
+ });
+};
diff --git a/test/fixtures/apps/cors/config/config.default.js b/test/fixtures/apps/cors/config/config.default.js
new file mode 100644
index 0000000000..d9b93b9490
--- /dev/null
+++ b/test/fixtures/apps/cors/config/config.default.js
@@ -0,0 +1,3 @@
+exports.cors = {
+ credentials: true,
+};
diff --git a/test/fixtures/apps/cors/config/plugin.js b/test/fixtures/apps/cors/config/plugin.js
new file mode 100644
index 0000000000..5347247057
--- /dev/null
+++ b/test/fixtures/apps/cors/config/plugin.js
@@ -0,0 +1 @@
+exports.cors = true;
diff --git a/test/fixtures/apps/cors/package.json b/test/fixtures/apps/cors/package.json
new file mode 100644
index 0000000000..be8a359c74
--- /dev/null
+++ b/test/fixtures/apps/cors/package.json
@@ -0,0 +1,3 @@
+{
+ "name": "cors"
+}
diff --git a/test/fixtures/apps/custom-env-app/app/router.js b/test/fixtures/apps/custom-env-app/app/router.js
new file mode 100644
index 0000000000..a4e9705aea
--- /dev/null
+++ b/test/fixtures/apps/custom-env-app/app/router.js
@@ -0,0 +1,9 @@
+'use strict';
+
+module.exports = app => {
+ app.get('/', function*() {
+ this.body = {
+ env: this.app.config.env,
+ };
+ });
+};
diff --git a/test/fixtures/apps/custom-env-app/config/config.default.js b/test/fixtures/apps/custom-env-app/config/config.default.js
new file mode 100644
index 0000000000..5365ab0bf2
--- /dev/null
+++ b/test/fixtures/apps/custom-env-app/config/config.default.js
@@ -0,0 +1,3 @@
+'use strict';
+
+exports.keys = 'foo';
diff --git a/test/fixtures/apps/custom-env-app/package.json b/test/fixtures/apps/custom-env-app/package.json
new file mode 100644
index 0000000000..2ba8c94568
--- /dev/null
+++ b/test/fixtures/apps/custom-env-app/package.json
@@ -0,0 +1,3 @@
+{
+ "name": "custom-env-app"
+}
diff --git a/test/fixtures/apps/custom-logger/config/config.default.js b/test/fixtures/apps/custom-logger/config/config.default.js
new file mode 100644
index 0000000000..77c1855b03
--- /dev/null
+++ b/test/fixtures/apps/custom-logger/config/config.default.js
@@ -0,0 +1,14 @@
+'use strict';
+
+const path = require('path');
+
+module.exports = info => {
+ return {
+ customLogger: {
+ myLogger: {
+ file: path.join(info.baseDir, 'logs/my.log'),
+ formatter: meta => meta.message,
+ },
+ },
+ };
+};
diff --git a/test/fixtures/apps/custom-logger/package.json b/test/fixtures/apps/custom-logger/package.json
new file mode 100644
index 0000000000..aa5619cadc
--- /dev/null
+++ b/test/fixtures/apps/custom-logger/package.json
@@ -0,0 +1,3 @@
+{
+ "name": "custom-logger"
+}
diff --git a/test/fixtures/apps/demo/app/controller/hello.js b/test/fixtures/apps/demo/app/controller/hello.js
new file mode 100644
index 0000000000..a8f3b30296
--- /dev/null
+++ b/test/fixtures/apps/demo/app/controller/hello.js
@@ -0,0 +1,6 @@
+'use strict';
+
+module.exports = function*() {
+ this.setCookie('hi', 'foo');
+ this.body = 'hello';
+};
diff --git a/test/fixtures/apps/demo/app/controller/home.js b/test/fixtures/apps/demo/app/controller/home.js
new file mode 100644
index 0000000000..fb335b81ac
--- /dev/null
+++ b/test/fixtures/apps/demo/app/controller/home.js
@@ -0,0 +1,5 @@
+module.exports = function* () {
+ this.body = {
+ workerTitle: process.title
+ };
+};
diff --git a/test/fixtures/apps/demo/app/controller/logger.js b/test/fixtures/apps/demo/app/controller/logger.js
new file mode 100644
index 0000000000..d56ffddb83
--- /dev/null
+++ b/test/fixtures/apps/demo/app/controller/logger.js
@@ -0,0 +1,17 @@
+'use strict';
+
+module.exports = function*() {
+ const message = this.query.message;
+
+ this.logger.debug('debug %s', message);
+ this.logger.info('info %s', message);
+ this.logger.warn('warn %s', message);
+ this.logger.error(new Error('error ' + message));
+
+ this.coreLogger.debug('core debug %s', message);
+ this.coreLogger.info('core info %s', message);
+ this.coreLogger.warn('core warn %s', message);
+ this.coreLogger.error(new Error('core error ' + message));
+
+ this.body = 'logger';
+};
diff --git a/test/fixtures/apps/demo/app/router.js b/test/fixtures/apps/demo/app/router.js
new file mode 100644
index 0000000000..ae6b825305
--- /dev/null
+++ b/test/fixtures/apps/demo/app/router.js
@@ -0,0 +1,12 @@
+module.exports = app => {
+ app.get('home', '/', 'home');
+ app.get('/hello', app.controller.hello);
+ app.get('/logger', app.controller.logger);
+ app.get('/protocol', function*() {
+ this.body = this.protocol;
+ });
+
+ app.get('/user.json', function*() {
+ this.jsonp = { name: 'fengmk2' };
+ });
+};
diff --git a/test/fixtures/apps/demo/config/config.default.js b/test/fixtures/apps/demo/config/config.default.js
new file mode 100644
index 0000000000..d4fbaa981d
--- /dev/null
+++ b/test/fixtures/apps/demo/config/config.default.js
@@ -0,0 +1,2 @@
+exports.keys = 'foo';
+exports.protocolHeaders = 'X-Forwarded-Proto';
diff --git a/test/fixtures/apps/demo/package.json b/test/fixtures/apps/demo/package.json
new file mode 100644
index 0000000000..e38ae2ede5
--- /dev/null
+++ b/test/fixtures/apps/demo/package.json
@@ -0,0 +1,3 @@
+{
+ "name": "demo"
+}
diff --git a/test/fixtures/apps/development/app/public/foo.js b/test/fixtures/apps/development/app/public/foo.js
new file mode 100644
index 0000000000..fb1c35f957
--- /dev/null
+++ b/test/fixtures/apps/development/app/public/foo.js
@@ -0,0 +1 @@
+alert('bar');
diff --git a/test/fixtures/apps/development/app/router.js b/test/fixtures/apps/development/app/router.js
new file mode 100644
index 0000000000..e0cefa5068
--- /dev/null
+++ b/test/fixtures/apps/development/app/router.js
@@ -0,0 +1,11 @@
+'use strict';
+
+module.exports = app => {
+ app.get('/foo.js', function* () {
+ this.body = 'foo.js';
+ });
+
+ app.get('/foo', function* () {
+ this.body = 'foo';
+ });
+};
diff --git a/test/fixtures/apps/development/config/config.default.js b/test/fixtures/apps/development/config/config.default.js
new file mode 100644
index 0000000000..b6cba897c8
--- /dev/null
+++ b/test/fixtures/apps/development/config/config.default.js
@@ -0,0 +1 @@
+exports.keys = 'foo';
diff --git a/test/fixtures/apps/development/config/plugin.js b/test/fixtures/apps/development/config/plugin.js
new file mode 100644
index 0000000000..fadc25284d
--- /dev/null
+++ b/test/fixtures/apps/development/config/plugin.js
@@ -0,0 +1 @@
+exports.static = true;
diff --git a/test/fixtures/apps/development/package.json b/test/fixtures/apps/development/package.json
new file mode 100644
index 0000000000..64279c6306
--- /dev/null
+++ b/test/fixtures/apps/development/package.json
@@ -0,0 +1,3 @@
+{
+ "name": "development"
+}
diff --git a/test/fixtures/apps/encrypt-cookies/app/controller/home.js b/test/fixtures/apps/encrypt-cookies/app/controller/home.js
new file mode 100644
index 0000000000..3c687c3fa3
--- /dev/null
+++ b/test/fixtures/apps/encrypt-cookies/app/controller/home.js
@@ -0,0 +1,17 @@
+module.exports = function* () {
+ var encrypt = this.getCookie('foo', {
+ encrypt: true
+ });
+ var encryptWrong = this.getCookie('foo');
+ var plain = this.getCookie('plain');
+ this.setCookie('foo', 'bar 中文', {
+ encrypt: true
+ });
+ this.setCookie('plain', 'text ok');
+ this.body = {
+ set: 'bar 中文',
+ encrypt: encrypt,
+ encryptWrong: encryptWrong,
+ plain: plain,
+ };
+};
diff --git a/test/fixtures/apps/encrypt-cookies/app/router.js b/test/fixtures/apps/encrypt-cookies/app/router.js
new file mode 100644
index 0000000000..fa70390f6a
--- /dev/null
+++ b/test/fixtures/apps/encrypt-cookies/app/router.js
@@ -0,0 +1,3 @@
+module.exports = app => {
+ app.get('/', app.controller.home);
+};
diff --git a/test/fixtures/apps/encrypt-cookies/config/config.default.js b/test/fixtures/apps/encrypt-cookies/config/config.default.js
new file mode 100644
index 0000000000..b6cba897c8
--- /dev/null
+++ b/test/fixtures/apps/encrypt-cookies/config/config.default.js
@@ -0,0 +1 @@
+exports.keys = 'foo';
diff --git a/test/fixtures/apps/encrypt-cookies/package.json b/test/fixtures/apps/encrypt-cookies/package.json
new file mode 100644
index 0000000000..16a18b2476
--- /dev/null
+++ b/test/fixtures/apps/encrypt-cookies/package.json
@@ -0,0 +1,3 @@
+{
+ "name": "encrypt-cookies"
+}
diff --git a/test/fixtures/apps/favicon/app/controller/home.js b/test/fixtures/apps/favicon/app/controller/home.js
new file mode 100644
index 0000000000..ac412a98d8
--- /dev/null
+++ b/test/fixtures/apps/favicon/app/controller/home.js
@@ -0,0 +1,5 @@
+'use strict';
+
+module.exports = function*() {
+ this.body = 'home';
+};
diff --git a/test/fixtures/apps/favicon/app/router.js b/test/fixtures/apps/favicon/app/router.js
new file mode 100644
index 0000000000..b3e62cf0b3
--- /dev/null
+++ b/test/fixtures/apps/favicon/app/router.js
@@ -0,0 +1,5 @@
+'use strict';
+
+module.exports = app => {
+ app.get('/', app.controller.home);
+};
diff --git a/test/fixtures/apps/favicon/config/config.default.js b/test/fixtures/apps/favicon/config/config.default.js
new file mode 100644
index 0000000000..c569bde7ed
--- /dev/null
+++ b/test/fixtures/apps/favicon/config/config.default.js
@@ -0,0 +1,5 @@
+exports.siteFile = {
+ '/favicon.ico': 'https://eggjs.org/favicon.ico',
+}
+
+exports.keys = 'foo';
diff --git a/test/fixtures/apps/favicon/package.json b/test/fixtures/apps/favicon/package.json
new file mode 100644
index 0000000000..61f27eae24
--- /dev/null
+++ b/test/fixtures/apps/favicon/package.json
@@ -0,0 +1,3 @@
+{
+ "name": "favicon"
+}
diff --git a/test/fixtures/apps/helper/app/controller/home.js b/test/fixtures/apps/helper/app/controller/home.js
new file mode 100644
index 0000000000..62d57de545
--- /dev/null
+++ b/test/fixtures/apps/helper/app/controller/home.js
@@ -0,0 +1,3 @@
+module.exports = function* () {
+ this.body = 'hello home';
+};
diff --git a/test/fixtures/apps/helper/app/router.js b/test/fixtures/apps/helper/app/router.js
new file mode 100644
index 0000000000..d9c4640fa9
--- /dev/null
+++ b/test/fixtures/apps/helper/app/router.js
@@ -0,0 +1,33 @@
+module.exports = app => {
+ app.get('home', '/home', 'home');
+
+ app.get('/pathFor', function* () {
+ this.body = this.helper.pathFor('home', this.query);
+ });
+
+ app.get('/urlFor', function* () {
+ this.body = this.helper.urlFor('home', this.query);
+ });
+
+ app.get('/escape', function* () {
+ this.body = this.helper.escape('
+
+
diff --git a/test/fixtures/apps/view-render/app/views/shtml.html b/test/fixtures/apps/view-render/app/views/shtml.html
new file mode 100644
index 0000000000..69e5e488a3
--- /dev/null
+++ b/test/fixtures/apps/view-render/app/views/shtml.html
@@ -0,0 +1 @@
+{{helper.shtml(foo)}}
diff --git a/test/fixtures/apps/view-render/app/views/sjs.html b/test/fixtures/apps/view-render/app/views/sjs.html
new file mode 100644
index 0000000000..c790bd3c9b
--- /dev/null
+++ b/test/fixtures/apps/view-render/app/views/sjs.html
@@ -0,0 +1 @@
+var foo = "{{ helper.sjs(foo) }}";
diff --git a/test/fixtures/apps/view-render/app/views/xss.html b/test/fixtures/apps/view-render/app/views/xss.html
new file mode 100644
index 0000000000..0bc6a5eef3
--- /dev/null
+++ b/test/fixtures/apps/view-render/app/views/xss.html
@@ -0,0 +1,4 @@
+{{ url }}
+{{ url | safe }}
+{{ helper.surl(url) }}
+{{ html }}
diff --git a/test/fixtures/apps/view-render/config/config.default.js b/test/fixtures/apps/view-render/config/config.default.js
new file mode 100644
index 0000000000..fbbbdfcbf8
--- /dev/null
+++ b/test/fixtures/apps/view-render/config/config.default.js
@@ -0,0 +1,5 @@
+exports.security = {
+ csp: {
+ enable: true,
+ },
+};
diff --git a/test/fixtures/apps/view-render/config/plugin.js b/test/fixtures/apps/view-render/config/plugin.js
new file mode 100644
index 0000000000..d41f56e103
--- /dev/null
+++ b/test/fixtures/apps/view-render/config/plugin.js
@@ -0,0 +1,6 @@
+'use strict';
+
+exports.view = {
+ enable: true,
+ package: 'egg-view-nunjucks',
+};
diff --git a/test/fixtures/apps/view-render/package.json b/test/fixtures/apps/view-render/package.json
new file mode 100644
index 0000000000..f87c58daea
--- /dev/null
+++ b/test/fixtures/apps/view-render/package.json
@@ -0,0 +1,3 @@
+{
+ "name": "view-render"
+}
diff --git a/test/fixtures/apps/view/app/app.js b/test/fixtures/apps/view/app/app.js
new file mode 100644
index 0000000000..4449388f4a
--- /dev/null
+++ b/test/fixtures/apps/view/app/app.js
@@ -0,0 +1,5 @@
+'use strict';
+
+// view-framework -> view
+module.exports = app => {
+};
diff --git a/test/fixtures/apps/view/app/controller/home.js b/test/fixtures/apps/view/app/controller/home.js
new file mode 100644
index 0000000000..d9c79475ea
--- /dev/null
+++ b/test/fixtures/apps/view/app/controller/home.js
@@ -0,0 +1,16 @@
+'use strict';
+
+exports.index = function *() {
+ this.locals = {b: 'b'};
+ yield this.render('index.html', {
+ a: '111',
+ });
+};
+
+exports.string = function *() {
+ this.locals = {b: 'b'};
+ this.body = yield this.renderString('{{a}}', {
+ a: '111',
+ });
+};
+
diff --git a/test/fixtures/apps/view/app/helper.js b/test/fixtures/apps/view/app/helper.js
new file mode 100644
index 0000000000..7867d12722
--- /dev/null
+++ b/test/fixtures/apps/view/app/helper.js
@@ -0,0 +1,3 @@
+exports.testHelper = () => {
+ return 'testHelper';
+};
diff --git a/test/fixtures/apps/view/app/router.js b/test/fixtures/apps/view/app/router.js
new file mode 100644
index 0000000000..e548171535
--- /dev/null
+++ b/test/fixtures/apps/view/app/router.js
@@ -0,0 +1,6 @@
+'use strict';
+
+module.exports = app => {
+ app.get('/', app.controller.home.index);
+ app.get('/string', app.controller.home.string);
+};
diff --git a/test/fixtures/apps/view/app/views/index.html b/test/fixtures/apps/view/app/views/index.html
new file mode 100644
index 0000000000..1faa87a613
--- /dev/null
+++ b/test/fixtures/apps/view/app/views/index.html
@@ -0,0 +1,2 @@
+{{a}}
+{{helper.testHelper()}}
diff --git a/test/fixtures/apps/view/config/config.default.js b/test/fixtures/apps/view/config/config.default.js
new file mode 100644
index 0000000000..b6cba897c8
--- /dev/null
+++ b/test/fixtures/apps/view/config/config.default.js
@@ -0,0 +1 @@
+exports.keys = 'foo';
diff --git a/test/fixtures/apps/view/package.json b/test/fixtures/apps/view/package.json
new file mode 100644
index 0000000000..d03eee3ee5
--- /dev/null
+++ b/test/fixtures/apps/view/package.json
@@ -0,0 +1,3 @@
+{
+ "name": "view"
+}
diff --git a/test/fixtures/apps/watcher-development-app/agent.js b/test/fixtures/apps/watcher-development-app/agent.js
new file mode 100644
index 0000000000..796e70652f
--- /dev/null
+++ b/test/fixtures/apps/watcher-development-app/agent.js
@@ -0,0 +1,26 @@
+'use strict';
+
+const utils = require('../../../utils');
+const file_path1 = utils.getFilepath('apps/watcher-development-app/tmp-agent.txt');
+const dir_path = utils.getFilepath('apps/watcher-development-app/tmp-agent');
+
+module.exports = function(agent) {
+ let count = 0;
+ function listener() {
+ count++;
+ }
+
+ agent.messenger.on('i-want-agent-file-changed-count', function() {
+ agent.messenger.broadcast('agent-file-changed-count', count);
+ });
+
+ agent.messenger.on('agent-unwatch', function() {
+ agent.watcher.unwatch([file_path1, dir_path], listener);
+ agent.messenger.broadcast('agent-unwatch-success', 'agent unwatch success');
+ });
+
+ agent.messenger.on('agent-watch', function() {
+ agent.watcher.watch([file_path1, dir_path], listener);
+ agent.messenger.broadcast('agent-watch-success', 'agent watch success');
+ });
+};
diff --git a/test/fixtures/apps/watcher-development-app/app/router.js b/test/fixtures/apps/watcher-development-app/app/router.js
new file mode 100644
index 0000000000..7722b41bec
--- /dev/null
+++ b/test/fixtures/apps/watcher-development-app/app/router.js
@@ -0,0 +1,55 @@
+'use strict';
+
+const utils = require('../../../../utils');
+const file_path1 = utils.getFilepath('apps/watcher-development-app/tmp.txt');
+// const file_path2 = utils.getFilePath('apps/watcher-development-app/tmp/tmp.txt');
+const dir_path = utils.getFilepath('apps/watcher-development-app/tmp');
+
+module.exports = function(app) {
+ let fileChangeCount = 0;
+
+ function callback() {
+ fileChangeCount++;
+ }
+
+ app.get('/app-watch', function*() {
+ app.watcher.watch([file_path1, dir_path], callback);
+ this.body = 'app watch success';
+ });
+
+ app.get('/app-unwatch', function*() {
+ app.watcher.unwatch([file_path1, dir_path], callback);
+ this.body = 'app unwatch success';
+ });
+
+ app.get('/app-msg', function*() {
+ this.body = fileChangeCount;
+ });
+
+ app.get('/agent-watch', function*() {
+ app.messenger.broadcast('agent-watch');
+ this.body = yield new Promise(function(resolve) {
+ app.messenger.on('agent-watch-success', function(msg) {
+ resolve(msg);
+ });
+ });
+ });
+
+ app.get('/agent-unwatch', function*() {
+ app.messenger.broadcast('agent-unwatch');
+ this.body = yield new Promise(function(resolve) {
+ app.messenger.on('agent-unwatch-success', function(msg) {
+ resolve(msg);
+ });
+ });
+ });
+
+ app.get('/agent-msg', function*() {
+ app.messenger.broadcast('i-want-agent-file-changed-count');
+ this.body = yield new Promise(function(resolve) {
+ app.messenger.on('agent-file-changed-count', function(msg) {
+ resolve(msg);
+ });
+ });
+ });
+};
diff --git a/test/fixtures/apps/watcher-development-app/config/config.unittest.js b/test/fixtures/apps/watcher-development-app/config/config.unittest.js
new file mode 100644
index 0000000000..ed9616cfbf
--- /dev/null
+++ b/test/fixtures/apps/watcher-development-app/config/config.unittest.js
@@ -0,0 +1,7 @@
+'use strict';
+
+exports.env = 'local';
+
+exports.watcher = {
+ type: 'development',
+};
diff --git a/test/fixtures/apps/watcher-development-app/package.json b/test/fixtures/apps/watcher-development-app/package.json
new file mode 100644
index 0000000000..ccab716e55
--- /dev/null
+++ b/test/fixtures/apps/watcher-development-app/package.json
@@ -0,0 +1,3 @@
+{
+ "name": "watcher-app"
+}
\ No newline at end of file
diff --git a/test/fixtures/apps/watcher-development-app/tmp-agent.txt b/test/fixtures/apps/watcher-development-app/tmp-agent.txt
new file mode 100644
index 0000000000..01f02e32ce
--- /dev/null
+++ b/test/fixtures/apps/watcher-development-app/tmp-agent.txt
@@ -0,0 +1 @@
+bbb
\ No newline at end of file
diff --git a/test/fixtures/apps/watcher-development-app/tmp-agent/tmp.txt b/test/fixtures/apps/watcher-development-app/tmp-agent/tmp.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/test/fixtures/apps/watcher-development-app/tmp.txt b/test/fixtures/apps/watcher-development-app/tmp.txt
new file mode 100644
index 0000000000..7c4a013e52
--- /dev/null
+++ b/test/fixtures/apps/watcher-development-app/tmp.txt
@@ -0,0 +1 @@
+aaa
\ No newline at end of file
diff --git a/test/fixtures/apps/watcher-development-app/tmp/tmp.txt b/test/fixtures/apps/watcher-development-app/tmp/tmp.txt
new file mode 100644
index 0000000000..7c4a013e52
--- /dev/null
+++ b/test/fixtures/apps/watcher-development-app/tmp/tmp.txt
@@ -0,0 +1 @@
+aaa
\ No newline at end of file
diff --git a/test/fixtures/apps/watcher-type-default/config/config.unittest.js b/test/fixtures/apps/watcher-type-default/config/config.unittest.js
new file mode 100644
index 0000000000..bef6520082
--- /dev/null
+++ b/test/fixtures/apps/watcher-type-default/config/config.unittest.js
@@ -0,0 +1,5 @@
+'use strict';
+
+exports.watcher = {
+ type: 'default',
+};
diff --git a/test/fixtures/apps/watcher-type-default/package.json b/test/fixtures/apps/watcher-type-default/package.json
new file mode 100644
index 0000000000..de0a2af5f7
--- /dev/null
+++ b/test/fixtures/apps/watcher-type-default/package.json
@@ -0,0 +1,3 @@
+{
+ "name": "watcher-type-default"
+}
\ No newline at end of file
diff --git a/test/fixtures/apps/worker-die/app.js b/test/fixtures/apps/worker-die/app.js
new file mode 100644
index 0000000000..e66241423e
--- /dev/null
+++ b/test/fixtures/apps/worker-die/app.js
@@ -0,0 +1 @@
+throw new Error('dddd');
\ No newline at end of file
diff --git a/test/fixtures/apps/worker-die/package.json b/test/fixtures/apps/worker-die/package.json
new file mode 100644
index 0000000000..475e7df300
--- /dev/null
+++ b/test/fixtures/apps/worker-die/package.json
@@ -0,0 +1,3 @@
+{
+ "name": "worker-die"
+}
diff --git a/test/fixtures/custom-egg/index.js b/test/fixtures/custom-egg/index.js
new file mode 100644
index 0000000000..8f3703b506
--- /dev/null
+++ b/test/fixtures/custom-egg/index.js
@@ -0,0 +1,3 @@
+'use strict';
+
+module.exports = require('../../../index');
diff --git a/test/fixtures/custom-egg/package.json b/test/fixtures/custom-egg/package.json
new file mode 100644
index 0000000000..377e8c356a
--- /dev/null
+++ b/test/fixtures/custom-egg/package.json
@@ -0,0 +1,3 @@
+{
+ "name": "custom-egg"
+}
diff --git a/test/lib/agent.test.js b/test/lib/agent.test.js
new file mode 100644
index 0000000000..c1b91dcf5c
--- /dev/null
+++ b/test/lib/agent.test.js
@@ -0,0 +1,88 @@
+'use strict';
+
+const should = require('should');
+const fs = require('fs');
+const path = require('path');
+const request = require('supertest');
+const execSync = require('child_process').execSync;
+const mm = require('egg-mock');
+const Agent = require('../../lib/agent');
+const utils = require('../utils');
+
+describe('test/lib/agent.test.js', () => {
+
+ afterEach(mm.restore);
+
+ describe('agent.dumpConfig()', () => {
+ it('should dump config and plugins', () => {
+ const baseDir = path.join(__dirname, '../fixtures/apps/demo');
+ new Agent({
+ baseDir,
+ });
+ const json = require(path.join(baseDir, 'run/agent_config.json'));
+ json.plugins.onerror.version.should.match(/\d+\.\d+\.\d+/);
+ json.config.name.should.equal('demo');
+ });
+ });
+
+ describe('require agent', () => {
+ it('should exit normal', () => {
+ execSync(`${process.execPath} -e "require('./lib/agent')"`, {
+ timeout: 3000,
+ });
+ });
+ });
+
+ describe('close()', () => {
+ it('should close all listeners', function() {
+ const baseDir = path.join(__dirname, '../fixtures/apps/demo');
+ const agent = new Agent({
+ baseDir,
+ });
+ process.listeners('unhandledRejection')
+ .indexOf(agent._unhandledRejectionHandler).should.not.equal(-1);
+ agent.close();
+ process.listeners('unhandledRejection')
+ .indexOf(agent._unhandledRejectionHandler).should.equal(-1);
+ });
+
+ it('should emit close event before exit', () => {
+ const baseDir = path.join(__dirname, '../fixtures/apps/demo');
+ const agent = new Agent({
+ baseDir,
+ });
+ let called = false;
+ agent.on('close', () => {
+ called = true;
+ });
+ agent.close();
+ called.should.equal(true);
+ });
+ });
+
+ describe('agent throw', () => {
+ const baseDir = utils.getFilepath('apps/agent-throw');
+ let app;
+ before(() => {
+ mm(process.env, 'EGG_LOG', 'none');
+ app = utils.cluster('apps/agent-throw');
+ return app.ready();
+ });
+
+ after(() => app.close());
+
+ it('should catch exeption', done => {
+ request(app.callback())
+ .get('/agent-throw')
+ .expect(200, err => {
+ should.not.exists(err);
+ setTimeout(() => {
+ const body = fs.readFileSync(path.join(baseDir, 'logs/agent-throw/common-error.log'), 'utf8');
+ body.should.containEql('nodejs.unhandledExceptionError: agent error');
+ app.notExpect(/nodejs.AgentWorkerDiedError/);
+ done();
+ }, 1000);
+ });
+ });
+ });
+});
diff --git a/test/lib/application.test.js b/test/lib/application.test.js
new file mode 100644
index 0000000000..0fb7d0aaf8
--- /dev/null
+++ b/test/lib/application.test.js
@@ -0,0 +1,93 @@
+'use strict';
+
+const Application = require('../../lib/application');
+const path = require('path');
+
+describe('test/lib/application.test.js', () => {
+ describe('create application', () => {
+ it('should throw options.baseDir required', () => {
+ (function() {
+ new Application({
+ baseDir: 1,
+ });
+ }).should.throw('options.baseDir required, and must be a string');
+ });
+
+ it('should throw options.baseDir not exist', () => {
+ (function() {
+ new Application({
+ baseDir: 'not-exist',
+ });
+ }).should.throw('Directory not-exist not exists');
+ });
+
+ it('should throw options.baseDir is not a directory', () => {
+ (function() {
+ new Application({
+ baseDir: __filename,
+ });
+ }).should.throw(`Directory ${__filename} is not a directory`);
+ });
+ });
+
+ describe('application.deprecate', () => {
+ it('should get deprecate with namespace egg', () => {
+ const app = createApplication();
+ const deprecate = app.deprecate;
+ deprecate._namespace.should.equal('egg');
+ deprecate.should.equal(app.deprecate);
+ });
+ });
+
+ describe('curl()', () => {
+ it('should curl success', function* () {
+ const app = createApplication();
+ const res = yield app.curl('https://a.alipayobjects.com/aliBridge/1.0.0/aliBridge.min.js');
+ res.status.should.equal(200);
+ });
+ });
+
+ describe('dumpConfig()', () => {
+ it('should dump config and plugins', () => {
+ const baseDir = path.join(__dirname, '../fixtures/apps/demo');
+ new Application({
+ baseDir,
+ });
+ const json = require(path.join(baseDir, 'run/application_config.json'));
+ json.plugins.onerror.version.should.match(/\d+\.\d+\.\d+/);
+ json.config.name.should.equal('demo');
+ });
+ });
+
+ describe('close()', () => {
+ it('should close all listeners', () => {
+ const baseDir = path.join(__dirname, '../fixtures/apps/demo');
+ const application = new Application({
+ baseDir,
+ });
+ process.listeners('unhandledRejection')
+ .indexOf(application._unhandledRejectionHandler).should.not.equal(-1);
+ application.close();
+ process.listeners('unhandledRejection')
+ .indexOf(application._unhandledRejectionHandler).should.equal(-1);
+ });
+ it('should emit close event before exit', () => {
+ const baseDir = path.join(__dirname, '../fixtures/apps/demo');
+ const application = new Application({
+ baseDir,
+ });
+ let called = false;
+ application.on('close', () => {
+ called = true;
+ });
+ application.close();
+ called.should.equal(true);
+ });
+ });
+});
+
+function createApplication(options) {
+ options = options || {};
+ options.baseDir = options.baseDir || path.join(__dirname, '../fixtures/apps/demo');
+ return new Application(options);
+}
diff --git a/test/lib/cluster/agent_worker.test.js b/test/lib/cluster/agent_worker.test.js
new file mode 100644
index 0000000000..3814101b98
--- /dev/null
+++ b/test/lib/cluster/agent_worker.test.js
@@ -0,0 +1,86 @@
+'use strict';
+
+const fs = require('fs');
+const path = require('path');
+const should = require('should');
+const rimraf = require('rimraf');
+const mm = require('egg-mock');
+const glob = require('glob');
+const utils = require('../../utils');
+const Agent = require('../../..').Agent;
+
+const fixtures = path.join(__dirname, '../../fixtures');
+
+describe.skip('test/lib/cluster/agent_worker.test.js', () => {
+
+ afterEach(mm.restore);
+
+ describe('agent custom loggers', () => {
+ let agent;
+
+ before(() => {
+ rimraf.sync(path.join(fixtures, 'apps/custom-logger/logs'));
+ agent = new Agent({
+ baseDir: path.join(fixtures, 'apps/custom-logger'),
+ });
+ });
+
+ it('should support custom logger in agent', done => {
+ should.exist(agent.loggers);
+ should.exist(agent.loggers.myLogger);
+
+ agent.loggers.myLogger.info('hello my logger!');
+
+ setTimeout(() => {
+ fs.readFileSync(path.join(fixtures, 'apps/custom-logger/logs/my.log'), 'utf8')
+ .should.equal('hello my logger!\n');
+ done();
+ }, 1500);
+ });
+
+ it('should reload log in agent', done => {
+ rimraf.sync(path.join(fixtures, 'apps/custom-logger/logs/my.log'));
+
+ process.emit('message', {
+ action: 'test-reload-logger',
+ });
+
+ setTimeout(() => {
+ agent.loggers.myLogger.info('goodbye my logger!');
+
+ setTimeout(() => {
+ fs.readFileSync(path.join(fixtures, 'apps/custom-logger/logs/my.log'), 'utf8')
+ .should.equal('goodbye my logger!\n');
+ done();
+ }, 1500);
+ }, 200);
+ });
+ });
+
+ describe('logrotater', () => {
+ let app;
+ before(done => {
+ mm(process.env, 'EGG_LOG', 'NONE');
+ app = utils.cluster('apps/app-monitor');
+ app.ready(done);
+ });
+
+ after(() => app.close());
+
+ it('should cut the log file', done => {
+ const baseDir = utils.getFilepath('apps/app-monitor');
+ this.app.process.send({
+ to: 'agent',
+ action: 'test-reload-logger',
+ });
+ setTimeout(() => {
+ const files = glob.sync(path.join(baseDir, 'logs/app-monitor/*.log.*'));
+ files.length.should.above(0);
+ files.forEach(file => {
+ file.should.match(/log.\d{4}-\d{2}-\d{2}$/);
+ });
+ done();
+ }, 1000);
+ });
+ });
+});
diff --git a/test/lib/cluster/app_worker.test.js b/test/lib/cluster/app_worker.test.js
new file mode 100644
index 0000000000..6da5907395
--- /dev/null
+++ b/test/lib/cluster/app_worker.test.js
@@ -0,0 +1,22 @@
+'use strict';
+
+const request = require('supertest-as-promised');
+const utils = require('../../utils');
+
+describe('test/lib/cluster/app_worker.test.js', () => {
+ let app;
+ before(done => {
+ app = utils.cluster('apps/app-server', {
+ coverage: true,
+ });
+ app.ready(done);
+ });
+
+ after(() => app.close());
+
+ it('should start cluster success and app worker emit `server` event', () => {
+ return request(app.callback())
+ .get('/')
+ .expect('true');
+ });
+});
diff --git a/test/lib/cluster/master.test.js b/test/lib/cluster/master.test.js
new file mode 100644
index 0000000000..7f785723a5
--- /dev/null
+++ b/test/lib/cluster/master.test.js
@@ -0,0 +1,206 @@
+'use strict';
+
+const fs = require('fs');
+const mm = require('egg-mock');
+const request = require('supertest-as-promised');
+const coffee = require('coffee');
+const utils = require('../../utils');
+
+describe('test/lib/cluster/master.test.js', () => {
+
+ afterEach(mm.restore);
+
+ describe('app worker die', () => {
+ let app;
+ before(() => {
+ mm.env('default');
+ app = utils.cluster('apps/app-die');
+ // app.debug();
+ app.coverage(false);
+ return app.ready();
+ });
+ after(() => app.close());
+
+ it('should restart after app worker exit', done => {
+ request(app.callback())
+ .get('/exit')
+ // wait for app worker restart
+ .end(() => setTimeout(() => {
+ // test common-error.log
+ const errorFile = utils.getFilepath('apps/app-die/logs/app-die/common-error.log');
+ const content = fs.readFileSync(errorFile, 'utf8');
+ content.should.match(/nodejs\.AppWorkerDiedError: \[master\]/);
+ content.should.match(/App Worker#1:\d+ died/);
+
+ // error pipe to console
+ app.expect('stdout', /App Worker#1:\d+ disconnect/);
+ app.expect('stderr', /nodejs\.AppWorkerDiedError: \[master\]/);
+ app.expect('stderr', /App Worker#1:\d+ died/);
+ app.expect('stdout', /App Worker#2:\d+ started/);
+
+ done();
+ // this will be slow on ci env
+ }, 5000));
+ });
+
+ it('should restart when app worker throw uncaughtException', done => {
+ request(app.callback())
+ .get('/uncaughtException')
+ // wait for app worker restart
+ .end(() => setTimeout(() => {
+ const errorFile = utils.getFilepath('apps/app-die/logs/app-die/common-error.log');
+ const content = fs.readFileSync(errorFile, 'utf8');
+ content.should.match(/nodejs\.Error: get uncaughtException \(uncaughtException throw 1 times on pid:\d+\)/);
+
+ app.expect('stderr', /\[graceful:worker:\d+:uncaughtException\] throw error 1 times/);
+ app.expect('stdout', /App Worker#\d:\d+ started/);
+ done();
+ }, 5000));
+ });
+ });
+
+ describe('Master start fail', () => {
+ let master;
+
+ after(() => master.close());
+
+ it('should master exit with 1', done => {
+ mm(process.env, 'EGG_LOG', 'none');
+ master = utils.cluster('apps/worker-die', { coverage: true });
+ master.expect('code', 1).ready(done);
+ });
+ });
+
+ describe('Master started log', () => {
+ let app;
+
+ afterEach(() => app.close());
+
+ it('should dev env stdout message include "Egg started"', done => {
+ app = utils.cluster('apps/master-worker-started', { coverage: true });
+ app.expect('stdout', /Egg started/).ready(done);
+ });
+
+ it('should production env stdout message include "Egg started"', done => {
+ mm.env('prod');
+ mm(process.env, 'HOME', utils.getFilepath('apps/mock-production-app/config'));
+ app = utils.cluster('apps/mock-production-app', { coverage: true });
+ app.expect('stdout', /Egg started/).ready(done);
+ });
+ });
+
+ describe('--cluster', () => {
+ let app;
+ before(() => {
+ mm(process.env, 'EGG_LOG', 'none');
+ app = utils.cluster('apps/cluster_mod_app', { coverage: true });
+ return app.ready();
+ });
+ after(() => app.close());
+
+ it('should online cluster mode startup success', () => {
+ return request(app.callback())
+ .get('/')
+ .expect('hi cluster')
+ .expect(200);
+ });
+ });
+
+ describe('--dev', () => {
+ let app;
+ before(() => {
+ app = utils.cluster('apps/cluster_mod_app', { coverage: true });
+ return app.ready();
+ });
+ after(() => app.close());
+
+ it('should dev cluster mode startup success', () => {
+ return request(app)
+ .get('/')
+ .expect('hi cluster')
+ .expect(200);
+ });
+ });
+
+ describe('start app with custom env', () => {
+ describe('cluster mode, serverEnv: prod', () => {
+ let app;
+ before(() => {
+ mm.env('prod');
+ mm(process.env, 'HOME', utils.getFilepath('apps/custom-env-app'));
+ app = utils.cluster('apps/custom-env-app');
+ return app.ready();
+ });
+ after(() => app.close());
+
+ it('should start with prod env', () => {
+ return request(app.callback())
+ .get('/')
+ .expect({
+ env: 'prod',
+ })
+ .expect(200);
+ });
+ });
+ });
+
+ describe('framework start', () => {
+ let app;
+ before(() => {
+ // dependencies relation:
+ // aliyun-egg-app -> aliyun-egg-biz -> aliyun-egg -> egg
+ mm(process.env, 'HOME', utils.getFilepath('apps/aliyun-egg-app'));
+ app = utils.cluster('apps/aliyun-egg-app', {
+ customEgg: utils.getFilepath('apps/aliyun-egg-biz'),
+ });
+ return app.ready();
+ });
+ after(() => app.close());
+
+ it('should start success', () => {
+ return request(app.callback())
+ .get('/')
+ .expect({
+ 'aliyun-egg-core': true,
+ 'aliyun-egg-plugin': true,
+ 'aliyun-egg-agent': true,
+ })
+ .expect(200);
+ });
+ });
+
+ describe('spawn start', () => {
+ let app;
+ after(() => {
+ // make sure process exit
+ app.proc.kill('SIGTERM');
+ });
+
+ it('should not cause master die when agent start error', done => {
+ app = coffee.spawn('node', [ utils.getFilepath('apps/agent-die/start.js') ])
+ .coverage(false);
+
+ // spawn can't comunication, so `end` event won't emit
+ setTimeout(() => {
+ app.emit('close', 0);
+ app.notExpect('stderr', /TypeError: process\.send is not a function/);
+ done();
+ }, 10000);
+ });
+ });
+
+ describe.skip('close watcher and logrotator both', () => {
+ let app;
+ before(done => {
+ mm.env('default');
+ app = utils.cluster('apps/close-watcher-logrotator');
+ app.ready(done);
+ });
+
+ after(() => app.close());
+
+ it('agent should exit normal', () => {
+ app.notExpect('stderr', /nodejs\.AgentWorkerDiedError/);
+ });
+ });
+});
diff --git a/test/lib/core/agent_worker_client.test.js b/test/lib/core/agent_worker_client.test.js
new file mode 100644
index 0000000000..6328a74d22
--- /dev/null
+++ b/test/lib/core/agent_worker_client.test.js
@@ -0,0 +1,289 @@
+'use strict';
+
+const should = require('should');
+const mm = require('egg-mock');
+const request = require('supertest-as-promised');
+const sleep = require('co-sleep');
+const path = require('path');
+const EventEmitter = require('events').EventEmitter;
+const utils = require('../../utils');
+const Agent = require('../../..').Agent;
+const fs = require('fs');
+
+const fixtures = path.join(__dirname, '../../fixtures');
+
+describe('test/lib/core/agent_worker_client.test.js', () => {
+ describe('single process', () => {
+ let agent;
+ let client;
+
+ before(done => {
+ agent = new Agent({
+ baseDir: path.join(fixtures, 'apps/demo'),
+ });
+
+ const realClient = Object.assign(Object.create(EventEmitter.prototype), {
+ ready(flagOrFunction) {
+ this._ready = !!this._ready;
+ this._readyCallbacks = this._readyCallbacks || [];
+
+ if (typeof flagOrFunction === 'function') {
+ this._readyCallbacks.push(flagOrFunction);
+ } else {
+ this._ready = !!flagOrFunction;
+ }
+
+ if (this._ready) {
+ this._readyCallbacks.splice(0, Infinity).forEach(callback => process.nextTick(callback));
+ }
+ return this;
+ },
+ getCallback(id, callback) {
+ setTimeout(() => {
+ if (id === 'error') {
+ callback(new Error('mock error'));
+ } else {
+ callback(null, 'mock data');
+ }
+ }, 100);
+ },
+ getData() {
+ return new Promise(resolve => {
+ setTimeout(() => {
+ resolve('mock data');
+ }, 100);
+ });
+ },
+ * getDataGenerator() {
+ yield sleep(100);
+ return 'mock data';
+ },
+ getError() {
+ return new Promise((_, reject) => {
+ setTimeout(() => {
+ reject(new Error('mock error'));
+ }, 100);
+ });
+ },
+ });
+
+ client = agent.startAgent({
+ name: 'mock',
+ client: realClient,
+ subscribe(info, listener) {
+ realClient.on(info.id, listener);
+ },
+ formatKey(info) {
+ return info.id;
+ },
+ });
+ client.ready(done);
+ realClient.ready(true);
+ });
+
+ afterEach(mm.restore);
+
+ it('should ready', done => {
+ client.ready(done);
+
+ setTimeout(() => {
+ client.innerClient.ready(true);
+ }, 100);
+ });
+
+ it('should not exit when dumpConfig error', () => {
+ const writeFileSync = fs.writeFileSync;
+ mm(fs, 'writeFileSync', function() {
+ if (arguments[0] && arguments[0].endsWith('config.json')) {
+ throw new Error('mock error');
+ }
+ writeFileSync.apply(fs, arguments);
+ });
+ new Agent({
+ baseDir: path.join(fixtures, 'apps/demo'),
+ });
+ });
+
+ it('should subscribe well', done => {
+ mm(client, '_broadcast', (command, info) => {
+ command.should.equal('mock_subscribe_changed');
+ info.value.should.equal('mock data');
+ done();
+ });
+
+ client.messenger.emit('mock_subscribe_request', {
+ info: {
+ id: 'mockKey',
+ },
+ pid: 123,
+ });
+
+ setImmediate(() => client.innerClient.emit('mockKey', 'mock data'));
+ });
+
+ it('should invoke callback well', done => {
+ mm(client, '_sendTo', (pid, action, data) => {
+ pid.should.equal(123);
+ action.should.equal('mock_invoke_response');
+ data.success.should.equal(true);
+ data.data.should.equal('mock data');
+ done();
+ });
+
+ client.messenger.emit('mock_invoke_request', {
+ opaque: 1,
+ method: 'getCallback',
+ args: [ '123' ],
+ pid: 123,
+ });
+ });
+
+ it('should invoke callback with error', done => {
+ mm(client, '_sendTo', (pid, action, data) => {
+ pid.should.equal(123);
+ action.should.equal('mock_invoke_response');
+ data.success.should.equal(false);
+ should.exist(data.errorMessage);
+ data.errorMessage.should.equal('mock error');
+ done();
+ });
+
+ client.messenger.emit('mock_invoke_request', {
+ method: 'getCallback',
+ args: [ 'error' ],
+ pid: 123,
+ });
+ });
+
+ it('should invoke promise well', done => {
+ mm(client, '_sendTo', (pid, action, data) => {
+ pid.should.equal(123);
+ action.should.equal('mock_invoke_response');
+ data.success.should.equal(true);
+ data.data.should.equal('mock data');
+ done();
+ });
+
+ client.messenger.emit('mock_invoke_request', {
+ method: 'getData',
+ args: [ '123' ],
+ pid: 123,
+ });
+ });
+
+ it('should invoke generatorFunction well', done => {
+ mm(client, '_sendTo', (pid, action, data) => {
+ pid.should.equal(123);
+ action.should.equal('mock_invoke_response');
+ data.success.should.equal(true);
+ data.data.should.equal('mock data');
+ done();
+ });
+
+ client.messenger.emit('mock_invoke_request', {
+ method: 'getDataGenerator',
+ args: [ '123' ],
+ pid: 123,
+ });
+ });
+
+ it('should invoke error', done => {
+ mm(client, '_sendTo', (pid, action, data) => {
+ pid.should.equal(123);
+ action.should.equal('mock_invoke_response');
+ data.success.should.equal(false);
+ should.exist(data.errorMessage);
+ data.errorMessage.should.equal('mock error');
+ done();
+ });
+
+ client.messenger.emit('mock_invoke_request', {
+ method: 'getError',
+ args: [ '123' ],
+ pid: 123,
+ });
+ });
+ });
+
+ describe('cluster', () => {
+ let app;
+
+ before(() => {
+ app = utils.cluster('apps/agent-app');
+ return app.ready();
+ });
+
+ after(() => app.close());
+
+ it('should request ok', () => {
+ return request(app.callback())
+ .get('/')
+ .expect(200, 'ok');
+ });
+
+ it('should request getData ok', () => {
+ return request(app.callback())
+ .get('/getData')
+ .expect(200, 'mock data');
+ });
+
+ it('should request getDataGenerator ok', () => {
+ return request(app.callback())
+ .get('/getDataGenerator')
+ .expect(200, 'mock data');
+ });
+
+ it('should request getError', () => {
+ return request(app.callback())
+ .get('/getError')
+ .expect(200, 'mock error');
+ });
+
+ it('should request sub ok', () => {
+ return request(app.callback())
+ .get('/sub')
+ .expect(200, 'bar');
+ });
+ });
+
+ describe('agent sync callback', () => {
+ let app;
+ before(() => {
+ app = utils.app('apps/agent-app-sync');
+ return app.ready();
+ });
+ after(() => app.close());
+
+ it('should call', () => {
+ return request(app.callback())
+ .get('/')
+ .expect(200)
+ .expect('test');
+ });
+ });
+
+ describe('agent restart', () => {
+ let app;
+ before(() => {
+ app = utils.cluster('apps/agent-restart');
+ // app.debug();
+ app.expect('code', 0);
+ return app.ready();
+ });
+
+ before(done => {
+ app.process.send({
+ action: 'die',
+ to: 'agent',
+ });
+ setTimeout(done, 8000);
+ });
+ after(() => app.close());
+
+ it('should resend subscribe', () => {
+ const stdout = app.stdout;
+ stdout.match(/Agent Worker started/g).length.should.eql(2);
+ stdout.match(/agent subscribe aaa/g).length.should.eql(2);
+ });
+ });
+});
diff --git a/test/lib/core/app/extend/agent.test.js b/test/lib/core/app/extend/agent.test.js
new file mode 100644
index 0000000000..2a75b74ee1
--- /dev/null
+++ b/test/lib/core/app/extend/agent.test.js
@@ -0,0 +1,54 @@
+'use strict';
+
+const fs = require('fs');
+const mm = require('egg-mock');
+const sleep = require('co-sleep');
+const utils = require('../../../../utils');
+
+describe('test/lib/core/app/extend/agent.test.js', () => {
+ afterEach(mm.restore);
+
+ describe('agent.addSingleton()', () => {
+ let app;
+ before(done => {
+ app = utils.app('apps/singleton-demo');
+ app.ready(done);
+ });
+ after(() => app.close());
+
+ it('should add singleton success', function* () {
+ let config = yield app.agent.dataService.get('second').getConfig();
+ config.foo.should.equal('bar');
+ config.foo2.should.equal('bar2');
+
+ const ds = yield app.agent.dataService.createInstance({ foo: 'barrr' });
+ config = yield ds.getConfig();
+ config.foo.should.equal('barrr');
+ });
+ });
+
+ describe('agent.instrument()', () => {
+ it.skip('should not log in unittest env', function* () {
+ mm.env('unittest');
+ const app = utils.app('apps/agent-instrument');
+ yield app.ready();
+ yield sleep(1000);
+ // TODO: why egg-agent.log not exists?
+ const log = fs.readFileSync(
+ utils.getFilepath('apps/agent-instrument/logs/agent-instrument/egg-agent.log'), 'utf8');
+ log.should.not.match(/\[http\] \/hello/);
+ app.close();
+ });
+
+ it('should log in local env', function* () {
+ mm.env('local');
+ const app = utils.app('apps/agent-instrument', { cache: false });
+ yield app.ready();
+ yield sleep(1000);
+ const log = fs.readFileSync(
+ utils.getFilepath('apps/agent-instrument/logs/agent-instrument/egg-agent.log'), 'utf8');
+ log.should.match(/\[http\] \/hello/);
+ app.close();
+ });
+ });
+});
diff --git a/test/lib/core/app/extend/application.test.js b/test/lib/core/app/extend/application.test.js
new file mode 100644
index 0000000000..ae7e6ab1e7
--- /dev/null
+++ b/test/lib/core/app/extend/application.test.js
@@ -0,0 +1,113 @@
+'use strict';
+
+const request = require('supertest-as-promised');
+const mm = require('egg-mock');
+const utils = require('../../../../utils');
+
+describe('test/lib/core/app/extend/application.test.js', () => {
+ describe('app.logger', () => {
+ let app;
+ before(() => {
+ app = utils.app('apps/demo');
+ return app.ready();
+ });
+ after(() => app.close());
+
+ it('should alias app.logger => app.loggers.logger', () => {
+ app.logger.should.equal(app.loggers.logger);
+ });
+ });
+
+ describe('app.inspect()', () => {
+ it('should inspect app properties', done => {
+ const app = utils.app('apps/demo');
+ app.ready(() => {
+ app.inspect().should.have.properties([
+ 'name', 'baseDir',
+ 'env', 'subdomainOffset', 'poweredBy',
+ 'controller', 'middlewares', 'serviceClasses',
+ 'config', 'urllib', 'loggers',
+ ]);
+ app.inspect().name.should.equal('demo');
+ app.close();
+ done();
+ });
+ });
+ });
+
+ describe('app.readyCallback()', () => {
+ let app;
+ after(() => app.close());
+
+ it('should log info when plugin is not ready', done => {
+ app = utils.app('apps/notready');
+ mm(app.console, 'warn', (message, a) => {
+ message.should.eql('[egg:core:ready_timeout] 10 seconds later %s was still unable to finish.');
+ a.should.eql('a');
+ done();
+ });
+ app.ready(() => {
+ throw new Error('should not be called');
+ });
+ });
+ });
+
+ describe('app.locals', () => {
+ let app;
+ before(() => {
+ app = utils.app('apps/locals');
+ return app.ready();
+ });
+ after(() => app.close());
+
+ it('should app.locals is same ref', () => {
+ return request(app.callback())
+ .get('/app_same_ref')
+ .expect('true');
+ });
+ });
+
+ describe('app.createAnonymousContext()', () => {
+ let app;
+ before(() => {
+ app = utils.app('apps/demo');
+ return app.ready();
+ });
+ after(() => app.close());
+
+ it('should get anonymous context object', function* () {
+ const ctx = app.createAnonymousContext({
+ socket: {
+ remoteAddress: '10.0.0.1',
+ },
+ headers: {
+ 'x-forwarded-for': '10.0.0.1',
+ },
+ url: '/foobar?ok=1',
+ });
+ ctx.ip.should.equal('10.0.0.1');
+ ctx.url.should.equal('/foobar?ok=1');
+ ctx.socket.remoteAddress.should.equal('10.0.0.1');
+ ctx.socket.remotePort.should.equal(7001);
+ });
+ });
+
+ describe('app.addSingleton()', () => {
+ let app;
+ before(() => {
+ app = utils.app('apps/singleton-demo');
+ return app.ready();
+ });
+ after(() => app.close());
+
+ it('should add singleton success', function* () {
+ let config = yield app.dataService.get('first').getConfig();
+ config.foo.should.equal('bar');
+ config.foo1.should.equal('bar1');
+
+ const ds = yield app.dataService.createInstance({ foo: 'barrr' });
+ config = yield ds.getConfig();
+ config.foo.should.equal('barrr');
+ });
+ });
+});
diff --git a/test/lib/core/app/extend/context.jsonp.test.js b/test/lib/core/app/extend/context.jsonp.test.js
new file mode 100644
index 0000000000..d543fd78ff
--- /dev/null
+++ b/test/lib/core/app/extend/context.jsonp.test.js
@@ -0,0 +1,43 @@
+'use strict';
+
+const mm = require('egg-mock');
+const request = require('supertest-as-promised');
+const utils = require('../../../../utils');
+
+describe('test/lib/core/app/extend/context.jsonp.test.js', () => {
+ let app;
+ before(() => {
+ app = utils.app('apps/demo');
+ return app.ready();
+ });
+ after(() => app.close());
+ afterEach(mm.restore);
+
+ it('should response jsonp', () => {
+ return request(app.callback())
+ .get('/user.json?_callback=$jQuery110208780175377614796_1406016639408&ctoken=123')
+ .set('Cookie', 'ctoken=123')
+ .expect('Content-Type', 'application/javascript; charset=utf-8')
+ .expect('X-Content-Type-Options', 'nosniff')
+ .expect('/**/ typeof $jQuery110208780175377614796_1406016639408 === \'function\' && $jQuery110208780175377614796_1406016639408({"name":"fengmk2"});')
+ .expect(200);
+ });
+
+ it('should response json body when callback empty', () => {
+ return request(app.callback())
+ .get('/user.json?_callback=&ctoken=123')
+ .set('Cookie', 'ctoken=123')
+ .expect('Content-Type', 'application/json; charset=utf-8')
+ .expect('{"name":"fengmk2"}')
+ .expect(200);
+ });
+
+ it('should response json body when callback missing', () => {
+ return request(app.callback())
+ .get('/user.json?callback=&ctoken=123')
+ .set('Cookie', 'ctoken=123')
+ .expect('Content-Type', 'application/json; charset=utf-8')
+ .expect('{"name":"fengmk2"}')
+ .expect(200);
+ });
+});
diff --git a/test/lib/core/app/extend/context.test.js b/test/lib/core/app/extend/context.test.js
new file mode 100644
index 0000000000..8fb6b4fdd6
--- /dev/null
+++ b/test/lib/core/app/extend/context.test.js
@@ -0,0 +1,308 @@
+'use strict';
+
+const should = require('should');
+const fs = require('fs');
+const path = require('path');
+const mm = require('egg-mock');
+const request = require('supertest-as-promised');
+const rimraf = require('rimraf');
+const utils = require('../../../../utils');
+
+describe('test/lib/core/app/extend/context.test.js', () => {
+
+ afterEach(mm.restore);
+
+ describe('ctx.logger', () => {
+ const baseDir = utils.getFilepath('apps/demo');
+
+ beforeEach(() => {
+ rimraf.sync(path.join(baseDir, 'logs'));
+ });
+
+ let app;
+ afterEach(() => app.close());
+
+ it('env=local: level => debug', done => {
+ mm.env('local');
+ mm(process.env, 'EGG_LOG', 'none');
+ app = utils.app('apps/demo');
+ const logdir = app.config.logger.dir;
+
+ request(app.callback())
+ .get('/logger?message=foo')
+ .expect('logger', err => {
+ should.not.exists(err);
+
+ const errorContent = fs.readFileSync(path.join(logdir, 'common-error.log'), 'utf8');
+ errorContent.should.containEql('nodejs.Error: error foo');
+ errorContent.should.containEql('nodejs.Error: core error foo');
+
+ const loggerContent = fs.readFileSync(path.join(logdir, 'demo-web.log'), 'utf8');
+ loggerContent.should.containEql('debug foo');
+ loggerContent.should.containEql('info foo');
+ loggerContent.should.containEql('warn foo');
+
+ const coreLoggerContent = fs.readFileSync(path.join(logdir, 'egg-web.log'), 'utf8');
+ coreLoggerContent.should.containEql('core debug foo');
+ coreLoggerContent.should.containEql('core info foo');
+ coreLoggerContent.should.containEql('core warn foo');
+ done();
+ });
+ });
+
+ it('env=unittest: level => info', done => {
+ mm.env('unittest');
+ app = utils.app('apps/demo');
+ app.ready(() => {
+ const logdir = app.config.logger.dir;
+
+ app.mockContext({
+ userId: '123123',
+ tracer: {
+ traceId: '456456',
+ },
+ });
+
+ request(app.callback())
+ .get('/logger?message=foo')
+ .expect('logger', err => {
+ should.not.exists(err);
+
+ const errorContent = fs.readFileSync(path.join(logdir, 'common-error.log'), 'utf8');
+ errorContent.should.containEql('nodejs.Error: error foo');
+ errorContent.should.containEql('nodejs.Error: core error foo');
+ errorContent.should.match(/\[123123\/[\d\.]+\/456456\/\d+ms GET \/logger\?message=foo]/);
+
+ const loggerContent = fs.readFileSync(path.join(logdir, 'demo-web.log'), 'utf8');
+ loggerContent.should.not.containEql('debug foo');
+ loggerContent.should.containEql('info foo');
+ loggerContent.should.containEql('warn foo');
+
+ const coreLoggerContent = fs.readFileSync(path.join(logdir, 'egg-web.log'), 'utf8');
+ coreLoggerContent.should.not.containEql('core debug foo');
+ coreLoggerContent.should.containEql('core info foo');
+ coreLoggerContent.should.containEql('core warn foo');
+
+ done();
+ });
+ });
+ });
+
+ it('env=prod: level => info', done => {
+ mm.env('unittest');
+ app = utils.app('apps/demo');
+ const logdir = app.config.logger.dir;
+
+ request(app.callback())
+ .get('/logger?message=foo')
+ .expect('logger', err => {
+ should.not.exists(err);
+
+ const errorContent = fs.readFileSync(path.join(logdir, 'common-error.log'), 'utf8');
+ errorContent.should.containEql('nodejs.Error: error foo');
+ errorContent.should.containEql('nodejs.Error: core error foo');
+
+ const loggerContent = fs.readFileSync(path.join(logdir, 'demo-web.log'), 'utf8');
+ loggerContent.should.not.containEql('debug foo');
+ loggerContent.should.containEql('info foo');
+ loggerContent.should.containEql('warn foo');
+
+ const coreLoggerContent = fs.readFileSync(path.join(logdir, 'egg-web.log'), 'utf8');
+ coreLoggerContent.should.not.containEql('core debug foo');
+ coreLoggerContent.should.containEql('core info foo');
+ coreLoggerContent.should.containEql('core warn foo');
+
+ done();
+ });
+ });
+ });
+
+ describe('properties', () => {
+ let app;
+ before(() => {
+ app = utils.app('apps/context-config-app');
+ return app.ready();
+ });
+ after(() => app.close());
+
+ describe('ctx.router', () => {
+ it('should work', () => {
+ return request(app.callback())
+ .get('/')
+ .expect(200)
+ .expect('{"path":"/","foo":1,"bar":2}');
+ });
+ });
+
+ describe('ctx.runtime', () => {
+ it('should work', () => {
+ return request(app.callback())
+ .get('/runtime')
+ .expect(200)
+ .expect('{"mysql":10,"foo":11}');
+ });
+ });
+ });
+
+ describe('ctx.instrument(event, action), app.instrument(event, action)', () => {
+ let app;
+ before(() => {
+ mm.env('local');
+ app = utils.app('apps/context-config-app');
+ return app.ready();
+ });
+ after(() => app.close());
+
+ it('should instrument whatever you want', done => {
+ const ctx = app.mockContext();
+ mm(ctx.logger, 'info', msg => {
+ msg.should.match(/\[foo\] test action on ctx \d+ms/);
+ done();
+ });
+ const ins = ctx.instrument('foo', 'test action on ctx');
+ ins.end();
+ });
+
+ it('should app.instrument work', done => {
+ mm(app.logger, 'info', msg => {
+ msg.should.match(/\[foo\] test action on app \d+ms/);
+ done();
+ });
+ const ins = app.instrument('foo', 'test action on app');
+ ins.end();
+ });
+ });
+
+ describe('ctx.view', () => {
+ let app;
+ before(() => {
+ app = utils.cluster({
+ baseDir: 'apps/view',
+ customEgg: utils.getFilepath('apps/view-framework'),
+ });
+ return app.ready();
+ });
+ after(() => app.close());
+
+ it('should render template', () => {
+ return request(app.callback())
+ .get('/')
+ .expect(200)
+ .expect('name=index.html, a=111, b=b, c=testHelper');
+ });
+
+ it('should render string', () => {
+ return request(app.callback())
+ .get('/string')
+ .expect(200)
+ .expect('tpl={{a}}, a=111, b=b, c=testHelper');
+ });
+ });
+
+ describe('ctx.locals', () => {
+ let app;
+ before(() => {
+ app = utils.app('apps/locals');
+ return app.ready();
+ });
+ after(() => app.close());
+
+ it('should same this.locals ref on every request ', () => {
+ return request(app.callback())
+ .get('/ctx_same_ref')
+ .expect('true');
+ });
+
+ it('should this.locals merge app.locals data', () => {
+ return request(app.callback())
+ .get('/ctx_merge_app')
+ .expect({
+ a: 1,
+ b: 1,
+ });
+ });
+
+ it('should this.locals cover app.locals data', () => {
+ return request(app.callback())
+ .get('/ctx_override_app')
+ .expect({
+ a: 'ctx.a',
+ b: 'ctx.b',
+ });
+ });
+
+ it('should not change this.locals data when app.locals change again', () => {
+ return request(app.callback())
+ .get('/ctx_app_update_can_not_affect_ctx')
+ .expect({
+ a: 'app.a',
+ b: 'app.b',
+ newPropertyExists: false,
+ });
+ });
+
+ it('should locals only support object format', () => {
+ return request(app.callback())
+ .get('/set_only_support_object')
+ .expect({
+ 'ctx.locals.object': true,
+ 'app.locals.object': true,
+ 'app.locals.string': false,
+ 'app.locals.number': false,
+ 'app.locals.function': false,
+ 'app.locals.array': false,
+ 'ctx.locals.string': false,
+ 'ctx.locals.number': false,
+ 'ctx.locals.function': false,
+ 'ctx.locals.array': false,
+ });
+ });
+ });
+
+ describe('ctx.roleFailureHandler()', () => {
+ it('should detect ajax', function* () {
+ const context = yield utils.createContext({ isAjax: true });
+ context.roleFailureHandler('admin');
+ context.body.should.eql({ message: 'Forbidden, required role: admin', stat: 'deny' });
+ });
+
+ it('should response message when is not ajax', function* () {
+ const context = yield utils.createContext();
+ context.roleFailureHandler('admin');
+ context.body.should.equal('Forbidden, required role: admin');
+ });
+ });
+
+ describe('ctx.curl()', () => {
+ it('should curl ok', function* () {
+ const context = yield utils.createContext();
+ const res = yield context.curl('https://a.alipayobjects.com/aliBridge/1.0.0/aliBridge.min.js');
+ res.status.should.equal(200);
+ });
+ });
+
+ describe('ctx.realStatus', () => {
+ it('should get from status ok', function* () {
+ const context = yield utils.createContext();
+ context.status = 200;
+ context.realStatus.should.equal(200);
+ });
+
+ it('should get from realStatus ok', () => {
+ const context = utils.createContext();
+ context.status = 302;
+ context.realStatus = 500;
+ context.realStatus.should.equal(500);
+ });
+ });
+
+ describe('ctx.state', () => {
+ it('should delegate ctx.locals', function* () {
+ const context = yield utils.createContext();
+ context.locals = { a: 'a', b: 'b' };
+ context.state = { a: 'aa', c: 'cc' };
+ context.state.should.eql({ a: 'aa', b: 'b', c: 'cc' });
+ context.state.should.equal(context.locals);
+ });
+ });
+});
diff --git a/test/lib/core/app/extend/helper.test.js b/test/lib/core/app/extend/helper.test.js
new file mode 100644
index 0000000000..bea7ad0b4b
--- /dev/null
+++ b/test/lib/core/app/extend/helper.test.js
@@ -0,0 +1,70 @@
+'use strict';
+
+const request = require('supertest-as-promised');
+const utils = require('../../../../utils');
+
+describe('test/lib/core/app/extend/helper.test.js', () => {
+ let app;
+ before(() => {
+ app = utils.app('apps/helper');
+ return app.ready();
+ });
+
+ describe('pathFor()', () => {
+ it('should get home path url', () => {
+ return request(app.callback())
+ .get('/pathFor')
+ .expect('/home')
+ .expect(200);
+ });
+
+ it('should get home path with params', () => {
+ return request(app.callback())
+ .get('/pathFor?foo=bar')
+ .expect('/home?foo=bar')
+ .expect(200);
+ });
+ });
+
+ describe('urlFor()', () => {
+ it('should get full home url', () => {
+ return request(app.callback())
+ .get('/urlFor')
+ .expect(/^http:\/\/127\.0\.0\.1:\d+\/home$/)
+ .expect(200);
+ });
+
+ it('should get full home url with params', () => {
+ return request(app.callback())
+ .get('/urlFor?foo=1')
+ .expect(/^http:\/\/127\.0\.0\.1:\d+\/home\?foo=1$/)
+ .expect(200);
+ });
+
+ });
+
+ describe.skip('escape()', () => {
+ it('should escape script', () => {
+ return request(app.callback())
+ .get('/escape')
+ .expect('<script>')
+ .expect(200);
+ });
+ });
+
+ describe.skip('shtml()', () => {
+ it('should ignore attribute if domain not in domainWhiteList', () => {
+ return request(app.callback())
+ .get('/shtml-not-in-domain-whitelist')
+ .expect('true')
+ .expect(200);
+ });
+
+ it('should keep attribute if domain in default domainWhiteList', () => {
+ return request(app.callback())
+ .get('/shtml-in-default-domain-whitelist')
+ .expect('true')
+ .expect(200);
+ });
+ });
+});
diff --git a/test/lib/core/app/extend/request.test.js b/test/lib/core/app/extend/request.test.js
new file mode 100644
index 0000000000..6cb50e2119
--- /dev/null
+++ b/test/lib/core/app/extend/request.test.js
@@ -0,0 +1,334 @@
+'use strict';
+
+const mm = require('egg-mock');
+const should = require('should');
+const merge = require('merge-descriptors');
+const urllib = require('urllib');
+const request = require('supertest');
+const utils = require('../../../../utils');
+const requestExt = require('../../../../../lib/core/app/extend/request');
+
+describe('test/lib/core/app/extend/request.test.js', () => {
+ afterEach(mm.restore);
+
+ describe('req.host', () => {
+ it('should return host with port', function* () {
+ const req = yield utils.createRequest();
+ req.header.host = 'foo.com:3000';
+ req.hostname.should.equal('foo.com');
+ });
+
+ it('should return "localhost" when no host present', function* () {
+ const req = yield utils.createRequest();
+ req.host.should.be.a.String;
+ req.host.should.equal('localhost');
+ });
+
+ it('should return host from X-Forwarded-Host header', function* () {
+ const req = yield utils.createRequest();
+ req.header['x-forwarded-host'] = 'foo.com';
+ req.host.should.be.a.String;
+ req.host.should.equal('foo.com');
+ });
+ });
+
+ describe('req.hostname', () => {
+ it('should return hostname with port', function* () {
+ const req = yield utils.createRequest();
+ req.header.host = 'foo.com:3000';
+ req.hostname.should.equal('foo.com');
+ });
+
+ it('should return "localhost" when no host present', function* () {
+ const req = yield utils.createRequest();
+ req.hostname.should.be.a.String;
+ req.hostname.should.equal('localhost');
+ });
+ });
+
+ describe('req.ip', () => {
+ it('should return ipv4', function* () {
+ const req = yield utils.createRequest();
+ req.socket.remoteAddress = '::ffff:127.0.0.1';
+ req.ip.should.equal('127.0.0.1');
+ req.ip.should.equal('127.0.0.1');
+ });
+ });
+
+ describe('req.ips', () => {
+ it('should used x-forwarded-for', function* () {
+ const req = yield utils.createRequest();
+ req.header['x-forwarded-for'] = '127.0.0.1,127.0.0.2';
+ req.ips.should.eql([ '127.0.0.1', '127.0.0.2' ]);
+ });
+
+ it('should used x-real-ip', function* () {
+ const req = yield utils.createRequest();
+ req.header['x-forwarded-for'] = '';
+ req.header['x-real-ip'] = '127.0.0.1,127.0.0.2';
+ req.ips.should.eql([ '127.0.0.1', '127.0.0.2' ]);
+ });
+
+ it('should return []', function* () {
+ const req = yield utils.createRequest();
+ req.header['x-forwarded-for'] = '';
+ req.header['x-real-ip'] = '';
+ req.ips.should.eql([]);
+ });
+ });
+
+ describe('req.protocol', () => {
+ let app;
+ beforeEach(() => {
+ app = utils.app('apps/demo');
+ return app.ready();
+ });
+ afterEach(() => app.close());
+
+ it('should return http when it not config and no protocol header', () => {
+ mm(app.config, 'protocl', null);
+ return request(app.callback())
+ .get('/protocol')
+ .expect('http');
+ });
+
+ it('should return value of X-Custom-Proto', () => {
+ mm(app.config, 'protocolHeaders', 'X-Custom-Proto');
+ return request(app.callback())
+ .get('/protocol')
+ .set('X-Custom-Proto', 'https')
+ .expect('https');
+ });
+
+ it('should ignore X-Client-Scheme', () => {
+ mm(app.config, 'protocolHeaders', 'X-Forwarded-Proto');
+ return request(app.callback())
+ .get('/protocol')
+ .set('X-Client-Scheme', 'https')
+ .expect('http');
+ });
+
+ it('should return value of X-Forwarded-Proto', () => {
+ return request(app.callback())
+ .get('/protocol')
+ .set('x-forwarded-proto', 'https')
+ .expect('https');
+ });
+
+ it('should ignore X-Forwarded-Proto', () => {
+ mm(app.config, 'protocolHeaders', '');
+ return request(app.callback())
+ .get('/protocol')
+ .set('x-forwarded-proto', 'https')
+ .expect('http');
+ });
+
+ it('should return value from config', () => {
+ mm(app.config, 'protocol', 'https');
+ return request(app.callback())
+ .get('/protocol')
+ .expect('https');
+ });
+ });
+
+ describe('this.query[key] => String', () => {
+ it('should get string value', () => {
+ createRequest('a=b').query.should.eql({ a: 'b' });
+ createRequest('a=&').query.should.eql({ a: '' });
+ createRequest('a=b&').query.should.eql({ a: 'b' });
+ createRequest('a.=b').query.should.eql({ 'a.': 'b' });
+ createRequest('a=b&a=c').query.should.eql({ a: 'b' });
+ createRequest('a=&a=c').query.should.eql({ a: '' });
+ createRequest('a=c&a=b').query.should.eql({ a: 'c' });
+ createRequest('a=c&a=b&b=bb').query.should.eql({ a: 'c', b: 'bb' });
+ createRequest('a[=c&a[=b').query.should.eql({ 'a[': 'c' });
+ createRequest('a{=c&a{=b').query.should.eql({ 'a{': 'c' });
+ createRequest('a[]=c&a[]=b').query.should.eql({ 'a[]': 'c' });
+ createRequest('a[]=&a[]=b').query.should.eql({ 'a[]': '' });
+ createRequest('a[foo]=c').query.should.eql({ 'a[foo]': 'c' });
+ createRequest('a[foo][bar]=c').query.should.eql({ 'a[foo][bar]': 'c' });
+ createRequest('a=').query.should.eql({ a: '' });
+ createRequest('a[]=a&a=b&a=c').query.should.eql({ 'a[]': 'a', a: 'b' });
+ });
+
+ it('should get undefined when key not exists', () => {
+ const request = createRequest('a=b');
+ request.query.should.eql({ a: 'b' });
+ should.not.exist(request.query.foo);
+ });
+ });
+
+ describe('this.queries[key] => Array', () => {
+ it('should get array value', () => {
+ createRequest('').queries.should.eql({ });
+ createRequest('a=').queries.should.eql({ a: [ '' ] });
+ createRequest('a=&').queries.should.eql({ a: [ '' ] });
+ createRequest('a=b&').queries.should.eql({ a: [ 'b' ] });
+ createRequest('a.=').queries.should.eql({ 'a.': [ '' ] });
+ createRequest('a=&a=&a=&a=&a=&a=&a=&a=').queries.should.eql({ a: [ '', '', '', '', '', '', '', '' ] });
+ createRequest('a=&a=&a=&a=&a=&a=&a=&a=&').queries.should.eql({ a: [ '', '', '', '', '', '', '', '' ] });
+ createRequest('a=&a=&a=&a=&a=&a=&a=&a=&&&&').queries.should.eql({ a: [ '', '', '', '', '', '', '', '' ] });
+ createRequest('a=b').queries.should.eql({ a: [ 'b' ] });
+ createRequest('a={}').queries.should.eql({ a: [ '{}' ] });
+ createRequest('a=[]').queries.should.eql({ a: [ '[]' ] });
+ createRequest('a[]=[]').queries.should.eql({ 'a[]': [ '[]' ], a: [ '[]' ] });
+ createRequest('a[]=&a[]=').queries.should.eql({ 'a[]': [ '', '' ], a: [ '', '' ] });
+ createRequest('a[]=[]&a[]=[]').queries.should.eql({ 'a[]': [ '[]', '[]' ], a: [ '[]', '[]' ] });
+ createRequest('a=b&a=c').queries.should.eql({ a: [ 'b', 'c' ] });
+ createRequest('a=&a=c').queries.should.eql({ a: [ '', 'c' ] });
+ createRequest('a=c&a=b').queries.should.eql({ a: [ 'c', 'b' ] });
+ createRequest('a=c&a=b&b=bb').queries.should.eql({ a: [ 'c', 'b' ], b: [ 'bb' ] });
+ createRequest('a[=c&a[=b').queries.should.eql({ 'a[': [ 'c', 'b' ] });
+ createRequest('a{=c&a{=b').queries.should.eql({ 'a{': [ 'c', 'b' ] });
+ createRequest('a[]=c&a[]=b').queries.should.eql({ 'a[]': [ 'c', 'b' ], a: [ 'c', 'b' ] });
+ createRequest('a[]=&a[]=b').queries.should.eql({ 'a[]': [ '', 'b' ], a: [ '', 'b' ] });
+ createRequest('a[]=&a[]=b&a=foo').queries.should.eql({ 'a[]': [ '', 'b' ], a: [ 'foo' ] });
+ createRequest('a=bar&a[]=&a[]=b&a=foo').queries.should.eql({ 'a[]': [ '', 'b' ], a: [ 'bar', 'foo' ] });
+
+ // a[][] 这种不支持自动变化为 a
+ createRequest('a[][]=&a[][]=b').queries.should.eql({ 'a[][]': [ '', 'b' ] });
+ createRequest('a][]=&a][]=b').queries.should.eql({ 'a][]': [ '', 'b' ] });
+ createRequest('a[[]=&a[[]=b').queries.should.eql({ 'a[[]': [ '', 'b' ] });
+ createRequest('[]=&[]=b').queries.should.eql({ '[]': [ '', 'b' ] });
+
+ // a[], a 混搭的时候,只返回最后一个 a 的值
+ createRequest('a[]=a&a=b&a=c').queries.should.eql({ 'a[]': [ 'a' ], a: [ 'b', 'c' ] });
+
+ // object
+ createRequest('a[foo]=c').queries.should.eql({ 'a[foo]': [ 'c' ] });
+ createRequest('a[foo]=c&a=b').queries.should.eql({ 'a[foo]': [ 'c' ], a: [ 'b' ] });
+ createRequest('a[foo]=c&a=b&b=bb&d=d1&d=d2').queries.should.eql({
+ 'a[foo]': [ 'c' ],
+ a: [ 'b' ],
+ b: [ 'bb' ],
+ d: [ 'd1', 'd2' ],
+ });
+ createRequest('a[foo]=c&a[]=b&a[]=d').queries.should.eql({
+ 'a[foo]': [ 'c' ],
+ 'a[]': [ 'b', 'd' ],
+ a: [ 'b', 'd' ],
+ });
+ createRequest('a[foo]=c&a[]=b&a[]=d&c=cc&c=c2&c=').queries.should.eql({
+ 'a[foo]': [ 'c' ],
+ 'a[]': [ 'b', 'd' ],
+ a: [ 'b', 'd' ],
+ c: [ 'cc', 'c2', '' ],
+ });
+ createRequest('a[foo][bar]=c').queries.should.eql({
+ 'a[foo][bar]': [ 'c' ],
+ });
+ });
+
+ it('should get undefined when key not exists', () => {
+ const request = createRequest('a=b');
+ request.queries.should.eql({ a: [ 'b' ] });
+ should.not.exist(request.queries.foo);
+ });
+ });
+
+ describe('this.query = obj', () => {
+ it('should set query with object', () => {
+ const req = createRequest('a=c');
+ req.query.should.eql({ a: 'c' });
+ req.query = {};
+ req.query.should.eql({});
+ req.querystring.should.equal('');
+
+ req.query = { foo: 'bar' };
+ req.query.should.eql({ foo: 'bar' });
+ req.querystring.should.equal('foo=bar');
+
+ req.query = { array: [ 1, 2 ] };
+ req.query.should.eql({ array: '1' });
+ req.querystring.should.equal('array=1&array=2');
+ });
+ });
+
+ describe('request.acceptJSON', () => {
+ it('should true when isAjax', function* () {
+ const req = yield utils.createRequest();
+ mm(req.req.headers, 'x-requested-with', 'XMLHttpRequest');
+ req.acceptJSON.should.equal(true);
+ });
+
+ it('should true when response is json', function* () {
+ const context = yield utils.createContext({
+ headers: {
+ accept: 'text/html',
+ },
+ url: '/',
+ });
+ context.res._headers = {
+ 'content-type': 'json',
+ };
+ context.request.acceptJSON.should.equal(true);
+ });
+
+ it('should true when accept json', function* () {
+ const context = yield utils.createContext({
+ headers: {
+ accept: 'application/json',
+ },
+ url: '/',
+ });
+ context.request.acceptJSON.should.equal(true);
+ });
+
+ it('should false when do not accept json', function* () {
+ const request = yield utils.createRequest({
+ headers: {
+ accept: 'text/html',
+ },
+ url: '/',
+ });
+ request.acceptJSON.should.equal(false);
+ });
+ });
+
+ describe('work with egg app', () => {
+ let app;
+ let host;
+ before(done => {
+ app = utils.app('apps/querystring-extended');
+ app.listen(0, function() {
+ host = `http://127.0.0.1:${this.address().port}`;
+ done();
+ });
+ });
+
+ it('should return query and queries', done => {
+ urllib.request(`${host}/?p=a,b&p=b,c&a[foo]=bar`, {
+ dataType: 'json',
+ }, (err, body) => {
+ body.should.eql({
+ query: { p: 'a,b', 'a[foo]': 'bar' },
+ queries: { p: [ 'a,b', 'b,c' ], 'a[foo]': [ 'bar' ] },
+ });
+ done(err);
+ });
+ });
+
+ it('should work with encodeURIComponent', done => {
+ urllib.request(`${host}/?p=a,b&p=b,c&${encodeURIComponent('a[foo]')}=bar`, {
+ dataType: 'json',
+ }, (err, body) => {
+ body.should.eql({
+ query: { p: 'a,b', 'a[foo]': 'bar' },
+ queries: { p: [ 'a,b', 'b,c' ], 'a[foo]': [ 'bar' ] },
+ });
+ done(err);
+ });
+ });
+ });
+
+ function createRequest(querystring) {
+ const app = {
+ context: {},
+ request: {
+ querystring,
+ },
+ };
+ merge(app.request, requestExt);
+ return app.request;
+ }
+});
diff --git a/test/lib/core/app/middleware/body_parser.test.js b/test/lib/core/app/middleware/body_parser.test.js
new file mode 100644
index 0000000000..eb955702c3
--- /dev/null
+++ b/test/lib/core/app/middleware/body_parser.test.js
@@ -0,0 +1,57 @@
+'use strict';
+
+const querystring = require('querystring');
+const should = require('should');
+const request = require('supertest');
+const utils = require('../../../../utils');
+
+describe('test/lib/core/app/middleware/body_parser.test.js', () => {
+ let app;
+ before(done => {
+ app = utils.app('apps/body_parser_testapp');
+ const that = this;
+
+ app.ready(() => {
+ request(app.callback())
+ .get('/test/body_parser/user')
+ .expect(200, (err, res) => {
+ should.not.exist(err);
+ that.csrf = res.body.csrf || '';
+ // that.cookies = res.headers['set-cookie'].join(';');
+ // res.headers['set-cookie'].forEach(function(cookie) {
+ // const item = cookie.split(';')[0].trim().split('=');
+ // if (item[0] === 'ctoken') {
+ // that.ctoken = item[1];
+ // }
+ // });
+ // should.exist(that.csrf);
+ done();
+ });
+ });
+ });
+
+ after(() => {
+ app.close();
+ });
+
+ it('should 200 when post form body below the limit', done => {
+ request(app.callback())
+ .post('/test/body_parser/user')
+ // .set('Cookie', this.cookies)
+ .set('Content-Type', 'application/x-www-form-urlencoded')
+ .set('Accept', 'application/json')
+ .send(querystring.stringify({ foo: 'bar', _csrf: this.csrf }))
+ .expect({ foo: 'bar', _csrf: this.csrf })
+ .expect(200, done);
+ });
+
+ it('should 200 when post json body below the limit', done => {
+ request(app.callback())
+ .post('/test/body_parser/user')
+ // .set('Cookie', this.cookies)
+ .set('Content-Type', 'application/json')
+ .send({ foo: 'bar', _csrf: this.csrf })
+ .expect({ foo: 'bar', _csrf: this.csrf })
+ .expect(200, done);
+ });
+});
diff --git a/test/lib/core/app/middleware/meta.test.js b/test/lib/core/app/middleware/meta.test.js
new file mode 100644
index 0000000000..4a40e56f5e
--- /dev/null
+++ b/test/lib/core/app/middleware/meta.test.js
@@ -0,0 +1,65 @@
+'use strict';
+
+const should = require('should');
+const mm = require('egg-mock');
+const request = require('supertest-as-promised');
+const utils = require('../../../../utils');
+
+describe('test/lib/core/app/middleware/meta.test.js', () => {
+ let app;
+ before(() => {
+ mm(process.env, 'HOSTNAME', 'appname-1-1');
+ app = utils.app('apps/middlewares');
+ return app.ready();
+ });
+
+ after(() => app.close());
+
+ afterEach(mm.restore);
+
+ it('should get X-Powered-By header', () => {
+ return request(app.callback())
+ .get('/')
+ .expect('X-Powered-By', 'Egg')
+ .expect(200);
+ });
+
+ it('should hide X-Powered-By header', () => {
+ mm(app, 'poweredBy', false);
+ return request(app.callback())
+ .get('/')
+ .expect(res => {
+ should.not.exist(res.headers['X-Powered-By']);
+ })
+ .expect(200);
+ });
+
+ it('should get X-Server-Id header', () => {
+ return request(app.callback())
+ .get('/')
+ .expect('X-Server-Id', '1-1')
+ .expect(200);
+ });
+
+ it('should hide X-Server-Id header', done => {
+ mm(process.env, 'HOSTNAME', '');
+ const app = utils.app('apps/middlewares');
+ request(app.callback())
+ .get('/')
+ .expect(res => {
+ should.not.exist(res.headers['X-Server-Id']);
+ })
+ .expect(200, err => {
+ app.close();
+ should.not.exist(err);
+ done();
+ });
+ });
+
+ it('should get X-Readtime header', () => {
+ return request(app.callback())
+ .get('/')
+ .expect('X-Readtime', /\d+/)
+ .expect(200);
+ });
+});
diff --git a/test/lib/core/app/middleware/notfound.test.js b/test/lib/core/app/middleware/notfound.test.js
new file mode 100644
index 0000000000..2851e42dc9
--- /dev/null
+++ b/test/lib/core/app/middleware/notfound.test.js
@@ -0,0 +1,88 @@
+'use strict';
+
+const pedding = require('pedding');
+const request = require('supertest');
+const mm = require('egg-mock');
+const utils = require('../../../../utils');
+
+describe('test/lib/core/app/middleware/notfound.test.js', () => {
+ let app;
+ before(() => {
+ app = utils.app('apps/notfound');
+ return app.ready();
+ });
+ after(() => app.close());
+
+ afterEach(mm.restore);
+
+ it('should 302 redirect to 404.html on production env', () => {
+ mm(app.config.notfound, 'enableRedirect', true);
+ return request(app.callback())
+ .get('/test/404')
+ .set('Accept', 'test/html')
+ .expect('Location', 'https://eggjs.org/404')
+ .expect(302);
+ });
+
+ it('should show 404 on dev env', () => {
+ return request(app.callback())
+ .get('/test/404')
+ .expect('404 Not Found
Because you are in a non-prod environment, you will be looking at this page, otherwise it will jump to https://eggjs.org/404')
+ .expect(404);
+ });
+
+ it('should 404 json response', () => {
+ return request(app.callback())
+ .get('/test/404.json?ctoken=404')
+ .set('Cookie', 'ctoken=404')
+ .expect({
+ message: 'Not Found',
+ })
+ .expect(404);
+ });
+
+ it('should 404 json response on rest api', () => {
+ return request(app.callback())
+ .get('/api/404.json?ctoken=404')
+ .set('Cookie', 'ctoken=404')
+ .expect({
+ message: 'Not Found',
+ })
+ .expect(404);
+ });
+
+ it('should show 404 page content when antx notfound.pageUrl not set', () => {
+ mm(app.config.notfound, 'pageUrl', '');
+ return request(app.callback())
+ .get('/foo')
+ .expect('404 Not Found
')
+ .expect(404);
+ });
+
+ describe('app.404.url=/404', () => {
+ let app;
+ before(() => {
+ app = utils.app('apps/notfound-custom-404');
+ return app.ready();
+ });
+ after(() => app.close());
+
+ afterEach(mm.restore);
+
+ it('should 302 redirect to custom /404 on production env', done => {
+ done = pedding(2, done);
+ mm(app.config.notfound, 'enableRedirect', true);
+
+ request(app.callback())
+ .get('/test/404')
+ .set('Accept', 'test/html')
+ .expect('Location', '/404')
+ .expect(302, done);
+
+ request(app.callback())
+ .get('/404')
+ .expect('Hi, this is 404')
+ .expect(200, done);
+ });
+ });
+});
diff --git a/test/lib/core/app/middleware/override_method.test.js b/test/lib/core/app/middleware/override_method.test.js
new file mode 100644
index 0000000000..304520253d
--- /dev/null
+++ b/test/lib/core/app/middleware/override_method.test.js
@@ -0,0 +1,37 @@
+'use strict';
+
+const request = require('supertest-as-promised');
+const utils = require('../../../../utils');
+
+describe('test/lib/core/app/middleware/override_method.test.js', () => {
+ let app;
+ before(() => {
+ app = utils.app('apps/override_method');
+ return app.ready();
+ });
+ after(() => app.close());
+
+ it('should put', () => {
+ return request(app.callback())
+ .post('/test')
+ .send({ _method: 'PUT' })
+ .expect(200)
+ .expect('test-put');
+ });
+
+ it('should patch', () => {
+ return request(app.callback())
+ .post('/test')
+ .send({ _method: 'PATCH' })
+ .expect(200)
+ .expect('test-patch');
+ });
+
+ it('should delete', () => {
+ return request(app.callback())
+ .post('/test')
+ .send({ _method: 'DELETE' })
+ .expect(200)
+ .expect('test-delete');
+ });
+});
diff --git a/test/lib/core/app/middleware/security.test.js b/test/lib/core/app/middleware/security.test.js
new file mode 100644
index 0000000000..408a106b0d
--- /dev/null
+++ b/test/lib/core/app/middleware/security.test.js
@@ -0,0 +1,105 @@
+'use strict';
+
+const request = require('supertest-as-promised');
+const utils = require('../../../../utils');
+
+describe.skip('test/lib/core/app/middleware/security.test.js', () => {
+ describe('security.ctoken = false', function() {
+ it('should not check ctoken', function(done) {
+ const app = utils.app('apps/ctoken-disable');
+ request(app.listen())
+ .get('/api/user.json?name=suqian.yf')
+ .expect(200)
+ .expect({
+ url: '/api/user.json?name=suqian.yf',
+ name: 'suqian.yf',
+ }, done);
+ });
+ });
+
+ describe('security.ctoken = true', function() {
+ it('should check ctoken', function(done) {
+ const app = utils.app('apps/csrf-disable');
+ request(app.listen())
+ .get('/api/user.json?name=suqian.yf')
+ .set('accept', 'application/json')
+ .expect(403)
+ .expect({
+ message: 'missing cookie ctoken',
+ }, done);
+ });
+ });
+
+ describe('security.csrf = false', function() {
+ it('should not check csrf', function(done) {
+ const app = utils.app('apps/csrf-disable');
+ request(app.listen())
+ .post('/api/user')
+ .send({ name: 'suqian.yf' })
+ .expect(200)
+ .expect({
+ url: '/api/user',
+ name: 'suqian.yf',
+ }, done);
+ });
+ });
+
+ describe('security.csrf = true', function() {
+ it('should check csrf', function(done) {
+ const app = utils.app('apps/ctoken-disable');
+ request(app.listen())
+ .post('/api/user')
+ .send({ name: 'suqian.yf' })
+ .expect(403)
+ .expect('secret is missing', done);
+ });
+ });
+
+ describe('security.csrfIgnore and ctokenIgnore', function() {
+ let app;
+ before(function() {
+ app = utils.app('apps/ctoken-ignore');
+ });
+
+ it('should not check csrf on /api/*', function(done) {
+ request(app.listen())
+ .post('/api/user')
+ .send({ name: 'suqian.yf' })
+ .expect(200)
+ .expect({
+ url: '/api/user',
+ name: 'suqian.yf',
+ }, done);
+ });
+
+ it('should not check ctoken on /api/*', function(done) {
+ request(app.listen())
+ .post('/api/user.json')
+ .send({ name: 'suqian.yf' })
+ .expect(200)
+ .expect({
+ url: '/api/user.json',
+ name: 'suqian.yf',
+ }, done);
+ });
+
+ it('should check ctoken on other', function(done) {
+ request(app.listen())
+ .post('/apiuser.json')
+ .set('accept', 'application/json')
+ .send({ name: 'suqian.yf' })
+ .expect({
+ message: 'missing cookie ctoken',
+ })
+ .expect(403, done);
+ });
+
+ it('should check csrf on other', function(done) {
+ request(app.listen())
+ .post('/apiuser')
+ .send({ name: 'suqian.yf' })
+ .expect('secret is missing')
+ .expect(403, done);
+ });
+ });
+});
diff --git a/test/lib/core/app/middleware/site_file.test.js b/test/lib/core/app/middleware/site_file.test.js
new file mode 100644
index 0000000000..d3cb1837bd
--- /dev/null
+++ b/test/lib/core/app/middleware/site_file.test.js
@@ -0,0 +1,98 @@
+'use strict';
+
+const should = require('should');
+const request = require('supertest-as-promised');
+const utils = require('../../../../utils');
+
+describe('test/lib/core/app/middleware/site_file.test.js', () => {
+ let app;
+ before(() => {
+ app = utils.app('apps/middlewares');
+ return app.ready();
+ });
+
+ after(() => app.close());
+
+ it('should GET /favicon.ico 200', () => {
+ return request(app.callback())
+ .get('/favicon.ico')
+ .expect('Content-Type', 'image/x-icon')
+ // .expect(res => console.log(res.headers))
+ .expect(200);
+ });
+
+ it('should GET /favicon.ico?t=123 200', () => {
+ return request(app.callback())
+ .get('/favicon.ico?t=123')
+ .expect('Content-Type', 'image/x-icon')
+ // .expect(res => console.log(res.headers))
+ .expect(200);
+ });
+
+ it('should 200 when accessing /robots.txt', () => {
+ return request(app.callback())
+ .get('/robots.txt')
+ .expect('User-agent: Baiduspider\nDisallow: /\n\nUser-agent: baiduspider\nDisallow: /')
+ .expect(200);
+ });
+
+ it('should 200 when accessing crossdomain.xml', () => {
+ return request(app.callback())
+ .get('/crossdomain.xml')
+ .expect('xxx')
+ .expect(200);
+ });
+
+ it('should support HEAD', () => {
+ return request(app.callback())
+ .head('/robots.txt')
+ .expect('content-length', 72)
+ .expect('') // body must be empty for HEAD
+ .expect(200);
+ });
+
+ it('should ignore POST', () => {
+ return request(app.callback())
+ .post('/robots.txt')
+ .expect(404);
+ });
+
+ it('normal router should work', () => {
+ return request(app.callback())
+ .get('/')
+ .expect('home')
+ .expect(200);
+ });
+
+ it('not defined router should 404', () => {
+ return request(app.callback())
+ .get('/xxx')
+ .expect(404);
+ });
+
+ it('should 404 when accessing fake.txt using wrong config', () => {
+ return request(app.callback())
+ .get('/fake.txt')
+ .expect(404);
+ });
+
+ describe('custom favicon', () => {
+ let app;
+ before(() => {
+ app = utils.app('apps/favicon');
+ return app.ready();
+ });
+
+ after(() => app.close());
+
+ it('should redirect https://eggjs.org/favicon.ico', () => {
+ return request(app.callback())
+ .get('/favicon.ico')
+ .expect(302, (err, res) => {
+ should.not.exist(err);
+ should.not.exist(res.headers['set-cookie']);
+ res.headers.location.should.eql('https://eggjs.org/favicon.ico');
+ });
+ });
+ });
+});
diff --git a/test/lib/core/app_worker_client.test.js b/test/lib/core/app_worker_client.test.js
new file mode 100644
index 0000000000..de5e9e9a75
--- /dev/null
+++ b/test/lib/core/app_worker_client.test.js
@@ -0,0 +1,204 @@
+'use strict';
+
+const should = require('should');
+const mm = require('egg-mock');
+const pedding = require('pedding');
+const utils = require('../../utils');
+
+describe('test/lib/core/app_worker_client.test.js', () => {
+
+ let app;
+ let client;
+
+ before(done => {
+ app = utils.app('apps/demo');
+ const impl = {
+ subscribe(reg, listener) {
+ this._subscribe(reg, listener);
+ return this;
+ },
+
+ unSubscribe(reg, listener) {
+ return this._unSubscribe(reg, listener);
+ },
+
+ * getData(id) {
+ return yield this._invoke('getData', [ id ]);
+ },
+
+ sendOneway(data) {
+ this._invokeOneway('sendOneway', [ data ]);
+ },
+ };
+
+ client = app.createAppWorkerClient('mock', impl, {
+ responseTimeout: 3000,
+ force: true,
+ });
+
+ client._on('xxx', () => {});
+ client._once('yyy', () => {});
+ client._removeListener('xxx', () => {});
+ client._removeAllListeners('xxx');
+ client._removeAllListeners('yyy');
+
+ client.ready(done);
+ });
+
+ afterEach(mm.restore);
+
+ it('should work', () => {
+ mm(client, '_opaque', Math.pow(2, 31) - 10);
+
+ client.publicEvents.should.eql([ 'agent_restart', 'error' ]);
+ client._getNextOpaque().should.equal(0);
+ });
+
+ it('should subscribe data well', done => {
+ done = pedding(done, 2);
+ const info = {
+ dataId: 'mockId',
+ groupId: 'mockGroup',
+ };
+
+ client.subscribe(info, function(value) {
+ value.should.equal('mock data');
+ done();
+ });
+
+ client.messenger.emit('mock_subscribe_changed', {
+ key: JSON.stringify(info),
+ info,
+ value: 'mock data',
+ });
+
+ client.unSubscribe(info, () => {});
+ client.unSubscribe(info);
+
+ client._subscriptions.has(JSON.stringify(info)).should.equal(false);
+
+ client.messenger.emit('mock_subscribe_changed', {
+ key: JSON.stringify(info),
+ info,
+ value: 'mock data2',
+ });
+
+ setTimeout(() => done(), 500);
+ });
+
+ it('should subscribe string well', done => {
+ const info = 'mock-info';
+
+ client.subscribe(info, function(value) {
+ value.should.equal('mock data');
+ done();
+ });
+
+ client.messenger.emit('mock_subscribe_changed', {
+ key: JSON.stringify(info),
+ info,
+ value: 'mock data',
+ });
+ });
+
+ it('should invoke API well', function* () {
+ mm(client, '_opaque', 1);
+
+ setTimeout(() => {
+ client.messenger.emit('mock_invoke_response', {
+ opaque: 1,
+ success: true,
+ data: 'mock data',
+ });
+ }, 100);
+
+ const result = yield client.getData('123');
+ result.should.equal('mock data');
+ });
+
+ it('should invoke API well with wrong opaque', function* () {
+ let warned = false;
+ mm(client, '_opaque', 10);
+ const logger = client.logger;
+ mm(logger, 'warn', (msg, name, data) => {
+ if (data === 'mock data') {
+ warned = true;
+ }
+ });
+
+ setTimeout(() => {
+ client.messenger.emit('mock_invoke_response', {
+ opaque: 1,
+ success: true,
+ data: 'mock data',
+ });
+ }, 100);
+
+ try {
+ yield client.getData('123');
+ } catch (err) {
+ // do noting
+ }
+ should(warned).equal(true);
+ });
+
+ it('should invoke oneway ok', done => {
+ client._invoke('sendOneway', [ '123' ], {
+ oneway: true,
+ }).then(done);
+ });
+
+ it('should call sendOneway ok', done => {
+ mm(client, '_opaque', 1);
+ mm(client, '_sendToAgent', (cmd, data) => {
+ cmd.should.equal('mock_invoke_request');
+ const pid = client.pid;
+ data.should.eql({
+ opaque: 1,
+ method: 'sendOneway',
+ args: [ '123' ],
+ pid,
+ oneway: true,
+ });
+ done();
+ });
+ client.sendOneway('123');
+ });
+
+ it('should invoke API with error', function* () {
+ mm(client, '_opaque', 1);
+
+ setTimeout(() => {
+ client.messenger.emit('mock_invoke_response', {
+ opaque: 1,
+ success: false,
+ errorMessage: 'mock error',
+ });
+ }, 100);
+
+ try {
+ yield client.getData('123');
+ throw new Error('should not run here');
+ } catch (err) {
+ err.message.should.equal('mock error');
+ }
+ });
+
+ it('should throw timeout error', function* () {
+ mm(client, '_opaque', 1);
+ try {
+ yield client.getData('123');
+
+ throw new Error('should not run here');
+ } catch (err) {
+ err.name.should.equal('AgentWorkerRequestTimeoutError');
+ err.message.should.equal('Agent worker no response in 3000ms, AppWorkerClient:mock invoke getData with req#1');
+ }
+ });
+
+ it('should emit agent_restart when agent worker restart', done => {
+ client.on('agent_restart', done);
+ client.messenger.emit('agent-start');
+ });
+
+});
diff --git a/test/lib/core/config/config.test.js b/test/lib/core/config/config.test.js
new file mode 100644
index 0000000000..91eb27cd85
--- /dev/null
+++ b/test/lib/core/config/config.test.js
@@ -0,0 +1,19 @@
+'use strict';
+
+const mm = require('egg-mock');
+const utils = require('../../../utils');
+
+describe('test/lib/core/config/config.test.js', () => {
+ let app;
+ before(() => {
+ app = utils.app('apps/demo');
+ return app.ready();
+ });
+ after(() => app.close());
+
+ afterEach(mm.restore);
+
+ it('should return config.core.name that is deprecated', () => {
+ app.config.core.name.should.equal('Egg');
+ });
+});
diff --git a/test/lib/core/cookies.test.js b/test/lib/core/cookies.test.js
new file mode 100644
index 0000000000..e52e782713
--- /dev/null
+++ b/test/lib/core/cookies.test.js
@@ -0,0 +1,249 @@
+'use strict';
+
+const should = require('should');
+const mm = require('egg-mock');
+const request = require('supertest-as-promised');
+const utils = require('../../utils');
+
+describe('test/lib/core/cookies.test.js', () => {
+ afterEach(mm.restore);
+
+ describe('secure = true', () => {
+ let app;
+ before(() => {
+ app = utils.app('apps/secure-app');
+ return app.ready();
+ });
+
+ after(() => app.close());
+
+ it('should throw TypeError when set secure on not secure request', () => {
+ const ctx = app.mockContext();
+ (function() {
+ ctx.setCookie('foo', 'bar', { secure: true });
+ }).should.throw('Cannot send secure cookie over unencrypted connection');
+ });
+
+ it('should set cookie twice and not set domain when ctx.hostname=localhost', () => {
+ const ctx = app.mockContext();
+ ctx.set('Set-Cookie', 'foo=bar');
+ ctx.setCookie('foo1', 'bar1');
+ ctx.response.get('set-cookie').should.eql([
+ 'foo=bar',
+ 'foo1=bar1; path=/; httponly',
+ ]);
+ });
+
+ it('should throw TypeError when set encrypt on keys not exists', () => {
+ mm(app, 'keys', null);
+ const ctx = app.mockContext();
+ (function() {
+ ctx.setCookie('foo', 'bar', {
+ encrypt: true,
+ });
+ }).should.throw('.keys required for encrypt cookies');
+ });
+
+ it('should throw TypeError when get encrypt on keys not exists', () => {
+ mm(app, 'keys', null);
+ const ctx = app.mockContext();
+ ctx.header.cookie = 'foo=bar';
+ (function() {
+ ctx.getCookie('foo', {
+ encrypt: true,
+ });
+ }).should.throw('.keys required for encrypt cookies');
+ });
+
+ it.skip('should not set secure when request protocol is http', done => {
+ request(app.callback())
+ .get('/')
+ .set('Host', 'demo.eggjs.org')
+ .set('X-Forwarded-Proto', 'http')
+ .expect('hello mock secure app')
+ .expect(200, (err, res) => {
+ should.not.exist(err);
+ const cookie = res.headers['set-cookie'][0];
+ should.exist(cookie);
+ cookie.should.match(/^ctoken=\w+secure\-app; path=\/; domain=\.eggjs\.org$/);
+ done();
+ });
+ });
+
+ it.skip('should set secure:true and httponly cookie', done => {
+ request(app.callback())
+ .get('/')
+ .set('Host', 'demo.eggjs.org')
+ .set('X-Forwarded-Proto', 'https')
+ .expect('hello mock secure app')
+ .expect(200, (err, res) => {
+ should.not.exist(err);
+ const cookie = res.headers['set-cookie'][0];
+ should.exist(cookie);
+ cookie.should.match(/^ctoken=\w+secure\-app; path=\/; domain=\.eggjs\.org; secure$/);
+ done();
+ });
+ });
+
+ it('should set cookie with path: /cookiepath/ok', done => {
+ request(app.callback())
+ .get('/?cookiepath=/cookiepath/ok')
+ .set('Host', 'demo.eggjs.org')
+ .set('X-Forwarded-Proto', 'https')
+ .expect('hello mock secure app')
+ .expect(200, (err, res) => {
+ should.not.exist(err);
+ const cookie = res.headers['set-cookie'][0];
+ should.exist(cookie);
+ cookie.should.match(/^cookiepath=\/cookiepath\/ok; path=\/cookiepath\/ok; secure; httponly$/);
+ done();
+ });
+ });
+
+ it('should delete cookie', done => {
+ request(app.callback())
+ .get('/?cookiedel=true')
+ .set('Host', 'demo.eggjs.org')
+ .set('Cookie', 'cookiedel=true')
+ .set('X-Forwarded-Proto', 'https')
+ .expect('hello mock secure app')
+ .expect(200, (err, res) => {
+ should.not.exist(err);
+ const cookie = res.headers['set-cookie'][0];
+ should.exist(cookie);
+ cookie.should.equal('cookiedel=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT; secure; httponly');
+ const expires = cookie.match(/expires=(.*?);/)[1];
+ (Date.now() > new Date(expires)).should.be.true;
+ done();
+ });
+ });
+
+ it('should delete cookie with options', done => {
+ request(app.callback())
+ .get('/?cookiedel=true&opts=true')
+ .set('Host', 'demo.eggjs.org')
+ .set('Cookie', 'cookiedel=true; path=/hello; domain=eggjs.org; expires=30')
+ .set('X-Forwarded-Proto', 'https')
+ .expect('hello mock secure app')
+ .expect(200, (err, res) => {
+ const cookie = res.headers['set-cookie'][0];
+ should.exist(cookie);
+ cookie.should.equal('cookiedel=; path=/hello; expires=Thu, 01 Jan 1970 00:00:00 GMT; domain=eggjs.org; secure; httponly');
+ const expires = cookie.match(/expires=(.*?);/)[1];
+ (Date.now() > new Date(expires)).should.be.true;
+ done();
+ });
+ });
+
+ it('should set cookie with domain: okcookie.eggjs.org', done => {
+ request(app.callback())
+ .get('/?cookiedomain=okcookie.eggjs.org&cookiepath=/')
+ .set('Host', 'demo.eggjs.org')
+ .set('X-Forwarded-Proto', 'https')
+ .expect('hello mock secure app')
+ .expect(200, (err, res) => {
+ should.not.exist(err);
+ const cookie = res.headers['set-cookie'][0];
+ should.exist(cookie);
+ cookie.should.equal('cookiepath=/; path=/; domain=okcookie.eggjs.org; secure; httponly');
+ done();
+ });
+ });
+
+ it('should not set domain and path', done => {
+ request(app.callback())
+ .get('/?notSetPath=okok')
+ .set('Host', 'demo.eggjs.org')
+ .set('X-Forwarded-Proto', 'https')
+ .expect('hello mock secure app')
+ .expect(200, (err, res) => {
+ should.not.exist(err);
+ const cookie = res.headers['set-cookie'][0];
+ should.exist(cookie);
+ cookie.should.equal('notSetPath=okok; secure; httponly');
+ done();
+ });
+ });
+ });
+
+ describe('secure = false', () => {
+ it('should set secure:false cookie', done => {
+ const app = utils.app('apps/demo');
+ request(app.callback())
+ .get('/hello')
+ .set('Host', 'demo.eggjs.org')
+ .expect('hello')
+ .expect(200, (err, res) => {
+ should.not.exist(err);
+ const cookie = res.headers['set-cookie'][0];
+ should.exist(cookie);
+ cookie.should.match('hi=foo; path=/; httponly');
+ done();
+ });
+ });
+ });
+
+ describe('encrypt = true', () => {
+ let app;
+
+ before(() => {
+ app = utils.app('apps/encrypt-cookies');
+ return app.ready();
+ });
+
+ after(() => app.close());
+
+ it('should get encrypt cookie', done => {
+ request(app.callback())
+ .get('/')
+ .expect({
+ set: 'bar 中文',
+ })
+ .expect(200, (err, res) => {
+ should.not.exist(err);
+ const encryptCookie = res.headers['set-cookie'][0];
+ should.exist(encryptCookie);
+ encryptCookie.should.equal('foo=B9om8kiaZ7Xg9dzTUoH-Pw==; path=/; httponly');
+
+ const plainCookie = res.headers['set-cookie'][1];
+ should.exist(plainCookie);
+ plainCookie.should.equal('plain=text ok; path=/; httponly');
+
+ request(app.callback())
+ .get('/')
+ .set('Cookie', res.headers['set-cookie'].join(';'))
+ .expect({
+ set: 'bar 中文',
+ encrypt: 'bar 中文',
+ encryptWrong: 'B9om8kiaZ7Xg9dzTUoH-Pw==',
+ plain: 'text ok',
+ })
+ .expect(200, done);
+ });
+ });
+
+ it('should decode encrypt value fail', done => {
+ request(app.callback())
+ .get('/')
+ .expect({
+ set: 'bar 中文',
+ })
+ .expect(200, (err, res) => {
+ should.not.exist(err);
+ const encryptCookie = res.headers['set-cookie'][0];
+ should.exist(encryptCookie);
+ encryptCookie.should.equal('foo=B9om8kiaZ7Xg9dzTUoH-Pw==; path=/; httponly');
+
+ request(app.callback())
+ .get('/')
+ .set('Cookie', 'foo=123123; plain=text ok')
+ .expect({
+ set: 'bar 中文',
+ encryptWrong: '123123',
+ plain: 'text ok',
+ })
+ .expect(200, done);
+ });
+ });
+ });
+});
diff --git a/test/lib/core/loader/config_loader.test.js b/test/lib/core/loader/config_loader.test.js
new file mode 100644
index 0000000000..e84ce746be
--- /dev/null
+++ b/test/lib/core/loader/config_loader.test.js
@@ -0,0 +1,26 @@
+'use strict';
+
+const utils = require('../../../utils');
+const AppWorkerLoader = require('../../../../').AppWorkerLoader;
+
+function createLoader(baseDir) {
+ baseDir = utils.getFilepath(baseDir);
+ const loader = new AppWorkerLoader({
+ baseDir,
+ });
+ loader.loadConfig();
+ return loader;
+}
+
+describe('test/lib/core/loader/config_loader.test.js', () => {
+ it('should get middlewares', () => {
+ const appLoader = createLoader('apps/demo');
+ appLoader.config.coreMiddleware.should.eql([
+ 'meta',
+ 'siteFile',
+ 'notfound',
+ 'bodyParser',
+ 'overrideMethod',
+ ]);
+ });
+});
diff --git a/test/lib/core/loader/load_app.test.js b/test/lib/core/loader/load_app.test.js
new file mode 100644
index 0000000000..84494bd860
--- /dev/null
+++ b/test/lib/core/loader/load_app.test.js
@@ -0,0 +1,28 @@
+'use strict';
+
+const should = require('should');
+const utils = require('../../../utils');
+
+describe('test/lib/core/loader/load_app.test.js', () => {
+ let app;
+ before(() => {
+ app = utils.app('apps/loader-plugin');
+ return app.ready();
+ });
+ after(() => app.close());
+
+ it('should load app.js', () => {
+ app.b.should.equal('plugin b');
+ app.c.should.equal('plugin c');
+ app.app.should.equal('app');
+ });
+
+ it('should load plugin app.js first', () => {
+ (app.dateB <= app.date).should.equal(true);
+ (app.dateC <= app.date).should.equal(true);
+ });
+
+ it('should not load disable plugin', () => {
+ should.not.exists(app.a);
+ });
+});
diff --git a/test/lib/core/loader/load_plugin.test.js b/test/lib/core/loader/load_plugin.test.js
new file mode 100644
index 0000000000..6d9aad1b0e
--- /dev/null
+++ b/test/lib/core/loader/load_plugin.test.js
@@ -0,0 +1,250 @@
+'use strict';
+
+const should = require('should');
+const path = require('path');
+const mm = require('egg-mock');
+const AppWorkerLoader = require('../../../../').AppWorkerLoader;
+const utils = require('../../../utils');
+
+const EGG_BASE = path.join(__dirname, '../../../../');
+
+describe('test/lib/core/loader/load_plugin.test.js', () => {
+
+ afterEach(mm.restore);
+
+ it('should loadConfig all plugins', () => {
+ const baseDir = utils.getFilepath('apps/loader-plugin');
+ const appLoader = new AppWorkerLoader({
+ baseDir,
+ });
+ appLoader.loadConfig();
+ appLoader.plugins.b.should.eql({
+ enable: true,
+ name: 'b',
+ dep: [],
+ env: [],
+ path: path.join(baseDir, 'node_modules/b'),
+ });
+ appLoader.plugins.c.should.eql({
+ enable: true,
+ name: 'c',
+ dep: [],
+ env: [],
+ path: path.join(baseDir, 'node_modules/c'),
+ });
+ appLoader.plugins.e.should.eql({
+ enable: true,
+ name: 'e',
+ dep: [ 'f' ],
+ env: [],
+ path: path.join(baseDir, 'plugins/e'),
+ });
+ appLoader.plugins.onerror.path.should.equal(path.join(EGG_BASE, 'node_modules/egg-onerror'));
+ appLoader.plugins.onerror.package.should.equal('egg-onerror');
+ appLoader.plugins.onerror.version.should.match(/\d+\.\d+\.\d+/);
+ appLoader.orderPlugins.should.be.an.Array;
+ });
+
+ it('should same name plugin level follow: app > framework > egg', () => {
+ const baseDir = utils.getFilepath('apps/loader-plugin');
+ const appLoader = new AppWorkerLoader({
+ baseDir,
+ });
+ appLoader.loadConfig();
+
+ appLoader.plugins.rds.should.eql({
+ enable: true,
+ name: 'rds',
+ dep: [ 'session' ],
+ env: [],
+ package: 'rds',
+ path: path.join(baseDir, 'node_modules/rds'),
+ });
+ });
+
+ it('should plguin support alias name', () => {
+ const baseDir = utils.getFilepath('apps/loader-plugin');
+ const appLoader = new AppWorkerLoader({
+ baseDir,
+ });
+ appLoader.loadConfig();
+ appLoader.plugins.d1.should.eql({
+ enable: true,
+ name: 'd1',
+ package: 'd',
+ dep: [],
+ env: [],
+ path: path.join(baseDir, 'node_modules/d'),
+ });
+ should.not.exists(appLoader.plugins.d);
+ });
+
+ it('should support package.json config', () => {
+ const baseDir = utils.getFilepath('apps/loader-plugin');
+ const appLoader = new AppWorkerLoader({
+ baseDir,
+ });
+ appLoader.loadConfig();
+ appLoader.plugins.g.should.eql({
+ enable: true,
+ name: 'g',
+ dep: [ 'f' ],
+ env: [],
+ path: path.join(baseDir, 'plugins/g'),
+ });
+ });
+
+ it('should show warning message when plugin name wrong', () => {
+ let message;
+ mm(console, 'warn', m => {
+ if (!m.startsWith('[egg:loader] eggPlugin is missing') && !message) {
+ message = m;
+ }
+ });
+ const baseDir = utils.getFilepath('apps/loader-plugin');
+ const appLoader = new AppWorkerLoader({
+ baseDir,
+ });
+ appLoader.loadConfig();
+
+ message.should.eql('[egg:loader] pluginName(e) is different from pluginConfigName(wrong-name)');
+ });
+
+ it('should loadConfig plugins with custom plugins config', () => {
+ const baseDir = utils.getFilepath('apps/loader-plugin');
+ const plugins = {
+ foo: {
+ enable: true,
+ path: path.join(baseDir, 'node_modules/d'),
+ },
+ d1: {
+ env: [ 'unittest' ],
+ },
+ };
+ const appLoader = new AppWorkerLoader({
+ baseDir,
+ plugins,
+ });
+ appLoader.loadConfig();
+
+ appLoader.plugins.d1.should.eql({
+ enable: true,
+ name: 'd1',
+ package: 'd',
+ dep: [],
+ env: [ 'unittest' ],
+ path: path.join(baseDir, 'node_modules/d'),
+ });
+ appLoader.plugins.foo.should.eql({
+ enable: true,
+ name: 'foo',
+ dep: [],
+ env: [],
+ path: path.join(baseDir, 'node_modules/d'),
+ });
+ should.not.exists(appLoader.plugins.d);
+ });
+
+ it('should throw error when plugin not exists', () => {
+ (function() {
+ const baseDir = utils.getFilepath('apps/loader-plugin-noexist');
+ const appLoader = new AppWorkerLoader({
+ baseDir,
+ });
+ appLoader.loadConfig();
+ }).should.throw(/Can not find plugin noexist in /);
+ });
+
+ it('should throw error when app baseDir not exists', () => {
+ (function() {
+ const baseDir = utils.getFilepath('apps/notexist-app');
+ const appLoader = new AppWorkerLoader({
+ baseDir,
+ });
+ appLoader.loadConfig();
+ }).should.throw(/notexist\-app not exists/);
+ });
+
+ it('should keep plugin list sorted', () => {
+ mm(process.env, 'NODE_ENV', 'development');
+ const baseDir = utils.getFilepath('apps/loader-plugin-dep');
+ const appLoader = new AppWorkerLoader({
+ baseDir,
+ });
+ appLoader.loadConfig();
+ appLoader.orderPlugins.map(plugin => {
+ return plugin.name;
+ }).should.eql([
+ 'onerror',
+ // 'userservice',
+ 'userrole',
+ 'session',
+ // 'i18n',
+ 'validate',
+ // 'watcher',
+ // 'multipart',
+ // 'security',
+ // 'development',
+ // 'logrotater',
+ // 'schedule',
+ 'b',
+ 'c1',
+ 'f',
+ 'a',
+ 'd',
+ 'e',
+ ]);
+ });
+
+ it('should throw recursive deps error', () => {
+ (function() {
+ const baseDir = utils.getFilepath('apps/loader-plugin-dep-recursive');
+ const appLoader = new AppWorkerLoader({
+ baseDir,
+ });
+ appLoader.loadConfig();
+ }).should.throw('sequencify plugins has problem, missing: [], recursive: [a,b,c,a]');
+ });
+
+ it('should throw error when plugin dep not exists', function() {
+ (function() {
+ const baseDir = utils.getFilepath('apps/loader-plugin-dep-missing');
+ const appLoader = new AppWorkerLoader({
+ baseDir,
+ });
+ appLoader.loadConfig();
+ }).should.throw('sequencify plugins has problem, missing: [a1], recursive: []\n\t>> Plugin [a1] is disabled or missed, but is required by [c]');
+ });
+
+ it('should auto fill plugin infos', () => {
+ mm(process.env, 'NODE_ENV', 'test');
+ const baseDir = utils.getFilepath('apps/loader-plugin');
+ const appLoader1 = new AppWorkerLoader({
+ baseDir,
+ });
+ appLoader1.loadConfig();
+ // unittest disable
+ const keys1 = appLoader1.orderPlugins.map(plugin => {
+ return plugin.name;
+ }).join(',');
+ keys1.should.containEql('b,c,d1,f,e');
+ should.not.exist(appLoader1.plugins.a1);
+
+ mm(process.env, 'NODE_ENV', 'development');
+ const appLoader2 = new AppWorkerLoader({
+ baseDir,
+ });
+ appLoader2.loadConfig();
+ const keys2 = appLoader2.orderPlugins.map(plugin => {
+ return plugin.name;
+ }).join(',');
+ keys2.should.containEql('d1,a1,b,c,f,e');
+ appLoader2.plugins.a1.should.eql({
+ enable: true,
+ name: 'a1',
+ dep: [ 'd1' ],
+ env: [ 'local', 'prod' ],
+ path: path.join(baseDir, 'node_modules/a1'),
+ });
+ });
+});
diff --git a/test/lib/core/loader/load_router.test.js b/test/lib/core/loader/load_router.test.js
new file mode 100644
index 0000000000..f5196a8a1e
--- /dev/null
+++ b/test/lib/core/loader/load_router.test.js
@@ -0,0 +1,27 @@
+'use strict';
+
+const request = require('supertest-as-promised');
+const pedding = require('pedding');
+const utils = require('../../../utils');
+
+describe('test/lib/core/loader/load_router.test.js', () => {
+ let app;
+ before(() => {
+ app = utils.app('apps/app-router');
+ return app.ready();
+ });
+ after(() => app.close());
+
+ it('should load app/router.js', done => {
+ done = pedding(2, done);
+ request(app.callback())
+ .get('/')
+ .expect(200)
+ .expect('hello', done);
+
+ request(app.callback())
+ .get('/home')
+ .expect(200)
+ .expect('hello', done);
+ });
+});
diff --git a/test/lib/core/loader/load_service.test.js b/test/lib/core/loader/load_service.test.js
new file mode 100644
index 0000000000..5ab125f673
--- /dev/null
+++ b/test/lib/core/loader/load_service.test.js
@@ -0,0 +1,108 @@
+'use strict';
+
+const should = require('should');
+const request = require('supertest');
+const mm = require('egg-mock');
+const utils = require('../../../utils');
+
+describe('test/lib/core/loader/load_service.test.js', () => {
+ afterEach(mm.restore);
+
+ it('should load app and plugin services', done => {
+ const app = utils.app('apps/loader-plugin');
+ app.ready(() => {
+ should.exists(app.serviceClasses.foo);
+ should.exists(app.serviceClasses.foo2);
+ should.not.exists(app.serviceClasses.bar1);
+ should.exists(app.serviceClasses.bar2);
+ should.exists(app.serviceClasses.foo4);
+
+ const ctx = app.mockContext();
+ should.exists(ctx.service.fooDir.foo5);
+ should.exists(ctx.service.foo);
+ should.exists(ctx.service.foo2);
+ should.exists(ctx.service.bar2);
+ should.exists(ctx.service.foo4);
+
+ request(app.callback())
+ .get('/')
+ .expect({
+ foo2: 'foo2',
+ foo3: 'foo3',
+ })
+ .expect(200, err => {
+ app.close();
+ done(err);
+ });
+ });
+ });
+
+ it('should service support es6', function* () {
+ const app = utils.app('apps/services_loader_verify');
+ yield app.ready();
+ app.serviceClasses.should.have.property('foo');
+ app.serviceClasses.foo.should.have.properties('bar', 'bar1', 'aa');
+ app.close();
+ });
+
+ it('should support extend app.Service class', done => {
+ const app = utils.app('apps/service-app');
+ app.ready(() => {
+ request(app.callback())
+ .get('/user')
+ .expect(res => {
+ should.exists(res.body.user);
+ res.body.user.userId.should.equal('123mock');
+ })
+ .expect(200, err => {
+ app.close();
+ done(err);
+ });
+ });
+ });
+
+ describe('sub dir', () => {
+ it('should support top 1 and 2 dirs, ignore others', done => {
+ mm(process.env, 'NO_DEPRECATION', '*');
+ const app = utils.app('apps/subdir-services');
+ request(app.callback())
+ .get('/')
+ .expect({
+ user: {
+ uid: '123',
+ },
+ cif: {
+ uid: '123cif',
+ cif: true,
+ },
+ bar1: {
+ name: 'bar1name',
+ bar: 'bar1',
+ },
+ bar2: {
+ name: 'bar2name',
+ bar: 'bar2',
+ },
+ 'foo.subdir2.sub2': {
+ name: 'bar3name',
+ bar: 'bar3',
+ },
+ subdir11bar: false,
+ ok: {
+ ok: true,
+ },
+ cmd: {
+ cmd: 'hihi',
+ method: 'GET',
+ url: '/',
+ },
+ serviceIsSame: true,
+ oldStyle: '/',
+ })
+ .expect(200, err => {
+ app.close();
+ done(err);
+ });
+ });
+ });
+});
diff --git a/test/lib/core/logger.test.js b/test/lib/core/logger.test.js
new file mode 100644
index 0000000000..bb1ad4188f
--- /dev/null
+++ b/test/lib/core/logger.test.js
@@ -0,0 +1,267 @@
+'use strict';
+
+const moment = require('moment');
+const should = require('should');
+const path = require('path');
+const fs = require('fs');
+const mm = require('egg-mock');
+const request = require('supertest');
+const Logger = require('egg-logger');
+const utils = require('../../utils');
+const Agent = require('../../..').Agent;
+
+describe('test/lib/core/logger.test.js', () => {
+ afterEach(mm.restore);
+
+ it('should got right default config on prod env', () => {
+ mm.env('prod');
+ mm(process.env, 'EGG_LOG', '');
+ mm(process.env, 'HOME', utils.getFilepath('apps/mock-production-app/config'));
+ const app = utils.app('apps/mock-production-app');
+ // 生产环境默认 _level = info
+ app.logger.get('file').options.level.should.equal(Logger.INFO);
+ // stdout 默认 INFO
+ app.logger.get('console').options.level.should.equal(Logger.INFO);
+ app.coreLogger.get('file').options.level.should.equal(Logger.INFO);
+ app.coreLogger.get('console').options.level.should.equal(Logger.INFO);
+ app.close();
+ });
+
+ it('should got right level on local env', () => {
+ mm.env('local');
+ mm(process.env, 'EGG_LOG', '');
+ const app = utils.app('apps/mock-dev-app');
+
+ app.logger.get('file').options.level.should.equal(Logger.DEBUG);
+ app.logger.get('console').options.level.should.equal(Logger.INFO);
+ app.coreLogger.get('file').options.level.should.equal(Logger.DEBUG);
+ app.coreLogger.get('console').options.level.should.equal(Logger.WARN);
+ app.close();
+ });
+
+ it('should set EGG_LOG level on local env', () => {
+ mm.env('local');
+ mm(process.env, 'EGG_LOG', 'ERROR');
+ const app = utils.app('apps/mock-dev-app');
+ app.logger.get('file').options.level.should.equal(Logger.DEBUG);
+ app.logger.get('console').options.level.should.equal(Logger.ERROR);
+ app.coreLogger.get('file').options.level.should.equal(Logger.DEBUG);
+ app.coreLogger.get('console').options.level.should.equal(Logger.ERROR);
+ app.close();
+ });
+
+ it('should got right config on unittest env', () => {
+ mm.env('unittest');
+ mm(process.env, 'EGG_LOG', '');
+ const app = utils.app('apps/mock-dev-app');
+ app.logger.get('file').options.level.should.equal(Logger.INFO);
+ app.logger.get('console').options.level.should.equal(Logger.WARN);
+ app.coreLogger.get('file').options.level.should.equal(Logger.INFO);
+ app.coreLogger.get('console').options.level.should.equal(Logger.WARN);
+ app.close();
+ });
+
+ it('should set log.consoleLevel to env.EGG_LOG', () => {
+ mm(process.env, 'EGG_LOG', 'ERROR');
+ const app = utils.app('apps/mock-dev-app');
+ app.logger.get('file').options.level.should.equal(Logger.INFO);
+ app.logger.get('console').options.level.should.equal(Logger.ERROR);
+ app.close();
+ });
+
+ it('log buffer disable cache on local and unittest env', done => {
+ mm(process.env, 'EGG_LOG', 'NONE');
+ const app = utils.app('apps/nobuffer-logger');
+ app.ready(() => {
+ const ctx = app.mockContext();
+ const logfile = path.join(app.config.logger.dir, 'common-error.log');
+ // app.config.logger.buffer.should.equal(false);
+ ctx.logger.error(new Error('mock nobuffer error'));
+ setTimeout(() => {
+ app.close();
+ fs.readFileSync(logfile, 'utf8').should.containEql('nodejs.Error: mock nobuffer error\n');
+ done();
+ }, 1000);
+ });
+ });
+
+ it('log buffer enable cache on non-local and non-unittest env', done => {
+ mm(process.env, 'EGG_LOG', 'none');
+ mm.env('prod');
+ mm(process.env, 'HOME', utils.getFilepath('apps/mock-production-app/config'));
+ const app = utils.app('apps/mock-production-app');
+ app.ready(() => {
+ const ctx = app.mockContext();
+ const logfile = path.join(app.config.logger.dir, 'common-error.log');
+ // app.config.logger.buffer.should.equal(true);
+ ctx.logger.error(new Error('mock enable buffer error'));
+ setTimeout(() => {
+ app.close();
+ fs.readFileSync(logfile, 'utf8').should.containEql('');
+ done();
+ }, 1000);
+ });
+ });
+
+ it('output .json format log', done => {
+ mm(process.env, 'EGG_LOG', 'none');
+ mm.env('local');
+ const app = utils.app('apps/logger-output-json');
+ app.ready(() => {
+ const ctx = app.mockContext();
+ const logfile = path.join(app.config.logger.dir, 'logger-output-json-web.json.log');
+ ctx.logger.info('json format');
+ setTimeout(() => {
+ app.close();
+ fs.existsSync(logfile).should.be.true;
+ fs.readFileSync(logfile, 'utf8').should.containEql('"message":"json format"');
+ done();
+ }, 1000);
+ });
+ });
+
+ it('dont output to console after app ready', done => {
+ mm.env('default');
+ const app = utils.cluster('apps/logger');
+ app
+ .debug(false)
+ .coverage(false)
+ .expect('stdout', /agent info/)
+ .expect('stdout', /app info/)
+ .notExpect('stdout', /app info after ready/)
+ .expect('stderr', /nodejs.Error: agent error/)
+ .expect('stderr', /nodejs.Error: app error/)
+ .end(err => {
+ app.close();
+ should.not.exists(err);
+ done();
+ });
+ });
+
+ it('should still output to console after app ready on local env', done => {
+ mm.env('local');
+ const app = utils.cluster('apps/logger');
+ app
+ // .debug()
+ .coverage(false)
+ .expect('stdout', /agent info/)
+ .expect('stdout', /app info/)
+ .expect('stdout', /app info after ready/)
+ .expect('stderr', /nodejs.Error: agent error/)
+ .expect('stderr', /nodejs.Error: app error/)
+ .end(err => {
+ app.close();
+ should.not.exists(err);
+ done();
+ });
+ });
+
+ it('agent and app error should output to common-error.log', done => {
+ const baseDir = utils.getFilepath('apps/logger');
+ mm.env('default');
+ mm(process.env, 'EGG_LOG', 'none');
+ mm(process.env, 'HOME', baseDir);
+ const app = utils.cluster('apps/logger');
+ app
+ // .debug()
+ .coverage(false)
+ .end(err => {
+ app.close();
+ should.not.exists(err);
+ const content = fs.readFileSync(path.join(baseDir, 'logs/logger/common-error.log'), 'utf8');
+ content.should.containEql('nodejs.Error: agent error');
+ content.should.containEql('nodejs.Error: app error');
+ done();
+ });
+ });
+
+ it('all loggers error should redirect to errorLogger', done => {
+ const app = utils.app('apps/logger');
+ app.ready(() => {
+ app.logger.error(new Error('logger error'));
+ app.coreLogger.error(new Error('coreLogger error'));
+ app.loggers.errorLogger.error(new Error('errorLogger error'));
+ app.loggers.customLogger.error(new Error('customLogger error'));
+
+ setTimeout(() => {
+ app.close();
+ const content = fs.readFileSync(path.join(app.baseDir, 'logs/logger/common-error.log'), 'utf8');
+ content.should.containEql('nodejs.Error: logger error');
+ content.should.containEql('nodejs.Error: coreLogger error');
+ content.should.containEql('nodejs.Error: errorLogger error');
+ content.should.containEql('nodejs.Error: customLogger error');
+ done();
+ }, 10);
+ });
+ });
+
+ it('agent\'s logger is same as coreLogger', done => {
+ const agent = new Agent({
+ baseDir: utils.getFilepath('apps/logger'),
+ });
+ agent.logger.options.file.should.equal(agent.coreLogger.options.file);
+ agent.ready(done);
+ });
+
+ describe.skip('logger.reload()', () => {
+ let app;
+ before(() => {
+ mm(process.env, 'EGG_LOG', 'none');
+ app = utils.cluster('apps/logger-reload');
+ return app.ready();
+ });
+
+ after(() => app.close());
+
+ it('should reload worker loggers', done => {
+ request(app.callback())
+ .get('/')
+ .expect({
+ method: 'GET',
+ path: '/',
+ })
+ .expect(200, err => {
+ should.not.exist(err);
+ app.process.send({
+ to: 'agent',
+ action: 'test-reload-logger',
+ });
+ setTimeout(() => {
+ const logname = moment().subtract(1, 'days').format('.YYYY-MM-DD');
+ const logfile1 = utils.getFilepath('apps/logger-reload/logs/logger-reload/logger-reload-web.log');
+ const content1 = fs.readFileSync(logfile1, 'utf8');
+ content1.should.equal('');
+
+ const logfile2 = utils.getFilepath(`apps/logger-reload/logs/logger-reload/logger-reload-web.log${logname}`);
+ const content2 = fs.readFileSync(logfile2, 'utf8');
+ content2.should.containEql('GET /');
+
+ const logfile3 = utils.getFilepath(`apps/logger-reload/logs/logger-reload/egg-agent.log${logname}`);
+ fs.existsSync(logfile3).should.be.true;
+ done();
+ }, 2000);
+ });
+ });
+ });
+
+ describe('logger.level = DEBUG', () => {
+ let app;
+ before(done => {
+ app = utils.app('apps/logger-level-debug');
+ app.ready(done);
+ });
+ after(() => app.close());
+
+ it('should save debug log to file', done => {
+ request(app.callback())
+ .get('/')
+ .expect('ok')
+ .end(err => {
+ should.not.exist(err);
+ fs.readFileSync(path.join(app.config.baseDir, 'logs/foo/foo-web.log'), 'utf8')
+ .should.containEql(' DEBUG ');
+ done();
+ });
+ });
+ });
+});
diff --git a/test/lib/core/messenger.test.js b/test/lib/core/messenger.test.js
new file mode 100644
index 0000000000..17bf087468
--- /dev/null
+++ b/test/lib/core/messenger.test.js
@@ -0,0 +1,167 @@
+'use strict';
+
+const mm = require('egg-mock');
+const utils = require('../../utils');
+const Messenger = require('../../../lib/core/messenger');
+
+describe('test/lib/core/messenger.test.js', () => {
+ let messenger;
+
+ before(() => {
+ messenger = new Messenger();
+ });
+
+ afterEach(mm.restore);
+
+ describe('send()', () => {
+ it('should send demo message', done => {
+ // mock not childprocess
+ mm(process, 'send', null);
+ messenger.send('messenger-test-demo', { foo: 'haha' });
+ messenger.once('messenger-test-demo', data => {
+ data.should.eql({ foo: 'haha' });
+ done();
+ });
+ });
+
+ it('should mock process.send exists', done => {
+ mm(process, 'connected', true);
+ mm(process, 'send', msg => {
+ msg.should.eql({
+ action: 'message-test-send-demo',
+ data: {
+ foo: 'ok',
+ },
+ });
+ done();
+ });
+
+ messenger.send('message-test-send-demo', {
+ foo: 'ok',
+ });
+ });
+ });
+
+ describe('on(action, data)', () => {
+ it('should listen an action event', done => {
+ messenger.on('messenger-test-on-event', data => {
+ data.should.eql({
+ success: true,
+ });
+ done();
+ });
+
+ process.emit('message', {});
+
+ process.emit('message', null);
+
+ process.emit('message', {
+ action: 'messenger-test-on-event',
+ data: {
+ success: true,
+ },
+ });
+ });
+ });
+
+ describe('close()', () => {
+ it('should remove all listeners', () => {
+ const messenger = new Messenger();
+ messenger.on('messenger-test-on-event', () => {
+ throw new Error('should never emitted');
+ });
+
+ messenger.close();
+
+ process.emit('message', {
+ action: 'messenger-test-on-event',
+ data: {
+ success: true,
+ },
+ });
+ });
+ });
+
+ describe('cluster messenger', () => {
+ let app;
+ before(done => {
+ app = utils.cluster('apps/messenger');
+ app.coverage(false);
+ // 等 agent 接受消息
+ app.ready(() => setTimeout(done, 1000));
+ });
+ after(() => app.close());
+
+ it('app should accept agent message', () => {
+ app.expect('stdout', /\[app\] agent-to-app agent msg/);
+ });
+ it('app should accept agent assgin pid message', () => {
+ app.expect('stdout', /\[app\] agent-to-app agent msg \d+/);
+ });
+ it('app should accept itself message', () => {
+ app.expect('stdout', /\[app\] app-to-agent app msg/);
+ });
+ it('agent should accept app message', () => {
+ app.expect('stdout', /\[agent\] app-to-agent app msg/);
+ });
+ it('agent should accept itself message', () => {
+ app.expect('stdout', /\[agent\] agent-to-app agent msg/);
+ });
+ it('agent should accept itself assgin pid message', () => {
+ app.expect('stdout', /\[agent\] agent-to-app agent msg \d+/);
+ });
+ });
+
+ describe('sendRandom', () => {
+ let app;
+ before(() => {
+ mm.env('default');
+ mm.consoleLevel('NONE');
+ app = utils.cluster('apps/messenger-random', { workers: 4 });
+ app.coverage(false);
+ return app.ready();
+ });
+ after(() => app.close());
+
+ it('app should accept agent message', done => {
+ setTimeout(() => {
+ const m = app.stdout.match(/\d+=\d+/g);
+ const map = new Map();
+ for (const item of m) {
+ const a = item.split('=');
+ map.set(a[0], a[1]);
+ }
+ map.size.should.equal(4);
+ done();
+ }, 8000);
+ });
+ });
+
+ describe('sendToApp and sentToAgent', () => {
+ let app;
+ before(() => {
+ mm.env('default');
+ mm.consoleLevel('NONE');
+ app = utils.cluster('apps/messenger-app-agent', { workers: 2 });
+ app.coverage(false);
+ return app.ready();
+ });
+ after(() => app.close());
+
+ it('app should accept agent message', done => {
+ setTimeout(() => {
+ count(app.stdout, 'agent2app').should.containEql(2);
+ count(app.stdout, 'app2app').should.containEql(4);
+ count(app.stdout, 'agent2agent').should.containEql(1);
+ count(app.stdout, 'app2agent').should.containEql(2);
+ done();
+ }, 500);
+
+ function count(data, key) {
+ return data.split('\n').filter(line => {
+ return line.indexOf(key) >= 0;
+ }).length;
+ }
+ });
+ });
+});
diff --git a/test/lib/core/router.test.js b/test/lib/core/router.test.js
new file mode 100644
index 0000000000..fe50a594cc
--- /dev/null
+++ b/test/lib/core/router.test.js
@@ -0,0 +1,157 @@
+'use strict';
+
+const request = require('supertest-as-promised');
+const utils = require('../../utils');
+
+describe('test/lib/core/router.test.js', () => {
+ let app;
+ before(() => {
+ app = utils.app('apps/router-app');
+ return app.ready();
+ });
+ after(() => app.close());
+
+ describe('router.resources', () => {
+ describe('normal', () => {
+ it('should GET /posts', () => {
+ return request(app.callback())
+ .get('/posts')
+ .expect(200)
+ .expect('index');
+ });
+
+ it('should GET /posts/new', () => {
+ return request(app.callback())
+ .get('/posts/new')
+ .expect(200)
+ .expect('new');
+ });
+
+ it('should POST /posts', () => {
+ return request(app.callback())
+ .post('/posts')
+ .expect(200)
+ .expect('create');
+ });
+
+ it('should GET /posts/:id', () => {
+ return request(app.callback())
+ .get('/posts/123')
+ .expect(200)
+ .expect('show - 123');
+ });
+
+ it('should GET /posts/:id/edit', () => {
+ return request(app.callback())
+ .get('/posts/123/edit')
+ .expect(200)
+ .expect('edit - 123');
+ });
+
+ it('should PUT /posts/:id', () => {
+ return request(app.callback())
+ .put('/posts/123')
+ .expect(200)
+ .expect('update - 123');
+ });
+
+ it('should DELETE /posts/:id', () => {
+ return request(app.callback())
+ .delete('/posts/123')
+ .expect(200)
+ .expect('destroy - 123');
+ });
+ });
+
+ describe('controller url', () => {
+ it('should GET /members', () => {
+ return request(app.callback())
+ .get('/members')
+ .expect(200)
+ .expect('index');
+ });
+
+ it('should GET /members/index', () => {
+ return request(app.callback())
+ .get('/members/index')
+ .expect(200)
+ .expect('index');
+ });
+
+ it('should GET /members/new', () => {
+ return request(app.callback())
+ .get('/members/new')
+ .expect(200)
+ .expect('new');
+ });
+
+ it('should GET /members/:id', () => {
+ return request(app.callback())
+ .get('/members/1231')
+ .expect(200)
+ .expect('show - 1231');
+ });
+
+ it('should POST /members', () => {
+ return request(app.callback())
+ .post('/members')
+ .expect(404);
+ });
+
+ it('should PUT /members/:id', () => {
+ return request(app.callback())
+ .put('/members/1231')
+ .expect(404);
+ });
+
+ it('should GET /POSTS', () => {
+ return request(app.callback())
+ .get('/POSTS')
+ .expect(404);
+ });
+ });
+ });
+
+ describe('router.url', () => {
+ it('should work', () => {
+ app.router.url('posts').should.equal('/posts');
+ app.router.url('members').should.equal('/members');
+ app.router.url('post', { id: 1 }).should.equal('/posts/1');
+ app.router.url('member', { id: 1 }).should.equal('/members/1');
+ app.router.url('new_post').should.equal('/posts/new');
+ app.router.url('new_member').should.equal('/members/new');
+ app.router.url('edit_post', { id: 1 }).should.equal('/posts/1/edit');
+ });
+
+ it('should work with unknow params', () => {
+ app.router.url('posts', { name: 'foo', page: 2 }).should.equal('/posts?name=foo&page=2');
+ app.router.url('posts', { name: 'foo&?', page: 2 }).should.equal('/posts?name=foo%26%3F&page=2');
+ app.router.url('edit_post', { id: 10, page: 2 }).should.equal('/posts/10/edit?page=2');
+ app.router.url('edit_post', { i: 2, id: 10 }).should.equal('/posts/10/edit?i=2');
+ app.router.url('edit_post', { id: 10, page: 2, tags: [ 'chair', 'develop' ] })
+ .should.equal('/posts/10/edit?page=2&tags=chair&tags=develop');
+ app.router.url('edit_post', { id: [ 10 ], page: [ 2 ], tags: [ 'chair', 'develop' ] })
+ .should.equal('/posts/10/edit?page=2&tags=chair&tags=develop');
+ app.router.url('edit_post', { id: [ 10, 11 ], page: [ 2 ], tags: [ 'chair', 'develop' ] })
+ .should.equal('/posts/10/edit?page=2&tags=chair&tags=develop');
+ });
+
+ it.skip('should have router var in view', () => {
+ return request(app.callback())
+ .get('/locals/router')
+ .expect('posts: /posts');
+ });
+ });
+
+ describe('router.pathFor', () => {
+ it('should work', () => {
+ app.router.pathFor('posts').should.equal('/posts');
+ });
+ });
+
+ describe('router.method', () => {
+ it('router method include HEAD', () => {
+ app.router.methods.should.containEql('HEAD');
+ });
+ });
+});
diff --git a/test/lib/core/singleton.test.js b/test/lib/core/singleton.test.js
new file mode 100644
index 0000000000..5fefc8ee8c
--- /dev/null
+++ b/test/lib/core/singleton.test.js
@@ -0,0 +1,138 @@
+'use strict';
+
+const Singleton = require('../../../lib/core/singleton');
+
+class DataService {
+ constructor(config) {
+ this.config = config;
+ }
+
+ * query() {
+ return {};
+ }
+}
+
+function create(config) {
+ return new DataService(config);
+}
+
+describe('test/lib/core/singleton.test.js', () => {
+ it('should init with client', () => {
+ const app = {
+ config: {
+ dataService: {
+ client: { foo: 'bar' },
+ },
+ },
+ };
+ const name = 'dataService';
+
+ const singleton = new Singleton({
+ name,
+ app,
+ create,
+ });
+ singleton.init();
+ (app.dataService instanceof DataService).should.be.ok;
+ app.dataService.config.foo.should.equal('bar');
+ (typeof app.dataService.createInstance).should.equal('function');
+ });
+
+ it('should init with clients', () => {
+ const app = {
+ config: {
+ dataService: {
+ clients: {
+ first: { foo: 'bar1' },
+ second: { foo: 'bar2' },
+ },
+ },
+ },
+ };
+ const name = 'dataService';
+
+ const singleton = new Singleton({
+ name,
+ app,
+ create,
+ });
+ singleton.init();
+ (app.dataService instanceof Singleton).should.be.ok;
+ app.dataService.get('first').config.foo.should.equal('bar1');
+ app.dataService.get('second').config.foo.should.equal('bar2');
+ (typeof app.dataService.createInstance).should.equal('function');
+ });
+
+ it('should client support default', () => {
+ const app = {
+ config: {
+ dataService: {
+ client: { foo: 'bar' },
+ default: { foo1: 'bar1' },
+ },
+ },
+ };
+ const name = 'dataService';
+
+ const singleton = new Singleton({
+ name,
+ app,
+ create,
+ });
+ singleton.init();
+ (app.dataService instanceof DataService).should.be.ok;
+ app.dataService.config.foo.should.equal('bar');
+ app.dataService.config.foo1.should.equal('bar1');
+ (typeof app.dataService.createInstance).should.equal('function');
+ });
+
+ it('should clients support default', () => {
+ const app = {
+ config: {
+ dataService: {
+ clients: {
+ first: { foo: 'bar1' },
+ second: { },
+ },
+ default: { foo: 'bar' },
+ },
+ },
+ };
+ const name = 'dataService';
+
+ const singleton = new Singleton({
+ name,
+ app,
+ create,
+ });
+ singleton.init();
+ (app.dataService instanceof Singleton).should.be.ok;
+ app.dataService.get('first').config.foo.should.equal('bar1');
+ app.dataService.get('second').config.foo.should.equal('bar');
+ (typeof app.dataService.createInstance).should.equal('function');
+ });
+
+ it('should createInstance without client/clients support default', () => {
+ const app = {
+ config: {
+ dataService: {
+ default: { foo: 'bar' },
+ },
+ },
+ };
+ const name = 'dataService';
+
+ const singleton = new Singleton({
+ name,
+ app,
+ create,
+ });
+ singleton.init();
+ app.dataService.should.equal(singleton);
+ (app.dataService instanceof Singleton).should.be.ok;
+ app.dataService = app.dataService.createInstance({ foo1: 'bar1' });
+ (app.dataService instanceof DataService).should.be.ok;
+ app.dataService.config.foo1.should.equal('bar1');
+ app.dataService.config.foo.should.equal('bar');
+ });
+});
diff --git a/test/lib/core/urllib.test.js b/test/lib/core/urllib.test.js
new file mode 100644
index 0000000000..f029d41603
--- /dev/null
+++ b/test/lib/core/urllib.test.js
@@ -0,0 +1,83 @@
+'use strict';
+
+const mm = require('egg-mock');
+const urllib = require('../../../lib/core/urllib');
+
+describe('test/lib/core/urllib.test.js', () => {
+ let client;
+ const url = 'https://a.alipayobjects.com/aliBridge/1.0.0/aliBridge.min.js';
+
+ before(() => {
+ client = urllib({
+ config: {},
+ });
+ client.on('request', info => {
+ info.args.headers = info.args.headers || {};
+ info.args.headers['mock-traceid'] = 'mock-traceid';
+ info.args.headers['mock-rpcid'] = 'mock-rpcid';
+ });
+ });
+
+ afterEach(mm.restore);
+
+ it('should request ok with log', done => {
+ const args = {
+ dataType: 'text',
+ };
+ client.once('response', info => {
+ info.req.options.headers['mock-traceid'].should.equal('mock-traceid');
+ info.req.options.headers['mock-rpcid'].should.equal('mock-rpcid');
+ done();
+ });
+
+ client.request(url, args);
+ });
+
+ it('should request callback with log', done => {
+ client.once('response', info => {
+ info.req.options.headers['mock-traceid'].should.equal('mock-traceid');
+ info.req.options.headers['mock-rpcid'].should.equal('mock-rpcid');
+ done();
+ });
+
+ client.request(url, () => {});
+ });
+
+ it('should curl ok with log', done => {
+ const args = {
+ dataType: 'text',
+ };
+ client.once('response', info => {
+ info.req.options.headers['mock-traceid'].should.equal('mock-traceid');
+ info.req.options.headers['mock-rpcid'].should.equal('mock-rpcid');
+ done();
+ });
+
+ client.curl(url, args);
+ });
+
+ it('should requestThunk ok with log', function* () {
+ const args = {
+ dataType: 'text',
+ };
+ client.once('response', info => {
+ info.req.options.headers['mock-traceid'].should.equal('mock-traceid');
+ info.req.options.headers['mock-rpcid'].should.equal('mock-rpcid');
+ });
+
+ yield client.requestThunk(url, args);
+ });
+
+ it('should request error with log', done => {
+ mm.http.requestError(/.*/i, null, 'mock res error');
+
+ client.once('response', info => {
+ info.req.options.headers['mock-traceid'].should.equal('mock-traceid');
+ info.req.options.headers['mock-rpcid'].should.equal('mock-rpcid');
+ info.error.message.should.containEql('mock res error');
+ done();
+ });
+
+ client.request(url);
+ });
+});
diff --git a/test/lib/core/util.test.js b/test/lib/core/util.test.js
new file mode 100644
index 0000000000..6e0d7c6175
--- /dev/null
+++ b/test/lib/core/util.test.js
@@ -0,0 +1,23 @@
+'use strict';
+
+const util = require('../../../lib/core/util');
+
+describe('test/lib/core/util.test.js', () => {
+ describe('assign()', () => {
+ it('should assign with object', () => {
+ const a = { a: 0, c: 1 };
+ const b = { a: 1, b: 1 };
+ const c = util.assign(a, b);
+ a.should.equal(c);
+ c.should.eql({ a: 1, b: 1, c: 1 });
+ });
+
+ it('should assign with array', () => {
+ const a = { a: 0, c: 0 };
+ const b = [{ a: 1, b: 0 }, { b: 1, c: 1 }];
+ const c = util.assign(a, b);
+ a.should.equal(c);
+ c.should.eql({ a: 1, b: 1, c: 1 });
+ });
+ });
+});
diff --git a/test/lib/plugins/cors.test.js b/test/lib/plugins/cors.test.js
new file mode 100644
index 0000000000..a43703b025
--- /dev/null
+++ b/test/lib/plugins/cors.test.js
@@ -0,0 +1,36 @@
+'use strict';
+
+const request = require('supertest-as-promised');
+const mm = require('egg-mock');
+const utils = require('../../utils');
+
+describe('test/lib/plugins/cors.test.js', () => {
+ let app;
+ before(() => {
+ app = utils.app('apps/cors');
+ return app.ready();
+ });
+ after(() => app.close());
+
+ afterEach(mm.restore);
+
+ it('should set `Access-Control-Allow-Origin` to request origin header', () => {
+ return request(app.callback())
+ .get('/')
+ .set('Origin', 'http://eggjs.org/foo')
+ .expect('Access-Control-Allow-Origin', 'http://eggjs.org/foo')
+ .expect('Access-Control-Allow-Credentials', 'true')
+ .expect({ foo: 'bar' })
+ .expect(200);
+ });
+
+ it('should set `Access-Control-Allow-Origin` on POST request', () => {
+ app.mockCsrf && app.mockCsrf();
+ return request(app.callback())
+ .post('/')
+ .set('Origin', 'http://eggjs.org')
+ .expect('Access-Control-Allow-Origin', 'http://eggjs.org')
+ .expect('Access-Control-Allow-Credentials', 'true')
+ .expect(200);
+ });
+});
diff --git a/test/lib/plugins/depd.test.js b/test/lib/plugins/depd.test.js
new file mode 100644
index 0000000000..4f309d9f00
--- /dev/null
+++ b/test/lib/plugins/depd.test.js
@@ -0,0 +1,23 @@
+'use strict';
+
+const mm = require('egg-mock');
+const utils = require('../../utils');
+
+describe('test/lib/plugins/depd.test.js', () => {
+ afterEach(mm.restore);
+
+ let app;
+ before(() => {
+ mm(process.env, 'NO_DEPRECATION', '*');
+ app = utils.app('apps/demo');
+ return app.ready();
+ });
+ after(() => app.close());
+
+ it('should use this.locals instead of this.state', () => {
+ const ctx = app.mockContext();
+ ctx.locals.test = 'aaa';
+ ctx.locals.should.eql(ctx.state);
+ ctx.locals.test.should.eql(ctx.state.test);
+ });
+});
diff --git a/test/lib/plugins/development.test.js b/test/lib/plugins/development.test.js
new file mode 100644
index 0000000000..fb631397a3
--- /dev/null
+++ b/test/lib/plugins/development.test.js
@@ -0,0 +1,100 @@
+'use strict';
+
+const fs = require('fs');
+const path = require('path');
+const request = require('supertest');
+const pedding = require('pedding');
+const mm = require('egg-mock');
+const utils = require('../../utils');
+
+describe.skip('test/lib/plugins/development.test.js', () => {
+ afterEach(mm.restore);
+
+ describe('development app', () => {
+ let app;
+ before(() => {
+ mm.env('local');
+ mm(process.env, 'EGG_LOG', 'none');
+ app = utils.app('apps/development');
+ return app.ready();
+ });
+ after(() => app.close());
+
+ it('should log status', done => {
+ done = pedding(3, done);
+ request(app.callback())
+ .get('/foo')
+ .expect(200, done);
+
+ request(app.callback())
+ .get('/not_exist')
+ .expect(404, done);
+
+ setTimeout(() => {
+ const content = fs.readFileSync(
+ utils.getFilepath('apps/development/logs/development/development-web.log'), 'utf8');
+ content.should.containEql('GET /foo] status 200');
+ content.should.containEql('GET /not_exist] status 404');
+ done();
+ }, 1000);
+ });
+
+ it('should ignore assets', done => {
+ done = pedding(4, done);
+ mm(app.logger, 'info', msg => {
+ if (msg.match(/status /)) {
+ throw new Error('should not log status');
+ }
+ });
+
+ request(app.callback())
+ .get('/foo.js')
+ .expect(200)
+ .end(done);
+
+ request(app.callback())
+ .get('/public/hello')
+ .expect(404, done);
+
+ request(app.callback())
+ .get('/assets/hello')
+ .expect(404, done);
+
+ request(app.callback())
+ .get('/__koa_mock_scene_toolbox/hello')
+ .expect(404, done);
+ });
+ });
+
+ describe('reload workers', () => {
+ let app;
+ const baseDir = utils.getFilepath('apps/reload-worker');
+ const filepath = path.join(baseDir, 'app/controller/home.js');
+ const body = fs.readFileSync(filepath);
+
+ before(done => {
+ mm.env('local');
+ mm(process.env, 'EGG_LOG', 'none');
+ app = utils.cluster('apps/reload-worker');
+ app.debug();
+ app.ready(done);
+ });
+ after(() => {
+ fs.writeFileSync(filepath, body);
+ app.close();
+ });
+
+ it('should reload when file changed', done => {
+ fs.writeFileSync(filepath, 'module.exports = function*() { this.body = \'change\'; };');
+ // 等待 app worker 重启
+ setTimeout(() => {
+ request(app.callback())
+ .get('/')
+ .expect('change', err => {
+ app.expect('stdout', /App Worker#2:\d+ started/);
+ done(err);
+ });
+ }, 10000);
+ });
+ });
+});
diff --git a/test/lib/plugins/empty.txt b/test/lib/plugins/empty.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/test/lib/plugins/i18n.test.js b/test/lib/plugins/i18n.test.js
new file mode 100644
index 0000000000..93f00d087b
--- /dev/null
+++ b/test/lib/plugins/i18n.test.js
@@ -0,0 +1,53 @@
+'use strict';
+
+const request = require('supertest-as-promised');
+const utils = require('../../utils');
+
+describe.skip('test/lib/plugins/i18n.test.js', () => {
+ let app;
+ before(() => {
+ app = utils.app('apps/i18n');
+ return app.ready();
+ });
+ after(() => app.close());
+
+ describe('ctx.__(key, value)', () => {
+ it('should return locale de', () => {
+ return request(app.callback())
+ .get('/message?locale=de')
+ .expect(200)
+ .expect('Set-Cookie', /locale=de; path=\/; expires=[^;]+ GMT/)
+ .expect({
+ message: 'Hallo fengmk2, wie geht es dir heute? Wie war dein 18.',
+ empty: '',
+ notexists_key: 'key not exists',
+ empty_string: '',
+ novalue: 'key %s ok',
+ arguments3: '1 2 3',
+ arguments4: '1 2 3 4',
+ arguments5: '1 2 3 4 5',
+ arguments6: '1 2 3 4 5. 6',
+ values: 'foo bar foo bar {2} {100}',
+ });
+ });
+ });
+
+ describe('view render with __(key, value)', () => {
+ it('should render with default locale: en-US', () => {
+ return request(app.callback())
+ .get('/')
+ .expect(200)
+ .expect('Set-Cookie', /locale=en-us; path=\/; expires=[^;]+ GMT/)
+ .expect('Email: \nHello fengmk2, how are you today?\nfoo bar\n');
+ });
+
+ it('should render with query locale: zh_CN', () => {
+ return request(app.callback())
+ .get('/?locale=zh_CN')
+ .set('Host', 'foo.example.com')
+ .expect(200)
+ .expect('Set-Cookie', /locale=zh-cn; path=\/; expires=[^;]+ GMT/)
+ .expect('邮箱: \nfengmk2,今天过得如何?\nfoo bar\n');
+ });
+ });
+});
diff --git a/test/lib/plugins/logrotater.test.js b/test/lib/plugins/logrotater.test.js
new file mode 100644
index 0000000000..8a479ae555
--- /dev/null
+++ b/test/lib/plugins/logrotater.test.js
@@ -0,0 +1,34 @@
+'use strict';
+
+const path = require('path');
+const mm = require('egg-mock');
+const glob = require('glob');
+const utils = require('../../utils');
+
+describe.skip('test/lib/plugins/logrotater.test.js', () => {
+ let app;
+ before(() => {
+ mm.env('unittest');
+ app = utils.cluster('apps/logrotater-app', { coverage: true });
+ return app.ready();
+ });
+
+ afterEach(mm.restore);
+
+ after(() => app.close());
+
+ it('should rotate log file default', done => {
+ app.process.send({
+ to: 'agent',
+ action: 'test-reload-logger',
+ });
+ setTimeout(() => {
+ const files = glob.sync(path.join(__dirname, '../../fixtures/apps/logrotater-app/logs/logrotater-app/*.log.*'));
+ files.length.should.above(0);
+ files.forEach(file => {
+ file.should.match(/log.\d{4}-\d{2}-\d{2}$/);
+ });
+ done();
+ }, 1000);
+ });
+});
diff --git a/test/lib/plugins/multipart.test.js b/test/lib/plugins/multipart.test.js
new file mode 100644
index 0000000000..56b3d30e9f
--- /dev/null
+++ b/test/lib/plugins/multipart.test.js
@@ -0,0 +1,53 @@
+'use strict';
+
+const request = require('supertest');
+const should = require('should');
+const formstream = require('formstream');
+const urllib = require('urllib');
+const utils = require('../../utils');
+
+describe.skip('test/lib/plugins/multipart.test.js', () => {
+ let app;
+ let csrfToken;
+ let cookies;
+ let host;
+ let server;
+ before(done => {
+ app = utils.app('apps/multipart');
+ server = app.listen();
+ request(server)
+ .get('/')
+ .expect(200, (err, res) => {
+ csrfToken = res.headers['x-csrf'];
+ cookies = res.headers['set-cookie'].join(';');
+ host = `http://127.0.0.1:${server.address().port}`;
+ done(err);
+ });
+ });
+
+ after(() => {
+ server.close();
+ });
+
+ it('should upload with csrf', done => {
+ const form = formstream();
+ // form.file('file', filepath, filename);
+ form.file('file', __filename);
+ // other form fields
+ form.field('foo', 'fengmk2').field('love', 'chair');
+
+ const headers = form.headers();
+ headers.Cookie = cookies;
+ urllib.request(`${host}/upload?_csrf=${csrfToken}`, {
+ method: 'POST',
+ headers,
+ stream: form,
+ }, (err, body, res) => {
+ should.not.exist(err);
+ res.statusCode.should.equal(200);
+ const data = JSON.parse(body);
+ data.filename.should.equal('multipart.test.js');
+ done();
+ });
+ });
+});
diff --git a/test/lib/plugins/onerror.test.js b/test/lib/plugins/onerror.test.js
new file mode 100644
index 0000000000..ffee4a988e
--- /dev/null
+++ b/test/lib/plugins/onerror.test.js
@@ -0,0 +1,27 @@
+'use strict';
+
+const request = require('supertest-as-promised');
+const mm = require('egg-mock');
+const utils = require('../../utils');
+
+describe('test/lib/plugins/onerror.test.js', () => {
+ let app;
+ before(() => {
+ mm.env('local');
+ mm(process.env, 'EGG_LOG', 'none');
+ app = utils.app('apps/onerror');
+ return app.ready();
+ });
+
+ after(() => app.close());
+
+ afterEach(mm.restore);
+
+ it('should redirect to error page', () => {
+ mm(app.config, 'env', 'test');
+ return request(app.callback())
+ .get('/?status=500')
+ .expect('Location', 'http://eggjs.org/500?real_status=500')
+ .expect(302);
+ });
+});
diff --git a/test/lib/plugins/rest.test.js b/test/lib/plugins/rest.test.js
new file mode 100644
index 0000000000..b74a95fc3f
--- /dev/null
+++ b/test/lib/plugins/rest.test.js
@@ -0,0 +1,48 @@
+'use strict';
+
+const request = require('supertest');
+const mm = require('egg-mock');
+const utils = require('../../utils');
+
+describe.skip('test/lib/plugins/rest.test.js', () => {
+ let app;
+ before(() => {
+ app = utils.app('apps/rest');
+ return app.ready();
+ });
+ after(() => app.close());
+
+ afterEach(mm.restore);
+
+ it('should GET /api/{objects} => app/apis/{objects}.js:index()', () => {
+ return request(app.callback())
+ .get('/api/users')
+ .expect({
+ data: [
+ {
+ id: 1,
+ name: 'suqian.yf',
+ age: 18,
+ },
+ {
+ id: 2,
+ name: 'name2',
+ age: 30,
+ },
+ ],
+ })
+ .expect('Content-Type', 'application/json; charset=utf-8')
+ .expect(200);
+ });
+
+ it('should GET /api/categories', () => {
+ return request(app.callback())
+ .get('/api/categories')
+ .expect({
+ data: [
+ { name: 'c1' },
+ { name: 'c2' },
+ ],
+ });
+ });
+});
diff --git a/test/lib/plugins/schedule.test.js b/test/lib/plugins/schedule.test.js
new file mode 100644
index 0000000000..d28bef3c87
--- /dev/null
+++ b/test/lib/plugins/schedule.test.js
@@ -0,0 +1,34 @@
+'use strict';
+
+const path = require('path');
+const fs = require('fs');
+const utils = require('../../utils');
+
+describe.skip('test/lib/plugins/schedule.test.js', () => {
+ it('should schedule work', function* () {
+ const app = utils.cluster('apps/schedule', {
+ workers: 4,
+ });
+ yield app.ready();
+ yield sleep(5000);
+ app.close();
+ const log = getLogContent('schedule');
+ // 由于 app.ready() 在 agent.ready 之后,ci 可能要耗太多时间导致多执行一次
+ contains(log, 'cron').should.within(1, 2);
+ });
+});
+
+function sleep(time) {
+ return new Promise(resolve => {
+ setTimeout(resolve, time);
+ });
+}
+
+function getLogContent(name) {
+ const logPath = path.join(__dirname, '../../fixtures/apps', name, 'logs', name, `${name}-web.log`);
+ return fs.readFileSync(logPath, 'utf8');
+}
+
+function contains(content, match) {
+ return content.split('\n').filter(line => line.indexOf(match) >= 0).length;
+}
diff --git a/test/lib/plugins/session.test.js b/test/lib/plugins/session.test.js
new file mode 100644
index 0000000000..7fb28b7ac4
--- /dev/null
+++ b/test/lib/plugins/session.test.js
@@ -0,0 +1,75 @@
+'use strict';
+
+const request = require('supertest');
+const should = require('should');
+const mm = require('egg-mock');
+const utils = require('../../utils');
+
+describe('test/lib/plugins/session.test.js', () => {
+ let app;
+ before(() => {
+ app = utils.app('apps/koa-session');
+ return app.ready();
+ });
+ after(() => app.close());
+ afterEach(mm.restore);
+
+ it('should work when userId change', done => {
+ app.mockContext({
+ userId: 's1',
+ });
+ request(app.callback())
+ .get('/?uid=1')
+ .expect({
+ userId: 's1',
+ sessionUid: '1',
+ uid: '1',
+ })
+ .expect(200, (err, res) => {
+ if (err) return done(err);
+ should.exist(res.headers['set-cookie']);
+ const cookie = res.headers['set-cookie'].join(';');
+ cookie.should.match(/EGG_SESS=[\w\-]+/);
+
+ // userId 不变,还是读取到上次的 session 值
+ app.mockContext({
+ userId: 's1',
+ });
+ request(app.callback())
+ .get('/?uid=2&userId=s1')
+ .set('Cookie', cookie)
+ .expect({
+ userId: 's1',
+ sessionUid: '1',
+ uid: '2',
+ })
+ .expect(200, (err, res) => {
+ if (err) return done(err);
+ should.not.exist(res.headers['set-cookie']);
+
+ // userId change, session still not change
+ app.mockContext({
+ userId: 's2',
+ });
+ request(app.callback())
+ .get('/?uid=2')
+ .set('Cookie', cookie)
+ .expect({
+ userId: 's2',
+ sessionUid: '1',
+ uid: '2',
+ })
+ .expect(res => {
+ should.not.exist(res.headers['set-cookie']);
+ })
+ .expect(200, err => {
+ if (err) return done(err);
+ request(app.callback())
+ .get('/clear')
+ .set('Cookie', cookie)
+ .expect('set-cookie', /EGG_SESS=;/, done);
+ });
+ });
+ });
+ });
+});
diff --git a/test/lib/plugins/static.test.js b/test/lib/plugins/static.test.js
new file mode 100644
index 0000000000..b5acd6be26
--- /dev/null
+++ b/test/lib/plugins/static.test.js
@@ -0,0 +1,19 @@
+'use strict';
+
+const request = require('supertest-as-promised');
+const utils = require('../../utils');
+
+describe('test/lib/plugins/static.test.js', () => {
+ let app;
+ before(() => {
+ app = utils.app('apps/static-server');
+ return app.ready();
+ });
+
+ it('should get exists js file', () => {
+ return request(app.callback())
+ .get('/public/foo.js')
+ .expect('alert(\'bar\');\n')
+ .expect(200);
+ });
+});
diff --git a/test/lib/plugins/userrole.test.js b/test/lib/plugins/userrole.test.js
new file mode 100644
index 0000000000..db90d985ab
--- /dev/null
+++ b/test/lib/plugins/userrole.test.js
@@ -0,0 +1,27 @@
+'use strict';
+
+const request = require('supertest-as-promised');
+const utils = require('../../utils');
+
+describe.skip('test/lib/plugins/userrole.test.js', () => {
+ let app;
+ before(() => {
+ app = utils.app('apps/userrole');
+ return app.ready();
+ });
+ after(() => app.close());
+
+ it('should GET /user 200 when user login', () => {
+ return request(app.callback())
+ .get('/user?name=user2')
+ .expect('hello user2')
+ .expect(200);
+ });
+
+ it('should GET /admin 200 when admin login', () => {
+ return request(app.callback())
+ .get('/admin?name=fengmk2')
+ .expect('hello admin')
+ .expect(200);
+ });
+});
diff --git a/test/lib/plugins/userservice.test.js b/test/lib/plugins/userservice.test.js
new file mode 100644
index 0000000000..80c69eb4da
--- /dev/null
+++ b/test/lib/plugins/userservice.test.js
@@ -0,0 +1,26 @@
+'use strict';
+
+const request = require('supertest-as-promised');
+const utils = require('../../utils');
+
+describe.skip('test/lib/plugins/userservice.test.js', () => {
+ let app;
+ before(() => {
+ app = utils.app('apps/userservice');
+ return app.ready();
+ });
+ after(() => app.close());
+
+ it('should get user and userId', () => {
+ return request(app.callback())
+ .get('/?uid=456&name=fengmk2')
+ .expect({
+ userId: '456',
+ user: {
+ uid: '456',
+ name: 'fengmk2',
+ },
+ })
+ .expect(200);
+ });
+});
diff --git a/test/lib/plugins/validate.test.js b/test/lib/plugins/validate.test.js
new file mode 100644
index 0000000000..c359f9f26b
--- /dev/null
+++ b/test/lib/plugins/validate.test.js
@@ -0,0 +1,27 @@
+'use strict';
+
+const request = require('supertest-as-promised');
+const utils = require('../../utils');
+
+describe('test/lib/plugins/validate.test.js', () => {
+ let app;
+ before(() => {
+ app = utils.app('apps/validate_form');
+ return app.ready();
+ });
+ after(() => app.close());
+
+ it('should return invalid_param when body empty', () => {
+ return request(app.callback())
+ .get('/users.json')
+ .expect({
+ code: 'invalid_param',
+ message: 'Validation Failed',
+ errors: [
+ { field: 'username', code: 'missing_field', message: 'required' },
+ { field: 'password', code: 'missing_field', message: 'required' },
+ ],
+ })
+ .expect(422);
+ });
+});
diff --git a/test/lib/plugins/view/render.test.js b/test/lib/plugins/view/render.test.js
new file mode 100644
index 0000000000..478d950ab9
--- /dev/null
+++ b/test/lib/plugins/view/render.test.js
@@ -0,0 +1,44 @@
+'use strict';
+
+const request = require('supertest');
+const mm = require('egg-mock');
+const utils = require('../../../utils');
+
+describe.skip('test/lib/plugins/view/render.test.js', () => {
+ let app;
+ before(function() {
+ app = utils.app('apps/view-render');
+ app.locals = {
+ copyright: '2014 @ mk2
',
+ };
+ });
+
+ afterEach(mm.restore);
+
+ it('should render with options', function(done) {
+ request(app.callback())
+ .get('/')
+ .expect(200)
+ .expect(`Hi, mk・2\ntest-app-helper: test-bar@${app.config.baseDir}\nraw: dar
\n2014 @ mk2 <br>\n`, done);
+ });
+
+ it('should render have helper instance', function(done) {
+ request(app.callback())
+ .get('/')
+ .expect(200, done);
+ });
+
+ it('should render with empty', function(done) {
+ request(app.callback())
+ .get('/empty')
+ .expect(200)
+ .expect(`Hi, \ntest-app-helper: test-bar@${app.config.baseDir}\nraw: dar
\n2014 @ mk2 <br>\n`, done);
+ });
+
+ it('should render template string', function(done) {
+ request(app.callback())
+ .get('/string')
+ .expect(200)
+ .expect('templateString', done);
+ });
+});
diff --git a/test/lib/plugins/watcher.test.js b/test/lib/plugins/watcher.test.js
new file mode 100644
index 0000000000..3ee2062f47
--- /dev/null
+++ b/test/lib/plugins/watcher.test.js
@@ -0,0 +1,167 @@
+'use strict';
+
+require('should');
+const mm = require('egg-mock');
+const fs = require('fs');
+const request = require('supertest');
+const utils = require('../../utils');
+const file_path1 = utils.getFilepath('apps/watcher-development-app/tmp.txt');
+const file_path2 = utils.getFilepath('apps/watcher-development-app/tmp/tmp.txt');
+const file_path1_agent = utils.getFilepath('apps/watcher-development-app/tmp-agent.txt');
+
+describe.skip('test/lib/plugins/watcher.test.js', () => {
+ describe('default', () => {
+ let app;
+ beforeEach(() => {
+ app = utils.cluster('apps/watcher-development-app');
+ return app.ready();
+ });
+
+ afterEach(() => {
+ app.close();
+ mm.restore();
+ });
+
+ it('should app watcher work', done => {
+ const server = app.callback();
+ let count = 0;
+ request(server)
+ .get('/app-watch')
+ .expect(200)
+ .expect('app watch success')
+ .end(function(err) {
+ if (err) {
+ return done(err);
+ }
+ fs.writeFileSync(file_path1, 'aaa');
+ setTimeout(function() {
+ request(server)
+ .get('/app-msg')
+ .expect(200)
+ .expect(function(res) {
+ const lastCount = count;
+ count = parseInt(res.text);
+ count.should.greaterThan(lastCount);
+ })
+ .end(function(err) {
+ if (err) {
+ return done(err);
+ }
+ fs.writeFileSync(file_path2, 'aaa');
+ setTimeout(function() {
+ request(server)
+ .get('/app-msg')
+ .expect(200)
+ .expect(function(res) {
+ const lastCount = count;
+ count = parseInt(res.text);
+ count.should.greaterThan(lastCount);
+ })
+ .end(function(err) {
+ if (err) {
+ return done(err);
+ }
+ request(server)
+ .get('/app-unwatch')
+ .expect(200)
+ .expect('app unwatch success')
+ .end(function(err) {
+ if (err) return done(err);
+ setTimeout(() => {
+ fs.writeFileSync(file_path2, 'aaa');
+ fs.writeFileSync(file_path1, 'aaa');
+ setTimeout(function() {
+ request(server)
+ .get('/app-msg')
+ .expect(200)
+ .expect(function(res) {
+ const lastCount = count;
+ count = parseInt(res.text);
+ count.should.equal(lastCount);
+ }) // unchanged
+ .end(done);
+ }, 100);
+ }, 100);
+ });
+
+ });
+ }, 100);
+ });
+ }, 100);
+ });
+ });
+
+ it('should agent watcher work', done => {
+ let count = 0;
+ request(app.callback())
+ .get('/agent-watch')
+ .expect(200)
+ .expect('agent watch success')
+ .end(err => {
+ if (err) {
+ return done(err);
+ }
+ fs.writeFileSync(file_path1_agent, 'bbb');
+ setTimeout(() => {
+ request(app.callback())
+ .get('/agent-msg')
+ .expect(200)
+ .expect(res => {
+ const lastCount = count;
+ count = parseInt(res.text);
+ count.should.greaterThan(lastCount);
+ })
+ .end(err => {
+ if (err) {
+ return done(err);
+ }
+ request(app.callback())
+ .get('/agent-unwatch')
+ .expect(200)
+ .expect('agent unwatch success')
+ .end(err => {
+ if (err) {
+ return done(err);
+ }
+
+ setTimeout(() => {
+ fs.writeFileSync(file_path1_agent, 'bbb');
+ setTimeout(() => {
+ request(app.callback())
+ .get('/agent-msg')
+ .expect(200)
+ .expect(res => {
+ const lastCount = count;
+ count = parseInt(res.text);
+ count.should.equal(lastCount);
+ })
+ .end(done);
+ }, 100);
+ }, 100);
+ });
+ });
+ }, 100);
+ });
+ });
+
+ });
+
+ describe('config.watcher.type is default', () => {
+ let app;
+ before(() => {
+ app = utils.cluster('apps/watcher-type-default');
+ return app.ready();
+ });
+
+ after(() => app.close());
+
+ it('should warn user', done => {
+ setTimeout(() => {
+ const content = fs.readFileSync(
+ utils.getFilepath('apps/watcher-type-default/logs/watcher-type-default/egg-agent.log')).toString();
+ content.should.containEql('defaultEventSource watcher will NOT take effect');
+ done();
+ }, 1000);
+ });
+ });
+});
diff --git a/test/mocha.opts b/test/mocha.opts
new file mode 100644
index 0000000000..e3052016d3
--- /dev/null
+++ b/test/mocha.opts
@@ -0,0 +1 @@
+--require should
diff --git a/test/utils.js b/test/utils.js
new file mode 100644
index 0000000000..a70ec6c989
--- /dev/null
+++ b/test/utils.js
@@ -0,0 +1,87 @@
+'use strict';
+
+const fs = require('fs');
+const path = require('path');
+const mm = require('egg-mock');
+const fixtures = path.join(__dirname, 'fixtures');
+const eggPath = path.join(__dirname, '..');
+
+exports.app = (name, options) => {
+ options = formatOptions(name, options);
+ return mm.app(options);
+};
+
+/**
+ * start app with cluster mode
+ *
+ * @param {String} name - cluster name.
+ * @param {Object} [options] - optional
+ * @return {App} app - Application object.
+ */
+exports.cluster = (name, options) => {
+ options = formatOptions(name, options);
+ return mm.cluster(options);
+};
+
+exports.getFilepath = name => {
+ return path.join(fixtures, name);
+};
+
+exports.getJSON = name => {
+ return JSON.parse(fs.readFileSync(exports.getFilepath(name)));
+};
+
+// context helper, come from https://github.com/koajs/koa/blob/master/test/context.js
+exports.createContext = (ctx, cb) => {
+ const app = exports.app('apps/demo');
+ return new Promise(function(resolve, reject) {
+ app.ready(() => {
+ const mockCtx = app.mockContext(ctx);
+ if (cb) cb(mockCtx);
+ resolve(mockCtx);
+ });
+
+ app.on('error', err => {
+ reject(err);
+ });
+ });
+};
+
+exports.createRequest = function(ctx, cb) {
+ return new Promise(function(resolve, reject) {
+ exports.createContext(ctx).then(mockCtx => {
+ const req = mockCtx.request;
+ if (cb) cb(req);
+ resolve(req);
+ }, err => {
+ reject(err);
+ });
+ });
+};
+
+exports.createResponse = function(ctx, cb) {
+ return new Promise(function(resolve, reject) {
+ exports.createContext(ctx).then(mockCtx => {
+ const res = mockCtx.response;
+ if (cb) cb(res);
+ resolve(res);
+ }, err => {
+ reject(err);
+ });
+ });
+};
+
+function formatOptions(name, options) {
+ let baseDir;
+ if (typeof name === 'string') {
+ baseDir = name;
+ } else {
+ // name is options
+ options = name;
+ }
+ return Object.assign({}, {
+ baseDir,
+ customEgg: eggPath,
+ cache: false,
+ }, options);
+}