diff --git a/README.md b/README.md index 3fe64ec..35a89a8 100644 --- a/README.md +++ b/README.md @@ -87,7 +87,7 @@ Following variables can be configured: - after you did database restore above, - execute `test_files/sql/email.sql` on heroku postgres. - $ cat test_files/sql/email.sql | heroku pg:psql + $ cat test_files/sql/email.sql | heroku pg:psql ---> Connecting to DATABASE_URL SET SET @@ -105,6 +105,44 @@ Following variables can be configured: GRANT +## Test and Coverage + - Follow *Database restore* to input test data + - Make sure `test_files/sql/email.sql` has been executed + - Make sure `test_files/sql/demo.sql` has been executed + - Adjust some test data in `test/test-helper.js` + - EX `emailIdDelivered`, this is email id which has delivered successful. + If We use email id just sent, we may get `404 Not Found` error + or not delivered response, both make test fail. + So we need input a successful delivered email id manually. + - Run test `npm run test` + - Report Coverage + - `npm install -g istanbul` + - `npm run coverage` + + ``` + 2016-09-13T20:33:46.340Z - debug: undefined + POST /api/v1/reset 200 162.002 ms - - + ✓ reset (163ms) + + + 84 passing (9s) + + ============================================================================= + Writing coverage object [/home/stevenfrog/temp/TC-StartPack-Nodejs-test-coverage/Topcoder-StarterPac + k_Node-Backend/coverage/coverage.json] + Writing coverage reports at [/home/stevenfrog/temp/TC-StartPack-Nodejs-test-coverage/Topcoder-Starte + rPack_Node-Backend/coverage] + ============================================================================= + + =============================== Coverage summary =============================== + Statements : 92.44% ( 1369/1481 ) + Branches : 64.62% ( 221/342 ) + Functions : 100% ( 134/134 ) + Lines : 92.35% ( 1352/1464 ) + ================================================================================ + ``` + + ## Setup postman - Load postman collection: @@ -175,7 +213,7 @@ Following variables can be configured: ## Additional functionality -- `GET /emails/stats` endpoint for tracking Mailgun usage for past 1 month. +- `GET /emails/stats` endpoint for tracking Mailgun usage for past 1 month. ## Module system for future developers @@ -185,7 +223,7 @@ Following variables can be configured: - a module should have `routes.js` on top, `src/modules//routes.js`. declare routes to controllers in this file. - use relative imports, e.g ) `const service = require('../services/fooService');` to load services from controllers within a module, or import between services within a module. - currently existing modules: `crud`, `sample`, `mail` -- how the modules are loaded: `src/app-routes.js` will glob `src/modules/*/routes.js` and load them. +- how the modules are loaded: `src/app-routes.js` will glob `src/modules/*/routes.js` and load them. - remove a module from application by deleting a module directory. ## Authentication & Authorization diff --git a/app.js b/app.js index 673afcd..0ecbd8c 100644 --- a/app.js +++ b/app.js @@ -67,3 +67,4 @@ const port = config.port; app.listen(port, '0.0.0.0'); logger.info('Express server listening on port %d in %s mode', port, process.env.NODE_ENV); +module.exports = app; diff --git a/modules/crud/crud.test.js b/modules/crud/crud.test.js new file mode 100644 index 0000000..e1aacbd --- /dev/null +++ b/modules/crud/crud.test.js @@ -0,0 +1,1673 @@ +'use strict'; +/* + * Copyright (c) 2016 TopCoder, Inc. All rights reserved. + */ + + +const assert = require('chai').assert; +const _ = require('lodash'); +const testHelper = require('../../test/test-helper'); +const api = testHelper.api; +const apiVersion = testHelper.apiVersion; +const data = testHelper.data; + + +describe('GET /objects/games/:id', () => { + const apiPath = `${apiVersion}/objects/games/`; + + it('get with id', (done) => { + api + .get(apiPath + '2609') + .set({ + authorization: testHelper.token + }) + .expect(200) + .end((err, res) => { + if (err) { + return done(err); + } + assert.deepEqual(res.body, data.game2609); + done(); + }); + }); + + it('get with single field name', (done) => { + api + .get(apiPath + '2609?fieldNames=name') + .set({ + authorization: testHelper.token + }) + .expect(200) + .end((err, res) => { + if (err) { + return done(err); + } + assert.equal(res.body.length, 1); + const item = res.body[0]; + assert.property(item, 'fieldName'); + assert.equal(item.fieldName, 'name'); + assert.property(item, 'fieldValue'); + assert.equal(item.fieldValue, '"10 Days in Africa"'); + done(); + }); + }); + + it('get with fields', (done) => { + api + .get(apiPath + '2609?fieldNames=name&fieldNames=image') + .set({ + authorization: testHelper.token + }) + .expect(200) + .end((err, res) => { + if (err) { + return done(err); + } + assert.equal(res.body.length, 2); + let item = res.body[0]; + assert.property(item, 'fieldName'); + assert.equal(item.fieldName, 'name'); + assert.property(item, 'fieldValue'); + assert.equal(item.fieldValue, '"10 Days in Africa"'); + item = res.body[1]; + assert.property(item, 'fieldName'); + assert.equal(item.fieldName, 'image'); + assert.property(item, 'fieldValue'); + assert.equal(item.fieldValue, '"http://cf.geekdo-images.com/images/pic1229634.jpg"'); + done(); + }); + }); + + it('get with fields []', (done) => { + api + .get(apiPath + '2609?fieldNames[]=name&fieldNames[]=image') + .set({ + authorization: testHelper.token + }) + .expect(200) + .end((err, res) => { + if (err) { + return done(err); + } + assert.equal(res.body.length, 2); + let item = res.body[0]; + assert.property(item, 'fieldName'); + assert.equal(item.fieldName, 'name'); + assert.property(item, 'fieldValue'); + assert.equal(item.fieldValue, '"10 Days in Africa"'); + item = res.body[1]; + assert.property(item, 'fieldName'); + assert.equal(item.fieldName, 'image'); + assert.property(item, 'fieldValue'); + assert.equal(item.fieldValue, '"http://cf.geekdo-images.com/images/pic1229634.jpg"'); + done(); + }); + }); + + it('get with fields [index]', (done) => { + api + .get(apiPath + '2609?fieldNames[1]=name&fieldNames[0]=image') + .set({ + authorization: testHelper.token + }) + .expect(200) + .end((err, res) => { + if (err) { + return done(err); + } + assert.equal(res.body.length, 2); + let item = res.body[0]; + assert.property(item, 'fieldName'); + assert.equal(item.fieldName, 'image'); + assert.property(item, 'fieldValue'); + assert.equal(item.fieldValue, '"http://cf.geekdo-images.com/images/pic1229634.jpg"'); + item = res.body[1]; + assert.property(item, 'fieldName'); + assert.equal(item.fieldName, 'name'); + assert.property(item, 'fieldValue'); + assert.equal(item.fieldValue, '"10 Days in Africa"'); + done(); + }); + }); + + it('get with invalid id (too small)', (done) => { + api + .get(apiPath + '-1') + .set({ + authorization: testHelper.token + }) + .expect(400) + .end((err, res) => { + if (err) { + return done(err); + } + assert.property(res.body, 'fields'); + assert.equal(res.body.fields, 'id'); + assert.property(res.body, 'message'); + assert.equal(res.body.message, '"id" must be larger than or equal to 1'); + assert.property(res.body, 'code'); + assert.equal(res.body.code, 400); + done(); + }); + }); + + it('get with invalid id (too large)', (done) => { + api + .get(apiPath + '9999999999999999999') + .set({ + authorization: testHelper.token + }) + .expect(400) + .end((err, res) => { + if (err) { + return done(err); + } + assert.property(res.body, 'fields'); + assert.equal(res.body.fields, 'id'); + assert.property(res.body, 'message'); + assert.equal(res.body.message, '"id" must be less than or equal to 9223372036854776000'); + assert.property(res.body, 'code'); + assert.equal(res.body.code, 400); + done(); + }); + }); + + it('get with invalid id (not integer)', (done) => { + api + .get(apiPath + '1.1') + .set({ + authorization: testHelper.token + }) + .expect(400) + .end((err, res) => { + if (err) { + return done(err); + } + assert.property(res.body, 'fields'); + assert.equal(res.body.fields, 'id'); + assert.property(res.body, 'message'); + assert.equal(res.body.message, '"id" must be an integer'); + assert.property(res.body, 'code'); + assert.equal(res.body.code, 400); + done(); + }); + }); + + it('get with not exist id', (done) => { + api + .get(apiPath + '999999') + .set({ + authorization: testHelper.token + }) + .expect(404) + .end((err, res) => { + if (err) { + return done(err); + } + assert.property(res.body, 'message'); + assert.equal(res.body.message, 'Could not find games by id 999999'); + assert.property(res.body, 'code'); + assert.equal(res.body.code, 404); + done(); + }); + }); + + it('get with not exist field', (done) => { + api + .get(apiPath + '2609?fieldNames=notexist') + .set({ + authorization: testHelper.token + }) + .expect(500) + .end((err, res) => { + if (err) { + return done(err); + } + assert.property(res.body, 'message'); + assert.equal(res.body.message, 'column "notexist" does not exist'); + assert.property(res.body, 'code'); + assert.equal(res.body.code, 500); + done(); + }); + }); + + it('get with not exist table', (done) => { + api + .get(`${apiVersion}/objects/notexist/2609`) + .set({ + authorization: testHelper.token + }) + .expect(500) + .end((err, res) => { + if (err) { + return done(err); + } + assert.property(res.body, 'message'); + assert.equal(res.body.message, 'relation "notexist" does not exist'); + assert.property(res.body, 'code'); + assert.equal(res.body.code, 500); + done(); + }); + }); + + it('get with none roles token', (done) => { + api + .get(apiPath + '2609') + .set({ + authorization: testHelper.noRolesToken + }) + .expect(403) + .end((err, res) => { + if (err) { + return done(err); + } + assert.property(res.body, 'message'); + assert.equal(res.body.message, 'You are not allowed to perform this action!'); + assert.property(res.body, 'code'); + assert.equal(res.body.code, 403); + done(); + }); + }); + + it('get with no token', (done) => { + api + .get(apiPath + '2609') + .expect(401) + .end((err, res) => { + if (err) { + return done(err); + } + assert.property(res.body, 'message'); + assert.equal(res.body.message, 'UnauthorizedError'); + assert.property(res.body, 'code'); + assert.equal(res.body.code, 401); + done(); + }); + }); + + it('get with invalid token', (done) => { + api + .get(apiPath + '2609') + .set({ + authorization: testHelper.invalidToken + }) + .expect(401) + .end((err, res) => { + if (err) { + return done(err); + } + assert.property(res.body, 'message'); + assert.equal(res.body.message, 'Failed to authenticate jwt token.'); + assert.property(res.body, 'code'); + assert.equal(res.body.code, 401); + done(); + }); + }); +}); + + +describe('GET /objects/games/', () => { + const apiPath = `${apiVersion}/objects/games`; + + it('searh all games', (done) => { + api + .get(apiPath) + .set({ + authorization: testHelper.token + }) + .expect(200) + .end((err, res) => { + if (err) { + return done(err); + } + + assert.property(res.body, 'totalRecords'); + assert.equal(res.body.totalRecords, 507); + assert.property(res.body, 'totalPages'); + assert.equal(res.body.totalPages, 1); + + assert.property(res.body, 'items'); + assert.equal(res.body.items.length, 507); + _.each(res.body.items, (item) => { + assert.property(item, 'objectType'); + assert.equal(item.objectType, 'games'); + assert.property(item, 'id'); + assert.property(item, 'fields'); + + if (item.id === 2609) { + assert.deepEqual(item.fields, data.game2609); + } else if (item.id === 2611) { + assert.deepEqual(item.fields, data.game2611); + } + }); + done(); + }); + }); + + it('search with pageSize and pageNumber', (done) => { + api + .get(apiPath + '?pageSize=2&pageNumber=2') + .set({ + authorization: testHelper.token + }) + .expect(200) + .end((err, res) => { + if (err) { + return done(err); + } + + assert.property(res.body, 'totalRecords'); + assert.equal(res.body.totalRecords, 507); + assert.property(res.body, 'totalPages'); + assert.equal(res.body.totalPages, 254); + + assert.property(res.body, 'items'); + assert.equal(res.body.items.length, 2); + _.each(res.body.items, (item) => { + assert.property(item, 'objectType'); + assert.equal(item.objectType, 'games'); + assert.property(item, 'id'); + assert.property(item, 'fields'); + + if (item.id === 2611) { + assert.deepEqual(item.fields, data.game2611); + } + }); + done(); + }); + }); + + it('search with sortBy and sortOrder', (done) => { + api + .get(apiPath + '?sortOrder=Descending&sortBy=name') + .set({ + authorization: testHelper.token + }) + .expect(200) + .end((err, res) => { + if (err) { + return done(err); + } + + assert.property(res.body, 'totalRecords'); + assert.equal(res.body.totalRecords, 507); + assert.property(res.body, 'totalPages'); + assert.equal(res.body.totalPages, 1); + + assert.property(res.body, 'items'); + assert.equal(res.body.items.length, 507); + + let item = res.body.items[0]; + assert.property(item, 'objectType'); + assert.equal(item.objectType, 'games'); + assert.property(item, 'id'); + assert.equal(item.id, 3115); + assert.property(item, 'fields'); + assert.deepEqual(item.fields, data.game3115); + + item = res.body.items[506]; + assert.property(item, 'objectType'); + assert.equal(item.objectType, 'games'); + assert.property(item, 'id'); + assert.equal(item.id, 2609); + assert.property(item, 'fields'); + assert.deepEqual(item.fields, data.game2609); + + done(); + }); + }); + + it('search with ExactMatching', (done) => { + api + .get(apiPath + '?matchCriteria[][fieldName]=name&matchCriteria[][value]="7 Wonders"&matchCriteria[][matchType]=ExactMatching') + .set({ + authorization: testHelper.token + }) + .expect(200) + .end((err, res) => { + if (err) { + return done(err); + } + + assert.property(res.body, 'totalRecords'); + assert.equal(res.body.totalRecords, 1); + assert.property(res.body, 'totalPages'); + assert.equal(res.body.totalPages, 1); + + assert.property(res.body, 'items'); + assert.equal(res.body.items.length, 1); + + const item = res.body.items[0]; + assert.property(item, 'objectType'); + assert.equal(item.objectType, 'games'); + assert.property(item, 'id'); + assert.equal(item.id, 2616); + assert.property(item, 'fields'); + assert.deepEqual(item.fields, data.game2616); + + done(); + }); + }); + + it('search with PartialMatching', (done) => { + api + .get(apiPath + '?matchCriteria[][fieldName]=name&matchCriteria[][value]="ab"&matchCriteria[][matchType]=PartialMatching&pageSize=5&pageNumber=1') + .set({ + authorization: testHelper.token + }) + .expect(200) + .end((err, res) => { + if (err) { + return done(err); + } + + assert.property(res.body, 'totalRecords'); + assert.equal(res.body.totalRecords, 10); + assert.property(res.body, 'totalPages'); + assert.equal(res.body.totalPages, 2); + + assert.property(res.body, 'items'); + assert.equal(res.body.items.length, 5); + + const itemsIds = _.map(res.body.items, 'id').sort(); + const expect = [2617, 2623, 2624, 2740, 2823]; + assert.deepEqual(itemsIds, expect); + + done(); + }); + }); + + it('search with Greater', (done) => { + api + .get(apiPath + '?matchCriteria[][fieldName]=rank&matchCriteria[][value]="1"&matchCriteria[][matchType]=Greater&pageSize=5&pageNumber=1') + .set({ + authorization: testHelper.token + }) + .expect(200) + .end((err, res) => { + if (err) { + return done(err); + } + + assert.property(res.body, 'totalRecords'); + assert.equal(res.body.totalRecords, 332); + assert.property(res.body, 'totalPages'); + assert.equal(res.body.totalPages, 67); + + assert.property(res.body, 'items'); + assert.equal(res.body.items.length, 5); + + const itemsIds = _.map(res.body.items, 'id').sort(); + const expect = [2609, 2610, 2611, 2612, 2614]; + assert.deepEqual(itemsIds, expect); + + done(); + }); + }); + + it('search with GreaterOrEqual', (done) => { + api + .get(apiPath + '?matchCriteria[][fieldName]=rank&matchCriteria[][value]="1"&matchCriteria[][matchType]=GreaterOrEqual&pageSize=5&pageNumber=1') + .set({ + authorization: testHelper.token + }) + .expect(200) + .end((err, res) => { + if (err) { + return done(err); + } + + assert.property(res.body, 'totalRecords'); + assert.equal(res.body.totalRecords, 333); + assert.property(res.body, 'totalPages'); + assert.equal(res.body.totalPages, 67); + + assert.property(res.body, 'items'); + assert.equal(res.body.items.length, 5); + + const itemsIds = _.map(res.body.items, 'id').sort(); + const expect = [2609, 2610, 2611, 2612, 2614]; + assert.deepEqual(itemsIds, expect); + + done(); + }); + }); + + it('search with Equal number', (done) => { + api + .get(apiPath + '?matchCriteria[][fieldName]=rank&matchCriteria[][value]="1"&matchCriteria[][matchType]=Equal&pageSize=5&pageNumber=1') + .set({ + authorization: testHelper.token + }) + .expect(200) + .end((err, res) => { + if (err) { + return done(err); + } + + assert.property(res.body, 'totalRecords'); + assert.equal(res.body.totalRecords, 1); + assert.property(res.body, 'totalPages'); + assert.equal(res.body.totalPages, 1); + + assert.property(res.body, 'items'); + assert.equal(res.body.items.length, 1); + + const itemsIds = _.map(res.body.items, 'id').sort(); + const expect = [2921]; + assert.deepEqual(itemsIds, expect); + + done(); + }); + }); + + it('search with Equal bool true', (done) => { + api + .get(apiPath + '?matchCriteria[][fieldName]=owned&matchCriteria[][value]="true"&matchCriteria[][matchType]=Equal&pageSize=5&pageNumber=1') + .set({ + authorization: testHelper.token + }) + .expect(200) + .end((err, res) => { + if (err) { + return done(err); + } + + assert.property(res.body, 'totalRecords'); + assert.equal(res.body.totalRecords, 380); + assert.property(res.body, 'totalPages'); + assert.equal(res.body.totalPages, 76); + + assert.property(res.body, 'items'); + assert.equal(res.body.items.length, 5); + + const itemsIds = _.map(res.body.items, 'id').sort(); + const expect = [2609, 2610, 2611, 2612, 2613]; + assert.deepEqual(itemsIds, expect); + + done(); + }); + }); + + it('search with Equal bool Y', (done) => { + api + .get(apiPath + '?matchCriteria[][fieldName]=owned&matchCriteria[][value]="Y"&matchCriteria[][matchType]=Equal&pageSize=5&pageNumber=1') + .set({ + authorization: testHelper.token + }) + .expect(200) + .end((err, res) => { + if (err) { + return done(err); + } + + assert.property(res.body, 'totalRecords'); + assert.equal(res.body.totalRecords, 380); + assert.property(res.body, 'totalPages'); + assert.equal(res.body.totalPages, 76); + + assert.property(res.body, 'items'); + assert.equal(res.body.items.length, 5); + + const itemsIds = _.map(res.body.items, 'id').sort(); + const expect = [2609, 2610, 2611, 2612, 2613]; + assert.deepEqual(itemsIds, expect); + + done(); + }); + }); + + it('search with Less', (done) => { + api + .get(apiPath + '?matchCriteria[][fieldName]=rank&matchCriteria[][value]="1"&matchCriteria[][matchType]=Less&pageSize=5&pageNumber=1') + .set({ + authorization: testHelper.token + }) + .expect(200) + .end((err, res) => { + if (err) { + return done(err); + } + + assert.property(res.body, 'totalRecords'); + assert.equal(res.body.totalRecords, 174); + assert.property(res.body, 'totalPages'); + assert.equal(res.body.totalPages, 35); + + assert.property(res.body, 'items'); + assert.equal(res.body.items.length, 5); + + const itemsIds = _.map(res.body.items, 'id').sort(); + const expect = [2613, 2617, 2618, 2620, 2621]; + assert.deepEqual(itemsIds, expect); + + done(); + }); + }); + + it('search with LessOrEqual', (done) => { + api + .get(apiPath + '?matchCriteria[][fieldName]=rank&matchCriteria[][value]="1"&matchCriteria[][matchType]=Less&pageSize=5&pageNumber=1') + .set({ + authorization: testHelper.token + }) + .expect(200) + .end((err, res) => { + if (err) { + return done(err); + } + + assert.property(res.body, 'totalRecords'); + assert.equal(res.body.totalRecords, 174); + assert.property(res.body, 'totalPages'); + assert.equal(res.body.totalPages, 35); + + assert.property(res.body, 'items'); + assert.equal(res.body.items.length, 5); + + const itemsIds = _.map(res.body.items, 'id').sort(); + const expect = [2613, 2617, 2618, 2620, 2621]; + assert.deepEqual(itemsIds, expect); + + done(); + }); + }); + + it('search with complex criteria', (done) => { + api + .get(apiPath + '?matchCriteria[0][fieldName]=owned&matchCriteria[0][value]="Y"&matchCriteria[0][matchType]=Equal&pageSize=5&pageNumber=1' + + '&matchCriteria[1][fieldName]=name&matchCriteria[1][value]="10 Days in Africa"&matchCriteria[1][matchType]=ExactMatching') + .set({ + authorization: testHelper.token + }) + .expect(200) + .end((err, res) => { + if (err) { + return done(err); + } + + assert.property(res.body, 'totalRecords'); + assert.equal(res.body.totalRecords, 1); + assert.property(res.body, 'totalPages'); + assert.equal(res.body.totalPages, 1); + + assert.property(res.body, 'items'); + assert.equal(res.body.items.length, 1); + + const item = res.body.items[0]; + assert.property(item, 'objectType'); + assert.equal(item.objectType, 'games'); + assert.property(item, 'id'); + assert.equal(item.id, 2609); + assert.property(item, 'fields'); + assert.deepEqual(item.fields, data.game2609); + + done(); + }); + }); + + it('search with invalid json value', (done) => { + api + .get(apiPath + '?matchCriteria[][fieldName]=name&matchCriteria[][value]=a&matchCriteria[][matchType]=ExactMatching&pageSize=5&pageNumber=1') + .set({ + authorization: testHelper.token + }) + .expect(400) + .end((err, res) => { + if (err) { + return done(err); + } + + assert.property(res.body, 'message'); + assert.equal(res.body.message, 'Invalid json string field value for \'name\''); + assert.property(res.body, 'code'); + assert.equal(res.body.code, 400); + + done(); + }); + }); + + it('search with invalid match type for string', (done) => { + api + .get(apiPath + '?matchCriteria[][fieldName]=name&matchCriteria[][value]="a"&matchCriteria[][matchType]=GreaterOrEqual&pageSize=5&pageNumber=1') + .set({ + authorization: testHelper.token + }) + .expect(400) + .end((err, res) => { + if (err) { + return done(err); + } + + assert.property(res.body, 'message'); + assert.equal(res.body.message, 'Invalid matchType for \'name\''); + assert.property(res.body, 'code'); + assert.equal(res.body.code, 400); + + done(); + }); + }); + + it('search with invalid match type for not string', (done) => { + api + .get(apiPath + '?matchCriteria[][fieldName]=rank&matchCriteria[][value]="3"&matchCriteria[][matchType]=ExactMatching&pageSize=5&pageNumber=1') + .set({ + authorization: testHelper.token + }) + .expect(400) + .end((err, res) => { + if (err) { + return done(err); + } + + assert.property(res.body, 'message'); + assert.equal(res.body.message, 'Invalid matchType for \'rank\''); + assert.property(res.body, 'code'); + assert.equal(res.body.code, 400); + + done(); + }); + }); + + it('search with invalid pageSize', (done) => { + api + .get(apiPath + '?pageSize=0&pageNumber=2') + .set({ + authorization: testHelper.token + }) + .expect(400) + .end((err, res) => { + if (err) { + return done(err); + } + + assert.property(res.body, 'fields'); + assert.equal(res.body.fields, 'query.pageSize'); + assert.property(res.body, 'message'); + assert.equal(res.body.message, '"pageSize" must be larger than or equal to 1'); + assert.property(res.body, 'code'); + assert.equal(res.body.code, 400); + + done(); + }); + }); + + it('search with invalid pageNumber', (done) => { + api + .get(apiPath + '?pageSize=2&pageNumber=-2') + .set({ + authorization: testHelper.token + }) + .expect(400) + .end((err, res) => { + if (err) { + return done(err); + } + + assert.property(res.body, 'fields'); + assert.equal(res.body.fields, 'query.pageNumber'); + assert.property(res.body, 'message'); + assert.equal(res.body.message, '"pageNumber" must be larger than or equal to 0'); + assert.property(res.body, 'code'); + assert.equal(res.body.code, 400); + + done(); + }); + }); + + it('search with invalid sortBy', (done) => { + api + .get(apiPath + '?sortOrder=Descending&sortBy=notexist') + .set({ + authorization: testHelper.token + }) + .expect(400) + .end((err, res) => { + if (err) { + return done(err); + } + + assert.property(res.body, 'message'); + assert.equal(res.body.message, 'There is no such column called \'notexist\' for \'games\''); + assert.property(res.body, 'code'); + assert.equal(res.body.code, 400); + + done(); + }); + }); + + it('search with invalid sortOrder', (done) => { + api + .get(apiPath + '?sortOrder=invalid&sortBy=name') + .set({ + authorization: testHelper.token + }) + .expect(400) + .end((err, res) => { + if (err) { + return done(err); + } + + assert.property(res.body, 'fields'); + assert.equal(res.body.fields, 'query.sortOrder'); + assert.property(res.body, 'message'); + assert.equal(res.body.message, '"sortOrder" must be one of [Ascending, Descending]'); + assert.property(res.body, 'code'); + assert.equal(res.body.code, 400); + + done(); + }); + }); + + it('search without token', (done) => { + api + .get(apiPath) + .expect(401) + .end((err, res) => { + if (err) { + return done(err); + } + + assert.property(res.body, 'message'); + assert.equal(res.body.message, 'UnauthorizedError'); + assert.property(res.body, 'code'); + assert.equal(res.body.code, 401); + + done(); + }); + }); + + it('search with invalid token', (done) => { + api + .get(apiPath) + .set({ + authorization: testHelper.invalidToken + }) + .expect(401) + .end((err, res) => { + if (err) { + return done(err); + } + + assert.property(res.body, 'message'); + assert.equal(res.body.message, 'Failed to authenticate jwt token.'); + assert.property(res.body, 'code'); + assert.equal(res.body.code, 401); + + done(); + }); + }); + + it('search with non roles token', (done) => { + api + .get(apiPath) + .set({ + authorization: testHelper.noRolesToken + }) + .expect(403) + .end((err, res) => { + if (err) { + return done(err); + } + + assert.property(res.body, 'message'); + assert.equal(res.body.message, 'You are not allowed to perform this action!'); + assert.property(res.body, 'code'); + assert.equal(res.body.code, 403); + + done(); + }); + }); +}); + + +describe('POST /objects/games', () => { + let newId; + const apiPath = `${apiVersion}/objects/games/`; + + beforeEach((done) => { + newId = undefined; + done(); + }); + + afterEach((done) => { + if (newId) { + // Removed item with new id); + api + .delete(apiPath + newId) + .set({ + authorization: testHelper.token + }) + .end(() => { + done(); + }); + } else { + done(); + } + }); + + it('create new game', (done) => { + api + .post(apiPath) + .set({ + authorization: testHelper.token + }) + .send(data.gameNew) + .expect(201) + .end((err, res) => { + if (err) { + return done(err); + } + newId = res.body; + done(); + }); + }); + + it('create with double-quotes', (done) => { + api + .post(apiPath) + .set({ + authorization: testHelper.token + }) + .send([{ fieldName: '"gameId"', fieldValue: '8888' }]) + .expect(201) + .end((err, res) => { + if (err) { + return done(err); + } + newId = res.body; + done(); + }); + }); + + it('create with invalid json in field value', (done) => { + api + .post(apiPath) + .set({ + authorization: testHelper.token + }) + .send([{ fieldName: 'invalidjson', fieldValue: 'a' }]) + .expect(400) + .end((err, res) => { + if (err) { + return done(err); + } + assert.property(res.body, 'message'); + assert.equal(res.body.message, 'Invalid json string field value for \'invalidjson\''); + assert.property(res.body, 'code'); + assert.equal(res.body.code, 400); + done(); + }); + }); + + it('create with not exist field', (done) => { + api + .post(apiPath) + .set({ + authorization: testHelper.token + }) + .send([{ fieldName: 'notexist', fieldValue: '"a"' }]) + .expect(500) + .end((err, res) => { + if (err) { + return done(err); + } + assert.property(res.body, 'message'); + assert.equal(res.body.message, 'column "notexist" of relation "games" does not exist'); + assert.property(res.body, 'code'); + assert.equal(res.body.code, 500); + done(); + }); + }); + + it('create with user role', (done) => { + api + .post(apiPath) + .set({ + authorization: testHelper.userToken + }) + .send(data.gameNew) + .expect(403) + .end((err, res) => { + if (err) { + return done(err); + } + assert.property(res.body, 'message'); + assert.equal(res.body.message, 'You are not allowed to perform this action!'); + assert.property(res.body, 'code'); + assert.equal(res.body.code, 403); + done(); + }); + }); + + it('create with empty fields', (done) => { + api + .post(apiPath) + .set({ + authorization: testHelper.token + }) + .send([]) + .expect(400) + .end((err, res) => { + if (err) { + return done(err); + } + assert.property(res.body, 'fields'); + assert.equal(res.body.fields, 'fields'); + assert.property(res.body, 'message'); + assert.equal(res.body.message, '"fields" must contain at least 1 items'); + assert.property(res.body, 'code'); + assert.equal(res.body.code, 400); + done(); + }); + }); + + it('create with not exist table', (done) => { + api + .post(`${apiVersion}/objects/notexist/`) + .set({ + authorization: testHelper.token + }) + .send(data.gameNew) + .expect(500) + .end((err, res) => { + if (err) { + return done(err); + } + assert.property(res.body, 'message'); + assert.equal(res.body.message, 'relation "notexist" does not exist'); + assert.property(res.body, 'code'); + assert.equal(res.body.code, 500); + done(); + }); + }); + + it('create with wrong body', (done) => { + api + .post(apiPath) + .set({ + authorization: testHelper.token + }) + .send({ wrong: true }) + .expect(400) + .end((err, res) => { + if (err) { + return done(err); + } + assert.property(res.body, 'fields'); + assert.equal(res.body.fields, 'fields'); + assert.property(res.body, 'message'); + assert.equal(res.body.message, '"fields" must be an array'); + assert.property(res.body, 'code'); + assert.equal(res.body.code, 400); + done(); + }); + }); + + it('create with unexpected fields object', (done) => { + api + .post(apiPath) + .set({ + authorization: testHelper.token + }) + .send([{ fieldName: 'notexist', fieldValue: '"a"', wrong: 2 }]) + .expect(400) + .end((err, res) => { + if (err) { + return done(err); + } + assert.property(res.body, 'fields'); + assert.equal(res.body.fields, 'fields.0.wrong'); + assert.property(res.body, 'message'); + assert.equal(res.body.message, '"wrong" is not allowed'); + assert.property(res.body, 'code'); + assert.equal(res.body.code, 400); + done(); + }); + }); + + it('create with no fields', (done) => { + api + .post(apiPath) + .set({ + authorization: testHelper.token + }) + .expect(400) + .end((err, res) => { + if (err) { + return done(err); + } + assert.property(res.body, 'fields'); + assert.equal(res.body.fields, 'fields'); + assert.property(res.body, 'message'); + assert.equal(res.body.message, '"fields" must be an array'); + assert.property(res.body, 'code'); + assert.equal(res.body.code, 400); + done(); + }); + }); + + it('create without token', (done) => { + api + .post(apiPath) + .send(testHelper.gameNew) + .expect(401) + .end((err, res) => { + if (err) { + return done(err); + } + assert.property(res.body, 'message'); + assert.equal(res.body.message, 'UnauthorizedError'); + assert.property(res.body, 'code'); + assert.equal(res.body.code, 401); + done(); + }); + }); + + it('create without invalid token', (done) => { + api + .post(apiPath) + .set({ + authorization: testHelper.invalidToken + }) + .send(testHelper.gameNew) + .expect(401) + .end((err, res) => { + if (err) { + return done(err); + } + assert.property(res.body, 'message'); + assert.equal(res.body.message, 'Failed to authenticate jwt token.'); + assert.property(res.body, 'code'); + assert.equal(res.body.code, 401); + done(); + }); + }); +}); + + +describe('PUT /objects/games/:id', () => { + let newId; + const apiPath = `${apiVersion}/objects/games/`; + + before((done) => { + api + .post(apiPath) + .set({ + authorization: testHelper.token + }) + .send(data.gameNew) + .end((err, res) => { + newId = res.body; + done(); + }); + }); + + after((done) => { + api + .delete(apiPath + newId) + .set({ + authorization: testHelper.token + }) + .end(() => { + done(); + }); + }); + + it('update game', (done) => { + api + .put(apiPath + newId) + .set({ + authorization: testHelper.token + }) + .send(data.gameUpdate) + .expect(200) + .end((err) => { + if (err) { + return done(err); + } + done(); + }); + }); + + it('update with double-quotes', (done) => { + api + .put(apiPath + newId) + .set({ + authorization: testHelper.token + }) + .send([{ fieldName: '"gameId"', fieldValue: '99999' }]) + .expect(200) + .end((err) => { + if (err) { + return done(err); + } + done(); + }); + }); + + it('update and set null', (done) => { + api + .put(apiPath + newId) + .set({ + authorization: testHelper.token + }) + .send([{ fieldName: 'gameId', fieldValue: '"null"' }]) + .expect(200) + .end((err) => { + if (err) { + return done(err); + } + done(); + }); + }); + + it('update with not exist id', (done) => { + api + .put(apiPath + '999999999') + .set({ + authorization: testHelper.token + }) + .send([{ fieldName: '"gameId"', fieldValue: '99999' }]) + .expect(404) + .end((err, res) => { + if (err) { + return done(err); + } + assert.property(res.body, 'message'); + assert.equal(res.body.message, 'Could not find games by id 999999999'); + assert.property(res.body, 'code'); + assert.equal(res.body.code, 404); + done(); + }); + }); + + it('update with not exist table', (done) => { + api + .put(apiVersion + '/objects/notexist/1') + .set({ + authorization: testHelper.token + }) + .send([{ fieldName: '"gameId"', fieldValue: '99999' }]) + .expect(500) + .end((err, res) => { + if (err) { + return done(err); + } + assert.property(res.body, 'message'); + assert.equal(res.body.message, 'relation "notexist" does not exist'); + assert.property(res.body, 'code'); + assert.equal(res.body.code, 500); + done(); + }); + }); + + it('update with invalid json', (done) => { + api + .put(apiPath + newId) + .set({ + authorization: testHelper.token + }) + .send([{ fieldName: 'invalidjson', fieldValue: 'a' }]) + .expect(400) + .end((err, res) => { + if (err) { + return done(err); + } + assert.property(res.body, 'message'); + assert.equal(res.body.message, 'Invalid json string field value for \'invalidjson\''); + assert.property(res.body, 'code'); + assert.equal(res.body.code, 400); + done(); + }); + }); + + it('update with not exist field', (done) => { + api + .put(apiPath + newId) + .set({ + authorization: testHelper.token + }) + .send([{ fieldName: 'notexist', fieldValue: '"a"' }]) + .expect(500) + .end((err, res) => { + if (err) { + return done(err); + } + assert.property(res.body, 'message'); + assert.equal(res.body.message, 'column "notexist" of relation "games" does not exist'); + assert.property(res.body, 'code'); + assert.equal(res.body.code, 500); + done(); + }); + }); + + it('update with user role', (done) => { + api + .put(apiPath + newId) + .set({ + authorization: testHelper.userToken + }) + .send(data.gameUpdate) + .expect(403) + .end((err, res) => { + if (err) { + return done(err); + } + assert.property(res.body, 'message'); + assert.equal(res.body.message, 'You are not allowed to perform this action!'); + assert.property(res.body, 'code'); + assert.equal(res.body.code, 403); + done(); + }); + }); + + it('update with empty fields', (done) => { + api + .put(apiPath + newId) + .set({ + authorization: testHelper.token + }) + .send([]) + .expect(400) + .end((err, res) => { + if (err) { + return done(err); + } + assert.property(res.body, 'fields'); + assert.equal(res.body.fields, 'fields'); + assert.property(res.body, 'message'); + assert.equal(res.body.message, '"fields" must contain at least 1 items'); + assert.property(res.body, 'code'); + assert.equal(res.body.code, 400); + done(); + }); + }); + + it('update with wrong body', (done) => { + api + .put(apiPath + newId) + .set({ + authorization: testHelper.token + }) + .send({ wrong: true }) + .expect(400) + .end((err, res) => { + if (err) { + return done(err); + } + assert.property(res.body, 'fields'); + assert.equal(res.body.fields, 'fields'); + assert.property(res.body, 'message'); + assert.equal(res.body.message, '"fields" must be an array'); + assert.property(res.body, 'code'); + assert.equal(res.body.code, 400); + done(); + }); + }); + + it('update with unexpected fields object', (done) => { + api + .put(apiPath + newId) + .set({ + authorization: testHelper.token + }) + .send([{ fieldName: 'notexist', fieldValue: '"a"', wrong: 2 }]) + .expect(400) + .end((err, res) => { + if (err) { + return done(err); + } + assert.property(res.body, 'fields'); + assert.equal(res.body.fields, 'fields.0.wrong'); + assert.property(res.body, 'message'); + assert.equal(res.body.message, '"wrong" is not allowed'); + assert.property(res.body, 'code'); + assert.equal(res.body.code, 400); + done(); + }); + }); + + it('update with no fields', (done) => { + api + .put(apiPath + newId) + .set({ + authorization: testHelper.token + }) + .expect(400) + .end((err, res) => { + if (err) { + return done(err); + } + assert.property(res.body, 'fields'); + assert.equal(res.body.fields, 'fields'); + assert.property(res.body, 'message'); + assert.equal(res.body.message, '"fields" must be an array'); + assert.property(res.body, 'code'); + assert.equal(res.body.code, 400); + done(); + }); + }); + + it('update without token', (done) => { + api + .put(apiPath + newId) + .send(data.gameUpdate) + .expect(401) + .end((err, res) => { + if (err) { + return done(err); + } + assert.property(res.body, 'message'); + assert.equal(res.body.message, 'UnauthorizedError'); + assert.property(res.body, 'code'); + assert.equal(res.body.code, 401); + done(); + }); + }); + + it('update with invalid token', (done) => { + api + .put(apiPath + newId) + .set({ + authorization: testHelper.invalidToken + }) + .send(data.gameUpdate) + .expect(401) + .end((err, res) => { + if (err) { + return done(err); + } + assert.property(res.body, 'message'); + assert.equal(res.body.message, 'Failed to authenticate jwt token.'); + assert.property(res.body, 'code'); + assert.equal(res.body.code, 401); + done(); + }); + }); +}); + + +describe('DELETE /objects/games/:id', () => { + const apiPath = `${apiVersion}/objects/games/`; + + it('delete a game', (done) => { + api + .post(apiPath) + .set({ + authorization: testHelper.token + }) + .send(data.gameNew) + .end((err, res) => { + const newId = res.body; + api + .delete(apiPath + newId) + .set({ + authorization: testHelper.token + }) + .expect(200) + .end((err2) => { + if (err2) { + return done(err2); + } + done(); + }); + }); + }); + + it('delete with invalid id (too small)', (done) => { + api + .delete(apiPath + '-1') + .set({ + authorization: testHelper.token + }) + .expect(400) + .end((err, res) => { + if (err) { + return done(err); + } + assert.property(res.body, 'fields'); + assert.equal(res.body.fields, 'id'); + assert.property(res.body, 'message'); + assert.equal(res.body.message, '"id" must be larger than or equal to 1'); + assert.property(res.body, 'code'); + assert.equal(res.body.code, 400); + done(); + }); + }); + + it('delete with invalid id (too large)', (done) => { + api + .delete(apiPath + '9999999999999999999') + .set({ + authorization: testHelper.token + }) + .expect(400) + .end((err, res) => { + if (err) { + return done(err); + } + assert.property(res.body, 'fields'); + assert.equal(res.body.fields, 'id'); + assert.property(res.body, 'message'); + assert.equal(res.body.message, '"id" must be less than or equal to 9223372036854776000'); + assert.property(res.body, 'code'); + assert.equal(res.body.code, 400); + done(); + }); + }); + + it('delete with invalid id (not integer)', (done) => { + api + .delete(apiPath + '1.1') + .set({ + authorization: testHelper.token + }) + .expect(400) + .end((err, res) => { + if (err) { + return done(err); + } + assert.property(res.body, 'fields'); + assert.equal(res.body.fields, 'id'); + assert.property(res.body, 'message'); + assert.equal(res.body.message, '"id" must be an integer'); + assert.property(res.body, 'code'); + assert.equal(res.body.code, 400); + done(); + }); + }); + + it('delete with not exist id', (done) => { + api + .delete(apiPath + '999999') + .set({ + authorization: testHelper.token + }) + .expect(404) + .end((err, res) => { + if (err) { + return done(err); + } + assert.property(res.body, 'message'); + assert.equal(res.body.message, 'Could not find games by id 999999'); + assert.property(res.body, 'code'); + assert.equal(res.body.code, 404); + done(); + }); + }); + + it('delete with not exist table', (done) => { + api + .delete(`${apiVersion}/objects/notexist/2609`) + .set({ + authorization: testHelper.token + }) + .expect(500) + .end((err, res) => { + if (err) { + return done(err); + } + assert.property(res.body, 'message'); + assert.equal(res.body.message, 'relation "notexist" does not exist'); + assert.property(res.body, 'code'); + assert.equal(res.body.code, 500); + done(); + }); + }); + + it('delete with none roles token', (done) => { + api + .delete(apiPath + '2609') + .set({ + authorization: testHelper.noRolesToken + }) + .expect(403) + .end((err, res) => { + if (err) { + return done(err); + } + assert.property(res.body, 'message'); + assert.equal(res.body.message, 'You are not allowed to perform this action!'); + assert.property(res.body, 'code'); + assert.equal(res.body.code, 403); + done(); + }); + }); + + it('delete with no token', (done) => { + api + .delete(apiPath + '2609') + .expect(401) + .end((err, res) => { + if (err) { + return done(err); + } + assert.property(res.body, 'message'); + assert.equal(res.body.message, 'UnauthorizedError'); + assert.property(res.body, 'code'); + assert.equal(res.body.code, 401); + done(); + }); + }); + + it('delete with invalid token', (done) => { + api + .delete(apiPath + '2609') + .set({ + authorization: testHelper.invalidToken + }) + .expect(401) + .end((err, res) => { + if (err) { + return done(err); + } + assert.property(res.body, 'message'); + assert.equal(res.body.message, 'Failed to authenticate jwt token.'); + assert.property(res.body, 'code'); + assert.equal(res.body.code, 401); + done(); + }); + }); +}); + diff --git a/modules/mail/mail.test.js b/modules/mail/mail.test.js new file mode 100644 index 0000000..49e1efa --- /dev/null +++ b/modules/mail/mail.test.js @@ -0,0 +1,238 @@ +'use strict'; +/* + * Copyright (c) 2016 TopCoder, Inc. All rights reserved. + */ + + +const assert = require('chai').assert; +const testHelper = require('../../test/test-helper'); +const api = testHelper.api; +const apiVersion = testHelper.apiVersion; +const data = testHelper.data; + + +describe('Email functions', () => { + const apiPath = `${apiVersion}/emails`; + let emailId = 1; + + it('send email', (done) => { + api + .post(apiPath) + .set({ + authorization: testHelper.token + }) + .send(data.emailNew) + .expect(200) + .end((err, res) => { + if (err) { + return done(err); + } + + assert.property(res.body, 'id'); + assert.isNumber(res.body.id); + assert.property(res.body, 'result'); + const result = res.body.result; + assert.property(result, 'success'); + assert.equal(result.success, true); + assert.property(result, 'code'); + assert.equal(result.code, 200); + + emailId = res.body.id; + + done(); + }); + }); + + it('get email', (done) => { + api + .get(`${apiPath}/${emailId}`) + .set({ + authorization: testHelper.token + }) + .expect(200) + .end((err, res) => { + if (err) { + return done(err); + } + + assert.property(res.body, 'sender'); + assert.equal(res.body.sender, 'thkang91@gmail.com'); + assert.property(res.body, 'recipients'); + assert.property(res.body, 'subject'); + assert.equal(res.body.subject, 'test email'); + assert.property(res.body, 'html_body'); + assert.equal(res.body.html_body, '
test
'); + assert.property(res.body, 'text_body'); + assert.equal(res.body.text_body, 'test-test-test'); + assert.property(res.body, 'headers'); + assert.property(res.body, 'attachments'); + assert.property(res.body, 'delivery_time'); + + done(); + }); + }); + + + it('email api access with bad token', (done) => { + api + .get(`${apiPath}/${emailId}`) + .set({ + authorization: '123' + }) + .expect(401) + .end((err, res) => { + if (err) { + return done(err); + } + + assert.property(res.body, 'code'); + assert.equal(res.body.code, 401); + assert.property(res.body, 'message'); + assert.equal(res.body.message, 'UnauthorizedError'); + + done(); + }); + }); + + it('get Mailgun statistics', (done) => { + api + .get(`${apiPath}/stats`) + .set({ + authorization: testHelper.token + }) + .expect(200) + .end((err, res) => { + if (err) { + return done(err); + } + + assert.property(res.body, 'delivered'); + assert.isNumber(res.body.delivered); + + done(); + }); + }); + + it('send email with image attachment', (done) => { + api + .post(apiPath) + .set({ + authorization: testHelper.token + }) + .send(data.emailNewWithImage) + .expect(200) + .end((err, res) => { + if (err) { + return done(err); + } + + assert.property(res.body, 'id'); + assert.isNumber(res.body.id); + assert.property(res.body, 'result'); + const result = res.body.result; + assert.property(result, 'success'); + assert.equal(result.success, true); + assert.property(result, 'code'); + assert.equal(result.code, 200); + + done(); + }); + }); + + it('send email scheduled', (done) => { + const date = new Date(); + date.setDate(date.getDate() + 1); + data.emailNewScheduled.email.delivery_time = date.toISOString(); + + api + .post(apiPath) + .set({ + authorization: testHelper.token + }) + .send(data.emailNewScheduled) + .expect(200) + .end((err, res) => { + if (err) { + return done(err); + } + + assert.property(res.body, 'id'); + assert.isNumber(res.body.id); + assert.property(res.body, 'result'); + const result = res.body.result; + assert.property(result, 'success'); + assert.equal(result.success, true); + assert.property(result, 'code'); + assert.equal(result.code, 200); + + done(); + }); + }); + + it('get delivery status', (done) => { + api + .get(apiPath + '/' + data.emailIdDeliveried + '/deliveryStatus') + .set({ + authorization: testHelper.token + }) + .expect(200) + .end((err, res) => { + if (err) { + return done(err); + } + + assert.property(res.body, 'delivered'); + assert.equal(res.body.delivered, true); + assert.property(res.body, 'delivery_time'); + + done(); + }); + }); + + + it('get delivery status with not exist id', (done) => { + api + .get(`${apiPath}/99999/deliveryStatus`) + .set({ + authorization: testHelper.token + }) + .expect(404) + .end((err, res) => { + if (err) { + return done(err); + } + + assert.property(res.body, 'code'); + assert.equal(res.body.code, 404); + assert.property(res.body, 'message'); + assert.equal(res.body.message, 'Could not find emails by id 99999'); + + done(); + }); + }); + + it('delete email', (done) => { + api + .delete(`${apiPath}/${emailId}`) + .set({ + authorization: testHelper.token + }) + .expect(200) + .end((err, res) => { + if (err) { + return done(err); + } + + assert.property(res.body, 'id'); + assert.equal(res.body.id, emailId); + assert.property(res.body, 'result'); + const result = res.body.result; + assert.property(result, 'success'); + assert.equal(result.success, true); + assert.property(result, 'message'); + assert.equal(result.message, `email id ${emailId} successfully deleted.`); + + done(); + }); + }); +}); diff --git a/modules/sample/sample.test.js b/modules/sample/sample.test.js new file mode 100644 index 0000000..8ac487e --- /dev/null +++ b/modules/sample/sample.test.js @@ -0,0 +1,78 @@ +'use strict'; +/* + * Copyright (c) 2016 TopCoder, Inc. All rights reserved. + */ + + +const assert = require('chai').assert; +const _ = require('lodash'); +const testHelper = require('../../test/test-helper'); +const api = testHelper.api; +const apiVersion = testHelper.apiVersion; +const data = testHelper.data; + + +describe('Demo', () => { + const apiPath = `${apiVersion}/objects/demo`; + let demoId = undefined; + + it('create with complex fields(json,array)', (done) => { + api + .post(apiPath) + .set({ + authorization: testHelper.token + }) + .send(data.demo) + .expect(201) + .end((err, res) => { + if (err) { + return done(err); + } + + demoId = res.body; + + done(); + }); + }); + + it('get item', (done) => { + api + .get(`${apiPath}/${demoId}`) + .set({ + authorization: testHelper.token + }) + .expect(200) + .end((err, res) => { + if (err) { + return done(err); + } + + assert.equal(res.body.length, 5); + _.each(res.body, (item) => { + if (item.fieldName === 'id') { + assert.equal(item.fieldValue, '' + demoId); + } else if (item.fieldName === 'name') { + assert.equal(item.fieldValue, 'null'); + } + }); + + done(); + }); + }); + + it('reset', (done) => { + api + .post(`${apiVersion}/reset`) + .set({ + authorization: testHelper.token + }) + .expect(200) + .end((err) => { + if (err) { + return done(err); + } + + done(); + }); + }); +}); diff --git a/modules/sample/services/DemoService.js b/modules/sample/services/DemoService.js index ecc3d89..1f44718 100644 --- a/modules/sample/services/DemoService.js +++ b/modules/sample/services/DemoService.js @@ -17,7 +17,7 @@ pgp.pg.defaults.poolSize = config.dbConfig.poolSize; pgp.pg.defaults.poolIdleTimeout = config.dbConfig.poolIdleTimeout; // the folder for sql files. -const relativePath = '../../../../test_files/sql/'; +const relativePath = '../../../test_files/sql/'; /** * Build sql file @@ -44,7 +44,7 @@ function* reset() { yield db.none(sql('ddl.sql')); - const games = require('../../../../test_files/games.json'); + const games = require('../../../test_files/games.json'); yield db.tx((t) => { const inserts = []; diff --git a/package.json b/package.json index b968894..a222ed6 100644 --- a/package.json +++ b/package.json @@ -1,42 +1,48 @@ { - "name": "backend", - "version": "1.0.0", - "description": "", - "main": "app.js", - "engines": { - "node": "6.x.x" - }, - "scripts": { - "start": "node app.js", - "lint": "eslint . --ext .js || true", - "lint:fix": "eslint . --ext .js --fix || true" - }, - "author": "", - "license": "ISC", - "dependencies": { - "bluebird": "^3.4.0", - "body-parser": "^1.15.1", - "co": "^4.6.0", - "config": "^1.20.1", - "cors": "^2.7.1", - "express": "^4.13.4", - "get-parameter-names": "^0.3.0", - "glob": "^7.0.3", - "joi": "^8.1.0", - "jsonwebtoken": "^7.0.0", - "lodash": "^4.12.0", - "mailgun-js": "^0.7.11", - "morgan": "^1.7.0", - "passport": "^0.3.2", - "passport-http-bearer": "^1.0.1", - "pg-promise": "^4.2.3", - "winston": "^2.2.0" - }, - "devDependencies": { - "eslint": "^2.10.2", - "eslint-config-airbnb": "^9.0.1", - "eslint-plugin-import": "^1.8.1", - "eslint-plugin-jsx-a11y": "^1.2.2", - "eslint-plugin-react": "^5.1.1" - } + "name": "backend", + "version": "1.0.0", + "description": "", + "main": "app.js", + "engines": { + "node": "6.x.x" + }, + "scripts": { + "start": "node app.js", + "lint": "eslint . --ext .js || true", + "lint:fix": "eslint . --ext .js --fix || true", + "test": "NODE_ENV=test mocha modules/**/*.test.js", + "coverage": "istanbul cover node_modules/mocha/bin/_mocha modules/**/*.test.js" + }, + "author": "", + "license": "ISC", + "dependencies": { + "bluebird": "^3.4.0", + "body-parser": "^1.15.1", + "co": "^4.6.0", + "config": "^1.20.1", + "cors": "^2.7.1", + "express": "^4.13.4", + "get-parameter-names": "^0.3.0", + "glob": "^7.0.3", + "joi": "^8.1.0", + "jsonwebtoken": "^7.0.0", + "lodash": "^4.12.0", + "mailgun-js": "^0.7.11", + "morgan": "^1.7.0", + "passport": "^0.3.2", + "passport-http-bearer": "^1.0.1", + "pg-promise": "^4.2.3", + "winston": "^2.2.0" + }, + "devDependencies": { + "chai": "^3.5.0", + "eslint": "^2.10.2", + "eslint-config-airbnb": "^9.0.1", + "eslint-plugin-import": "^1.8.1", + "eslint-plugin-jsx-a11y": "^1.2.2", + "eslint-plugin-react": "^5.1.1", + "istanbul": "^0.4.5", + "mocha": "^3.0.2", + "supertest": "^2.0.0" + } } diff --git a/test/mocha.opts b/test/mocha.opts new file mode 100644 index 0000000..197a354 --- /dev/null +++ b/test/mocha.opts @@ -0,0 +1,2 @@ +--timeout 10000 +-- diff --git a/test/test-helper.js b/test/test-helper.js new file mode 100644 index 0000000..46a14fa --- /dev/null +++ b/test/test-helper.js @@ -0,0 +1,129 @@ +'use strict'; +/* + * Copyright (c) 2016 TopCoder, Inc. All rights reserved. + */ + +const request = require('supertest'); +const server = require('../app'); +const api = request(server); + +const apiVersion = '/api/v1'; + +const token = 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlcyI6WyJzdXBlci1hZG1pbiJdLCJlbWFpbCI6InN1cGVyQHRlc3QuY29tIiwiaWF0IjoxNDYzODA4OTA5fQ.tzcdo_YMQOtzRC15sktJUTwhzPD1ncyxUDx-eQ2Kvzs'; + +const noRolesToken = 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImVycm9yQHRlc3QuY29tIiwiaWF0IjoxNDYzODA4OTA5fQ.sn44yhluKglGZxIxGaNvU7Z7YKPNmQsfgAGyBYzlfck'; + +const invalidToken = 'Bearer xxx'; + +const userToken = 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlcyI6WyJ1c2VyIl0sImVtYWlsIjoidXNlckB0ZXN0LmNvbSIsImlhdCI6MTQ2MzgwODkwOX0.YaN_1--t6NvasjvjyhjztDSBID5VlYR8p_fsCiKQeQo'; + +const game2609 = [{ fieldName: 'id', fieldValue: '2609' }, { fieldName: 'gameId', fieldValue: '"7865"' }, { fieldName: 'name', fieldValue: '"10 Days in Africa"' }, { fieldName: 'image', fieldValue: '"http://cf.geekdo-images.com/images/pic1229634.jpg"' }, { fieldName: 'thumbnail', fieldValue: '"http://cf.geekdo-images.com/images/pic1229634_t.jpg"' }, { fieldName: 'minPlayers', fieldValue: '2' }, { fieldName: 'maxPlayers', fieldValue: '4' }, { fieldName: 'playingTime', fieldValue: '25' }, { fieldName: 'isExpansion', fieldValue: 'false' }, { fieldName: 'yearPublished', fieldValue: '2003' }, { fieldName: 'bggRating', fieldValue: '0' }, { fieldName: 'averageRating', fieldValue: '6.56779' }, { fieldName: 'rank', fieldValue: '1302' }, { fieldName: 'numPlays', fieldValue: '4' }, { fieldName: 'rating', fieldValue: '7' }, { fieldName: 'owned', fieldValue: 'true' }, { fieldName: 'preOrdered', fieldValue: 'false' }, { fieldName: 'forTrade', fieldValue: 'false' }, { fieldName: 'previousOwned', fieldValue: 'false' }, { fieldName: 'want', fieldValue: 'false' }, { fieldName: 'wantToPlay', fieldValue: 'false' }, { fieldName: 'wantToBuy', fieldValue: 'false' }, { fieldName: 'wishList', fieldValue: 'false' }, { fieldName: 'userComment', fieldValue: '""' }]; + +const game2611 = [{ fieldName: 'id', fieldValue: '2611' }, { fieldName: 'gameId', fieldValue: '"64956"' }, { fieldName: 'name', fieldValue: '"10 Days in the Americas"' }, { fieldName: 'image', fieldValue: '"http://cf.geekdo-images.com/images/pic1229649.jpg"' }, { fieldName: 'thumbnail', fieldValue: '"http://cf.geekdo-images.com/images/pic1229649_t.jpg"' }, { fieldName: 'minPlayers', fieldValue: '2' }, { fieldName: 'maxPlayers', fieldValue: '4' }, { fieldName: 'playingTime', fieldValue: '20' }, { fieldName: 'isExpansion', fieldValue: 'false' }, { fieldName: 'yearPublished', fieldValue: '2010' }, { fieldName: 'bggRating', fieldValue: '0' }, { fieldName: 'averageRating', fieldValue: '6.65927' }, { fieldName: 'rank', fieldValue: '2154' }, { fieldName: 'numPlays', fieldValue: '2' }, { fieldName: 'rating', fieldValue: '7' }, { fieldName: 'owned', fieldValue: 'true' }, { fieldName: 'preOrdered', fieldValue: 'false' }, { fieldName: 'forTrade', fieldValue: 'false' }, { fieldName: 'previousOwned', fieldValue: 'false' }, { fieldName: 'want', fieldValue: 'false' }, { fieldName: 'wantToPlay', fieldValue: 'false' }, { fieldName: 'wantToBuy', fieldValue: 'false' }, { fieldName: 'wishList', fieldValue: 'false' }, { fieldName: 'userComment', fieldValue: '""' }]; + +const game2616 = [{ fieldName: 'id', fieldValue: '2616' }, { fieldName: 'gameId', fieldValue: '"68448"' }, { fieldName: 'name', fieldValue: '"7 Wonders"' }, { fieldName: 'image', fieldValue: '"http://cf.geekdo-images.com/images/pic860217.jpg"' }, { fieldName: 'thumbnail', fieldValue: '"http://cf.geekdo-images.com/images/pic860217_t.jpg"' }, { fieldName: 'minPlayers', fieldValue: '2' }, { fieldName: 'maxPlayers', fieldValue: '7' }, { fieldName: 'playingTime', fieldValue: '30' }, { fieldName: 'isExpansion', fieldValue: 'false' }, { fieldName: 'yearPublished', fieldValue: '2010' }, { fieldName: 'bggRating', fieldValue: '0' }, { fieldName: 'averageRating', fieldValue: '7.85178' }, { fieldName: 'rank', fieldValue: '25' }, { fieldName: 'numPlays', fieldValue: '22' }, { fieldName: 'rating', fieldValue: '9' }, { fieldName: 'owned', fieldValue: 'true' }, { fieldName: 'preOrdered', fieldValue: 'false' }, { fieldName: 'forTrade', fieldValue: 'false' }, { fieldName: 'previousOwned', fieldValue: 'false' }, { fieldName: 'want', fieldValue: 'false' }, { fieldName: 'wantToPlay', fieldValue: 'false' }, { fieldName: 'wantToBuy', fieldValue: 'false' }, { fieldName: 'wishList', fieldValue: 'false' }, { fieldName: 'userComment', fieldValue: '""' }]; + + +const game3115 = [{ fieldName: 'id', fieldValue: '3115' }, { fieldName: 'gameId', fieldValue: '"62871"' }, { fieldName: 'name', fieldValue: '"Zombie Dice"' }, { fieldName: 'image', fieldValue: '"http://cf.geekdo-images.com/images/pic2664015.jpg"' }, { fieldName: 'thumbnail', fieldValue: '"http://cf.geekdo-images.com/images/pic2664015_t.jpg"' }, { fieldName: 'minPlayers', fieldValue: '2' }, { fieldName: 'maxPlayers', fieldValue: '99' }, { fieldName: 'playingTime', fieldValue: '20' }, { fieldName: 'isExpansion', fieldValue: 'false' }, { fieldName: 'yearPublished', fieldValue: '2010' }, { fieldName: 'bggRating', fieldValue: '0' }, { fieldName: 'averageRating', fieldValue: '6.26041' }, { fieldName: 'rank', fieldValue: '1459' }, { fieldName: 'numPlays', fieldValue: '13' }, { fieldName: 'rating', fieldValue: '6' }, { fieldName: 'owned', fieldValue: 'true' }, { fieldName: 'preOrdered', fieldValue: 'false' }, { fieldName: 'forTrade', fieldValue: 'false' }, { fieldName: 'previousOwned', fieldValue: 'false' }, { fieldName: 'want', fieldValue: 'false' }, { fieldName: 'wantToPlay', fieldValue: 'false' }, { fieldName: 'wantToBuy', fieldValue: 'false' }, { fieldName: 'wishList', fieldValue: 'false' }, { fieldName: 'userComment', fieldValue: '""' }]; + +const gameNew = [{ fieldName: '"gameId"', fieldValue: '"8888"' }, { fieldName: '"name"', fieldValue: '"test name"' }, { fieldName: '"image"', fieldValue: '"//cf.geekdo-images.com/images/pic1229634.jpg"' }, { fieldName: '"thumbnail"', fieldValue: '"//cf.geekdo-images.com/images/pic1229634_t.jpg"' }, { fieldName: '"minPlayers"', fieldValue: '"2"' }, { fieldName: '"maxPlayers"', fieldValue: '"4"' }, { fieldName: '"playingTime"', fieldValue: '"25"' }, { fieldName: '"isExpansion"', fieldValue: '"false"' }, { fieldName: '"yearPublished"', fieldValue: '"2003"' }, { fieldName: '"bggRating"', fieldValue: '"5"' }, { fieldName: '"averageRating"', fieldValue: '6.56779' }, { fieldName: '"rank"', fieldValue: '"1302"' }, { fieldName: '"numPlays"', fieldValue: '"4"' }, { fieldName: '"rating"', fieldValue: '"7"' }, { fieldName: '"owned"', fieldValue: '"true"' }, { fieldName: '"preOrdered"', fieldValue: '"false"' }, { fieldName: '"forTrade"', fieldValue: '"false"' }, { fieldName: '"previousOwned"', fieldValue: '"false"' }, { fieldName: '"want"', fieldValue: '"false"' }, { fieldName: '"wantToPlay"', fieldValue: '"false"' }, { fieldName: '"wantToBuy"', fieldValue: '"false"' }, { fieldName: '"wishList"', fieldValue: '"false"' }, { fieldName: '"userComment"', fieldValue: '"test comment"' }]; + +const gameUpdate = [{ fieldName: '"gameId"', fieldValue: '"8888"' }, { fieldName: '"name"', fieldValue: '"new test name"' }]; + +const emailIdDeliveried = 2; + +const emailAddress = 'thkang91@gmail.com'; + +const emailNew = { + email: { + sender: emailAddress, + recipients: [emailAddress], + subject: 'test email', + html_body: '
test
', + text_body: 'test-test-test', + headers: ['x-test-header:1234'], + attachments: [{ + file_name: 'blank.txt', + file_type: '"text"', + content_bytes: 'IAo=' + }] + } +}; + +const b64image = '/9j/4QewRXhpZgAATU0AKgAAAAgABwESAAMAAAABAAEAAAEaAAUAAAABAAAAYgEbAAUAAAABAAAAagEoAAMAAAABAAIAAAExAAIAAAAcAAAAcgEyAAIAAAAUAAAAjodpAAQAAAABAAAApAAAANAACvyAAAAnEAAK/IAAACcQQWRvYmUgUGhvdG9zaG9wIENTNSBXaW5kb3dzADIwMTY6MDU6MzAgMjM6NDM6MzgAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAAiaADAAQAAAABAAAAFQAAAAAAAAAGAQMAAwAAAAEABgAAARoABQAAAAEAAAEeARsABQAAAAEAAAEmASgAAwAAAAEAAgAAAgEABAAAAAEAAAEuAgIABAAAAAEAAAZ6AAAAAAAAAEgAAAABAAAASAAAAAH/2P/tAAxBZG9iZV9DTQAB/+4ADkFkb2JlAGSAAAAAAf/bAIQADAgICAkIDAkJDBELCgsRFQ8MDA8VGBMTFRMTGBEMDAwMDAwRDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAENCwsNDg0QDg4QFA4ODhQUDg4ODhQRDAwMDAwREQwMDAwMDBEMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwM/8AAEQgAFQCJAwEiAAIRAQMRAf/dAAQACf/EAT8AAAEFAQEBAQEBAAAAAAAAAAMAAQIEBQYHCAkKCwEAAQUBAQEBAQEAAAAAAAAAAQACAwQFBgcICQoLEAABBAEDAgQCBQcGCAUDDDMBAAIRAwQhEjEFQVFhEyJxgTIGFJGhsUIjJBVSwWIzNHKC0UMHJZJT8OHxY3M1FqKygyZEk1RkRcKjdDYX0lXiZfKzhMPTdePzRieUpIW0lcTU5PSltcXV5fVWZnaGlqa2xtbm9jdHV2d3h5ent8fX5/cRAAICAQIEBAMEBQYHBwYFNQEAAhEDITESBEFRYXEiEwUygZEUobFCI8FS0fAzJGLhcoKSQ1MVY3M08SUGFqKygwcmNcLSRJNUoxdkRVU2dGXi8rOEw9N14/NGlKSFtJXE1OT0pbXF1eX1VmZ2hpamtsbW5vYnN0dXZ3eHl6e3x//aAAwDAQACEQMRAD8A7vK+tfTMe11TRZc5hguYBtkc+5zmqrlfXnpVVZ2h7bj9FtjdP6zvSdYsPr/T3dNzLNw/QPmyp/8AJ52/1mLL6V9W+rdct9ZjPSx3HXIs0bH/AAY+lb/ZWj915c4uIyMYkfPerk/e+bOWUBEWDXCIu3Z1a3qBLjkeqP3Wu9o/sN+iiYtmax84rngj92Y/tfmrW6Z9S+i4Ia6xhyrx/hLTpP8AJqb7P+rWw7Dxy3a1gYBoNukfL6K57nOQmCZcrkOSXT3z7Z/x4cX/AKjb+HHM0cp4T/U9TRo6tdVil+fUd7O9cGR+8W7lFn1mwHOhzLGj94gH79rkW/Ce0GB6jDz8PNq549OufnfZahJcZaewafznf1Vm4viPxGOT2M8BDKPlHD/OR/qy/Tdbl+X5ecTxEnhF8V9Hrn5DGUeuA6xkBw9NpeSDxtYz3OWf03rVd3RqeoZk1Esb6nscA57gNKGaut3ud+j9PetKmptNLKW/RraGj4AQubw7qh0jo9xcLGdNcw5tbfc6uarcffbW3c5voXP3OXQi6F79Wias1t0dvH6pj3Xtx3NsoueC6tlzCwvA+l6ZPtdt/OZ9NDPWsQ+p6TLr/Re6u01VOcGuYSx7XQP5P5iBlZeL1DLwacGxmS+q4X2WVOD211tY9vvsZLW+tv8ATYz89D6V1Pp+LTlsyshlDm5eU6LCGSPXt91e+PU/sIodF/U8JmIzM9TfTbAqLAXOeXfRZXW0b32fyEqOo02797bMc1N3uF7Cz2fv7neza38/3exY2ODjNwuoXsNWIMjKsIcCPSZkOe7GtsZ/gm7fp/6H11c6pk4/Uum5eLgWNyrfTDi2o7mubIcafUZ+j33MGzZvSUnZ1vDeayW211XENqvsrc2txdoz3uHt9T/B7/pouT1KjHuGPtsuvLd/pVMLyG8b3x7WN3fvrP6p1Tp+d0u3Ew7W3ZWWw1U47T+kD3e0Gyr+cp+zu/SW+p/NKdWRR0/q2YM61tX2oVPousIY14YwVWVB7vZ6ldjX2+n/AMMkpJg9Tbbk9Rsst242OayPUGz0x6YfbvDw17Pd++jV9Xxn2Vscy2ptx202W1uYxxP0W7nD2Of+Y2zYsfJ3ZzesOwy6wepi2DYJL21iq2z0muH6TcxjvT/MuVh78XOrZR+2Tf6zm7amNqL9zXNe2a66vVr9N7ffu/mv8Ikp0z1PGGWcNu+y9rg17WMc7aHAPa+xwGxlfu+k5W1Rwf8AlDqP/G1/+ealeSU//9D1DI+y7P1rZ6c/4SIn+2iCIG3jtHEL5WSR6DdHU7ftfqpJfKqSCX6qUB6e87du+NYiYXywkmneO313/wAFI6v1Uot2a7Y51jx818rpJyH6oZ6cH04iddsc/JUOkDHFF0O3frmTBeA07/Ws3NZ7n/Rd9B6+ZkklP1UoV+nt/Rbdv8mI/BfLCSSn6nHp73Rt3/nRE/2k7/T2/pI2/wAqIn5r5XSSU/VSg3097tu3f+dET/aXywkkp+qkl8qpJKf/2f/tDtpQaG90b3Nob3AgMy4wADhCSU0EJQAAAAAAEAAAAAAAAAAAAAAAAAAAAAA4QklNBDoAAAAAAJMAAAAQAAAAAQAAAAAAC3ByaW50T3V0cHV0AAAABQAAAABDbHJTZW51bQAAAABDbHJTAAAAAFJHQkMAAAAASW50ZWVudW0AAAAASW50ZQAAAABDbHJtAAAAAE1wQmxib29sAQAAAA9wcmludFNpeHRlZW5CaXRib29sAAAAAAtwcmludGVyTmFtZVRFWFQAAAABAAAAOEJJTQQ7AAAAAAGyAAAAEAAAAAEAAAAAABJwcmludE91dHB1dE9wdGlvbnMAAAASAAAAAENwdG5ib29sAAAAAABDbGJyYm9vbAAAAAAAUmdzTWJvb2wAAAAAAENybkNib29sAAAAAABDbnRDYm9vbAAAAAAATGJsc2Jvb2wAAAAAAE5ndHZib29sAAAAAABFbWxEYm9vbAAAAAAASW50cmJvb2wAAAAAAEJja2dPYmpjAAAAAQAAAAAAAFJHQkMAAAADAAAAAFJkICBkb3ViQG/gAAAAAAAAAAAAR3JuIGRvdWJAb+AAAAAAAAAAAABCbCAgZG91YkBv4AAAAAAAAAAAAEJyZFRVbnRGI1JsdAAAAAAAAAAAAAAAAEJsZCBVbnRGI1JsdAAAAAAAAAAAAAAAAFJzbHRVbnRGI1B4bEBSAAAAAAAAAAAACnZlY3RvckRhdGFib29sAQAAAABQZ1BzZW51bQAAAABQZ1BzAAAAAFBnUEMAAAAATGVmdFVudEYjUmx0AAAAAAAAAAAAAAAAVG9wIFVudEYjUmx0AAAAAAAAAAAAAAAAU2NsIFVudEYjUHJjQFkAAAAAAAA4QklNA+0AAAAAABAASAAAAAEAAgBIAAAAAQACOEJJTQQmAAAAAAAOAAAAAAAAAAAAAD+AAAA4QklNBA0AAAAAAAQAAAB4OEJJTQQZAAAAAAAEAAAAHjhCSU0D8wAAAAAACQAAAAAAAAAAAQA4QklNJxAAAAAAAAoAAQAAAAAAAAACOEJJTQP1AAAAAABIAC9mZgABAGxmZgAGAAAAAAABAC9mZgABAKGZmgAGAAAAAAABADIAAAABAFoAAAAGAAAAAAABADUAAAABAC0AAAAGAAAAAAABOEJJTQP4AAAAAABwAAD/////////////////////////////A+gAAAAA/////////////////////////////wPoAAAAAP////////////////////////////8D6AAAAAD/////////////////////////////A+gAADhCSU0EAAAAAAAAAgAAOEJJTQQCAAAAAAACAAA4QklNBDAAAAAAAAEBADhCSU0ELQAAAAAABgABAAAAAjhCSU0ECAAAAAAAEAAAAAEAAAJAAAACQAAAAAA4QklNBB4AAAAAAAQAAAAAOEJJTQQaAAAAAANJAAAABgAAAAAAAAAAAAAAFQAAAIkAAAAKAFUAbgB0AGkAdABsAGUAZAAtADEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAIkAAAAVAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAEAAAAAAABudWxsAAAAAgAAAAZib3VuZHNPYmpjAAAAAQAAAAAAAFJjdDEAAAAEAAAAAFRvcCBsb25nAAAAAAAAAABMZWZ0bG9uZwAAAAAAAAAAQnRvbWxvbmcAAAAVAAAAAFJnaHRsb25nAAAAiQAAAAZzbGljZXNWbExzAAAAAU9iamMAAAABAAAAAAAFc2xpY2UAAAASAAAAB3NsaWNlSURsb25nAAAAAAAAAAdncm91cElEbG9uZwAAAAAAAAAGb3JpZ2luZW51bQAAAAxFU2xpY2VPcmlnaW4AAAANYXV0b0dlbmVyYXRlZAAAAABUeXBlZW51bQAAAApFU2xpY2VUeXBlAAAAAEltZyAAAAAGYm91bmRzT2JqYwAAAAEAAAAAAABSY3QxAAAABAAAAABUb3AgbG9uZwAAAAAAAAAATGVmdGxvbmcAAAAAAAAAAEJ0b21sb25nAAAAFQAAAABSZ2h0bG9uZwAAAIkAAAADdXJsVEVYVAAAAAEAAAAAAABudWxsVEVYVAAA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cGhvdG9zaG9wLzEuMC8iIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBXaW5kb3dzIiB4bXA6Q3JlYXRlRGF0ZT0iMjAxNi0wNS0zMFQyMzo0MzozOCswOTowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAxNi0wNS0zMFQyMzo0MzozOCswOTowMCIgeG1wOk1vZGlmeURhdGU9IjIwMTYtMDUtMzBUMjM6NDM6MzgrMDk6MDAiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6QzY4ODQyRTU3NDI2RTYxMTg4QTZENjZGQTUzQTMxNDQiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6QzU4ODQyRTU3NDI2RTYxMTg4QTZENjZGQTUzQTMxNDQiIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDpDNTg4NDJFNTc0MjZFNjExODhBNkQ2NkZBNTNBMzE0NCIgZGM6Zm9ybWF0PSJpbWFnZS9qcGVnIiBwaG90b3Nob3A6Q29sb3JNb2RlPSIzIiBwaG90b3Nob3A6SUNDUHJvZmlsZT0ic1JHQiBJRUM2MTk2Ni0yLjEiPiA8eG1wTU06SGlzdG9yeT4gPHJkZjpTZXE+IDxyZGY6bGkgc3RFdnQ6YWN0aW9uPSJjcmVhdGVkIiBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOkM1ODg0MkU1NzQyNkU2MTE4OEE2RDY2RkE1M0EzMTQ0IiBzdEV2dDp3aGVuPSIyMDE2LTA1LTMwVDIzOjQzOjM4KzA5OjAwIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ1M1IFdpbmRvd3MiLz4gPHJkZjpsaSBzdEV2dDphY3Rpb249InNhdmVkIiBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOkM2ODg0MkU1NzQyNkU2MTE4OEE2RDY2RkE1M0EzMTQ0IiBzdEV2dDp3aGVuPSIyMDE2LTA1LTMwVDIzOjQzOjM4KzA5OjAwIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ1M1IFdpbmRvd3MiIHN0RXZ0OmNoYW5nZWQ9Ii8iLz4gPC9yZGY6U2VxPiA8L3htcE1NOkhpc3Rvcnk+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDw/eHBhY2tldCBlbmQ9InciPz7/4gxYSUNDX1BST0ZJTEUAAQEAAAxITGlubwIQAABtbnRyUkdCIFhZWiAHzgACAAkABgAxAABhY3NwTVNGVAAAAABJRUMgc1JHQgAAAAAAAAAAAAAAAQAA9tYAAQAAAADTLUhQICAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABFjcHJ0AAABUAAAADNkZXNjAAABhAAAAGx3dHB0AAAB8AAAABRia3B0AAACBAAAABRyWFlaAAACGAAAABRnWFlaAAACLAAAABRiWFlaAAACQAAAABRkbW5kAAACVAAAAHBkbWRkAAACxAAAAIh2dWVkAAADTAAAAIZ2aWV3AAAD1AAAACRsdW1pAAAD+AAAABRtZWFzAAAEDAAAACR0ZWNoAAAEMAAAAAxyVFJDAAAEPAAACAxnVFJDAAAEPAAACAxiVFJDAAAEPAAACAx0ZXh0AAAAAENvcHlyaWdodCAoYykgMTk5OCBIZXdsZXR0LVBhY2thcmQgQ29tcGFueQAAZGVzYwAAAAAAAAASc1JHQiBJRUM2MTk2Ni0yLjEAAAAAAAAAAAAAABJzUkdCIElFQzYxOTY2LTIuMQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWFlaIAAAAAAAAPNRAAEAAAABFsxYWVogAAAAAAAAAAAAAAAAAAAAAFhZWiAAAAAAAABvogAAOPUAAAOQWFlaIAAAAAAAAGKZ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'; + +const emailNewWithImage = { + email: { + sender: emailAddress, + recipients: [emailAddress], + subject: 'test email', + html_body: '
test
', + text_body: 'test-test-test', + headers: ['x-test-header:1234'], + attachments: [{ + file_name: 'blank.txt', + file_type: '"text"', + content_bytes: 'IAo=' + }, { + file_name: 'logo.jpg', + file_type: 'image/jpg', + content_bytes: b64image + }] + } +}; + +const emailNewScheduled = { + email: { + sender: emailAddress, + recipients: [emailAddress], + subject: 'test email', + html_body: '
test
', + text_body: 'test-test-test', + headers: ['x-test-header:1234'], + attachments: [{ + file_name: 'blank.txt', + file_type: '"text"', + content_bytes: 'IAo=' + }], + delivery_time: '2016-09-13T12:25:23+0800' + } +}; + + +const demo = [ + { + fieldName: '"json"', + fieldValue: '{"a":1,"b":2,"c":3}' + }, + { + fieldName: '"tags"', + fieldValue: '[7,8,9]' + }, + { + fieldName: '"timestamp"', + fieldValue: '"2016-05-20T09:21:03.322Z"' + } +]; + +// Exports +module.exports = { + api, + apiVersion, + token, + noRolesToken, + invalidToken, + userToken, + data: { + game2609, + game2611, + game2616, + game3115, + gameNew, + gameUpdate, + emailNew, + emailNewWithImage, + emailNewScheduled, + emailIdDeliveried, + demo + } +};