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 = '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AAAAAAAEAAAAHjhCSU0D8wAAAAAACQAAAAAAAAAAAQA4QklNJxAAAAAAAAoAAQAAAAAAAAACOEJJTQP1AAAAAABIAC9mZgABAGxmZgAGAAAAAAABAC9mZgABAKGZmgAGAAAAAAABADIAAAABAFoAAAAGAAAAAAABADUAAAABAC0AAAAGAAAAAAABOEJJTQP4AAAAAABwAAD/////////////////////////////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 + } +}; pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy