Skip to content

Commit 9fcc87e

Browse files
committed
fix hooks and write tests
1 parent 83d53da commit 9fcc87e

File tree

7 files changed

+261
-5
lines changed

7 files changed

+261
-5
lines changed

hookslib/CloutApiRoute.js

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
* Copyright(c) 2018 Muhammad Dadu
44
* MIT Licensed
55
*/
6+
const debug = require('debug')('clout-js:api')
67
const { safePromisifyCallFn } = require('../lib/utils');
78
const types = require('./apiType');
89

@@ -64,13 +65,17 @@ class CloutApiRoute {
6465
* handles router method in a promise
6566
* @param {*} fn RouterCallback
6667
*/
67-
handlePromisePostTriggers(fn) {
68-
const { isPublicFacing } = this;
68+
handlePromisePostTriggers(fn, _isPublicFacing) {
69+
const { isPublicFacing } = typeof _isPublicFacing !== 'undefined' ? _isPublicFacing : this;
6970
return function postPromiseHandle(req, resp, next, ...args) {
7071
safePromisifyCallFn(fn, this, [req, resp, null, ...args])
7172
.then((data) => {
73+
const headerSent = !!resp._header;
74+
debug(req.url, 'isPublicFacing', isPublicFacing)
75+
debug(req.url, 'headerSent', headerSent)
76+
debug('data', data);
7277
if (isPublicFacing) {
73-
if (!resp.headerSent) {
78+
if (!headerSent) {
7479
return resp.ok(data);
7580
}
7681
} else {

hookslib/apiType/api.js

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1+
const debug = require('debug')('clout-js:api');
2+
13
module.exports = {
24
name: 'api',
35
fn(fn) {
46
const apiPath = this.path && `${this.path}.:acceptType?`;
57

68
const attachHook = (method, hookFn) => this.router[method](
79
apiPath,
8-
this.handlePromisePostTriggers(hookFn),
10+
this.handlePromisePostTriggers(hookFn, false),
911
);
1012

1113
this.methods.forEach((method) => {
@@ -16,7 +18,19 @@ module.exports = {
1618
});
1719

1820
// attach hooks
19-
this.hooks.map(hookFn => attachHook(method, hookFn));
21+
this.hooks
22+
.filter(hookFn => {
23+
const isFunction = typeof hookFn === 'function';
24+
if (!isFunction) {
25+
console.error({apiPath, isFunction}, 'hook is not a function');;
26+
}
27+
28+
return isFunction;
29+
})
30+
.map(hookFn => {
31+
debug({method, hookFn}, 'attaching hookFn for method');
32+
return attachHook(method, hookFn);
33+
});
2034

2135
this.router[method](apiPath, this.handlePromisePostTriggers(fn));
2236
});

test/e2e_user_auth_test.js

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
const should = require('should');
2+
const testLib = require('./lib');
3+
const request = testLib.request;
4+
const {merge} = require('lodash');
5+
6+
const USER_1 = {
7+
name: 'Dadu',
8+
email: 'dadu@test.com',
9+
};
10+
11+
const USER_2 = {
12+
name: 'Marcos',
13+
email: 'marcos@test.com',
14+
};
15+
16+
describe('e2e User Auth Tests', function () {
17+
let clout;
18+
19+
before(() => {
20+
process.env.PORT = 8420;
21+
process.env.NODE_ENV = 'test';
22+
clout = testLib.createInstance();
23+
});
24+
25+
it('start server', (done) => {
26+
clout.start();
27+
clout.on('started', () => {
28+
let server = clout.server['http'];
29+
if (server) {
30+
let port = server.address().port;
31+
serverAddress = `http://localhost:${port}`;
32+
}
33+
done();
34+
});
35+
});
36+
37+
describe('/user - non authenticated', () => {
38+
it('should give 403 /user', async () => {
39+
const response = await request({ uri: `/api/user`, json: true });
40+
should(response.statusCode).be.equal(401);
41+
});
42+
43+
it('should add 1 user to /user', async () => {
44+
const response = await request({
45+
method: 'put',
46+
uri: `/api/user`,
47+
body: USER_1,
48+
json: true
49+
});
50+
51+
should(response.body).be.deepEqual(merge({ id: 1 }, USER_1));
52+
});
53+
});
54+
55+
56+
57+
describe('/user - example api', () => {
58+
const headers = {
59+
'X-Auth-Token': 'test-auth-token',
60+
}
61+
62+
it('should return 1 item from /user', async () => {
63+
const response = await request({ uri: `/api/user`, json: true , headers});
64+
65+
should(response.statusCode).be.equal(200);
66+
should(response.body.length).be.equal(1);
67+
});
68+
69+
it('should add 1 more item to /user', async () => {
70+
const response = await request({
71+
method: 'put',
72+
uri: `/api/user`,
73+
body: USER_2,
74+
json: true,
75+
headers,
76+
});
77+
78+
should(response.body).be.deepEqual(merge({ id: 2 }, USER_2));
79+
});
80+
81+
it('should return 2 items from /user', async () => {
82+
const response = await request({ uri: `/api/user`, json: true, headers });
83+
84+
should(response.statusCode).be.equal(200);
85+
should(response.body.length).be.equal(2);
86+
});
87+
88+
it('should delete an item from /user/2 & /user/3', async () => {
89+
await request({ uri: `/api/user/1`, method: 'delete', json: true, headers });
90+
await request({ uri: `/api/user/2`, method: 'delete', json: true, headers });
91+
92+
const response = await request({ uri: `/api/user`, json: true, headers });
93+
94+
should(response.statusCode).be.equal(200);
95+
should(response.body.length).be.equal(0);
96+
});
97+
});
98+
99+
after('stop server', (done) => {
100+
clout.on('stopped', () => done());
101+
clout.stop();
102+
});
103+
});
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/**
2+
* User API Example
3+
* Define API settings and helpers
4+
*/
5+
6+
module.exports = {
7+
userId: {
8+
type: 'param',
9+
param: 'userId',
10+
result: 'user',
11+
description: 'define userId',
12+
fn: (req, resp, next, userId) => {
13+
return req.models.user.getById(userId)
14+
.catch((err) => {
15+
req.logger.error(err);
16+
throw err;
17+
});
18+
}
19+
}
20+
};
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/**
2+
* Simple API Example
3+
*/
4+
const {isAuthenticated} = require('../../middleware/authentication');
5+
6+
module.exports = {
7+
list: {
8+
path: '/user',
9+
method: 'get',
10+
description: `
11+
API Example to demonstrate simple key value storage using a user model
12+
`,
13+
hooks: [isAuthenticated],
14+
fn: (req, resp) => {
15+
const UserDB = req.models.user;
16+
17+
return UserDB.list();
18+
}
19+
},
20+
create: {
21+
path: '/user',
22+
method: 'put',
23+
params: {
24+
name: 'string',
25+
email: 'string'
26+
},
27+
description: `
28+
API Example to demonstrate simple key value storage using a user model
29+
`,
30+
fn: (req, resp) => {
31+
const UserDB = req.models.user;
32+
const params = req.body;
33+
34+
return UserDB.add(params);
35+
}
36+
},
37+
getById: {
38+
path: '/user/:userId',
39+
method: 'get',
40+
description: `
41+
API Example to demonstrate advanced routing
42+
`,
43+
hooks: [isAuthenticated],
44+
fn: (req, resp) => {
45+
resp.ok(req.param('user'));
46+
}
47+
},
48+
delete: {
49+
path: '/user/:userId',
50+
method: 'delete',
51+
description: `
52+
API Example to demonstrate advanced routing
53+
`,
54+
hooks: [isAuthenticated],
55+
fn: (req, resp) => {
56+
req.param('user').delete()
57+
.then(() => resp.ok({}));
58+
}
59+
}
60+
};
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
const authToken = 'test-auth-token';
2+
3+
module.exports = {
4+
isAuthenticated: (req, resp, next) => {
5+
const isValidToken = req.header('X-Auth-Token') === authToken;
6+
7+
if (!isValidToken) {
8+
return resp.unauthorized();
9+
}
10+
11+
return next();
12+
}
13+
};
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
const simpleModel = require('./simple');
2+
const userDB = {};
3+
4+
class User {
5+
constructor(user) {
6+
Object.assign(this, user);
7+
}
8+
9+
async save() {
10+
const instance = !this.id
11+
? await simpleModel.create(this)
12+
: await simpleModel.getById(this.id);
13+
14+
Object.assign(this, instance);
15+
userDB[this.id] = instance;
16+
17+
return Promise.resolve(instance);
18+
}
19+
20+
delete() {
21+
simpleModel.deleteById(this.id);
22+
userDB[this.id] = null;
23+
delete userDB[this.id];
24+
return Promise.resolve();
25+
}
26+
}
27+
28+
module.exports = {
29+
list() {
30+
let list = Object.keys(userDB).map((id) => Object.assign(userDB[id], { id }));
31+
32+
return Promise.resolve(list);
33+
},
34+
getById(userId) {
35+
return simpleModel.getById(userId)
36+
.then((info) => new User(info));
37+
},
38+
add(info) {
39+
return (new User(info)).save();
40+
}
41+
};

0 commit comments

Comments
 (0)