From b68861681c8c5c03725130be4e6d5a337a0ff28a Mon Sep 17 00:00:00 2001 From: Blade Barringer Date: Wed, 13 Jan 2016 12:23:24 -0600 Subject: [PATCH] tests(integration): Create separate helper for api integration tests --- test/api/v3/integration/notFound.test.js | 2 +- .../integration/tags/DELETE-tags_id.test.js | 2 +- test/api/v3/integration/tags/GET-tags.test.js | 2 +- .../v3/integration/tags/GET-tags_id.test.js | 2 +- .../api/v3/integration/tags/POST-tags.test.js | 2 +- .../v3/integration/tags/PUT-tags_id.test.js | 2 +- .../integration/tasks/DELETE-tasks_id.test.js | 2 +- .../v3/integration/tasks/GET-tasks.test.js | 2 +- .../v3/integration/tasks/GET-tasks_id.test.js | 2 +- .../v3/integration/tasks/POST-tasks.test.js | 2 +- .../POST-tasks_id_score_direction.test.js | 2 +- .../v3/integration/tasks/PUT-tasks_id.test.js | 2 +- ...LETE-tasks_taskId_checklist_itemId.test.js | 2 +- .../POST-tasks_taskId_checklist.test.js | 2 +- ...asks_taskId_checklist_itemId_score.test.js | 2 +- .../PUT-tasks_taskId_checklist_itemId.test.js | 2 +- .../DELETE-tasks_taskId_tags_tagId.test.js | 2 +- .../tags/POST-tasks_taskId_tags_tagId.test.js | 2 +- test/api/v3/integration/user/GET-user.test.js | 2 +- .../user/auth/POST-register_local.test.js | 2 +- test/helpers/api-v3-integration.helper.js | 300 ++++++++++++++++++ 21 files changed, 320 insertions(+), 20 deletions(-) create mode 100644 test/helpers/api-v3-integration.helper.js diff --git a/test/api/v3/integration/notFound.test.js b/test/api/v3/integration/notFound.test.js index 6539a0037d..959e0d8b28 100644 --- a/test/api/v3/integration/notFound.test.js +++ b/test/api/v3/integration/notFound.test.js @@ -1,4 +1,4 @@ -import { requester } from '../../../helpers/api-integration.helper'; +import { requester } from '../../../helpers/api-v3-integration.helper'; describe('notFound Middleware', () => { it('returns a 404 error when the resource is not found', async () => { diff --git a/test/api/v3/integration/tags/DELETE-tags_id.test.js b/test/api/v3/integration/tags/DELETE-tags_id.test.js index 9d6b53c531..3aefe11d48 100644 --- a/test/api/v3/integration/tags/DELETE-tags_id.test.js +++ b/test/api/v3/integration/tags/DELETE-tags_id.test.js @@ -1,6 +1,6 @@ import { generateUser, -} from '../../../../helpers/api-integration.helper'; +} from '../../../../helpers/api-v3-integration.helper'; describe('DELETE /tags/:tagId', () => { let user; diff --git a/test/api/v3/integration/tags/GET-tags.test.js b/test/api/v3/integration/tags/GET-tags.test.js index 5281bd35af..6c27da12c3 100644 --- a/test/api/v3/integration/tags/GET-tags.test.js +++ b/test/api/v3/integration/tags/GET-tags.test.js @@ -1,6 +1,6 @@ import { generateUser, -} from '../../../../helpers/api-integration.helper'; +} from '../../../../helpers/api-v3-integration.helper'; describe('GET /tags', () => { let user; diff --git a/test/api/v3/integration/tags/GET-tags_id.test.js b/test/api/v3/integration/tags/GET-tags_id.test.js index adccff6504..1189c2af24 100644 --- a/test/api/v3/integration/tags/GET-tags_id.test.js +++ b/test/api/v3/integration/tags/GET-tags_id.test.js @@ -1,6 +1,6 @@ import { generateUser, -} from '../../../../helpers/api-integration.helper'; +} from '../../../../helpers/api-v3-integration.helper'; describe('GET /tags/:tagId', () => { let user; diff --git a/test/api/v3/integration/tags/POST-tags.test.js b/test/api/v3/integration/tags/POST-tags.test.js index 7b7b5c4a34..85947e5add 100644 --- a/test/api/v3/integration/tags/POST-tags.test.js +++ b/test/api/v3/integration/tags/POST-tags.test.js @@ -1,6 +1,6 @@ import { generateUser, -} from '../../../../helpers/api-integration.helper'; +} from '../../../../helpers/api-v3-integration.helper'; describe('POST /tags', () => { let user; diff --git a/test/api/v3/integration/tags/PUT-tags_id.test.js b/test/api/v3/integration/tags/PUT-tags_id.test.js index c2576a3f0c..4653f284d6 100644 --- a/test/api/v3/integration/tags/PUT-tags_id.test.js +++ b/test/api/v3/integration/tags/PUT-tags_id.test.js @@ -1,6 +1,6 @@ import { generateUser, -} from '../../../../helpers/api-integration.helper'; +} from '../../../../helpers/api-v3-integration.helper'; describe('PUT /tags/:tagId', () => { let user; diff --git a/test/api/v3/integration/tasks/DELETE-tasks_id.test.js b/test/api/v3/integration/tasks/DELETE-tasks_id.test.js index 59866dab3b..671a278d1d 100644 --- a/test/api/v3/integration/tasks/DELETE-tasks_id.test.js +++ b/test/api/v3/integration/tasks/DELETE-tasks_id.test.js @@ -1,7 +1,7 @@ import { generateUser, translate as t, -} from '../../../../helpers/api-integration.helper'; +} from '../../../../helpers/api-v3-integration.helper'; describe('DELETE /tasks/:id', () => { let user; diff --git a/test/api/v3/integration/tasks/GET-tasks.test.js b/test/api/v3/integration/tasks/GET-tasks.test.js index 4c169b3f6c..8d7bd154d0 100644 --- a/test/api/v3/integration/tasks/GET-tasks.test.js +++ b/test/api/v3/integration/tasks/GET-tasks.test.js @@ -1,6 +1,6 @@ import { generateUser, -} from '../../../../helpers/api-integration.helper'; +} from '../../../../helpers/api-v3-integration.helper'; import Q from 'q'; describe('GET /tasks', () => { diff --git a/test/api/v3/integration/tasks/GET-tasks_id.test.js b/test/api/v3/integration/tasks/GET-tasks_id.test.js index 84df18187a..4b0d74543d 100644 --- a/test/api/v3/integration/tasks/GET-tasks_id.test.js +++ b/test/api/v3/integration/tasks/GET-tasks_id.test.js @@ -1,7 +1,7 @@ import { generateUser, translate as t, -} from '../../../../helpers/api-integration.helper'; +} from '../../../../helpers/api-v3-integration.helper'; import { v4 as generateUUID } from 'uuid'; describe('GET /tasks/:id', () => { diff --git a/test/api/v3/integration/tasks/POST-tasks.test.js b/test/api/v3/integration/tasks/POST-tasks.test.js index 7a8804be6c..5966aee05a 100644 --- a/test/api/v3/integration/tasks/POST-tasks.test.js +++ b/test/api/v3/integration/tasks/POST-tasks.test.js @@ -1,7 +1,7 @@ import { generateUser, translate as t, -} from '../../../../helpers/api-integration.helper'; +} from '../../../../helpers/api-v3-integration.helper'; describe('POST /tasks', () => { let user; diff --git a/test/api/v3/integration/tasks/POST-tasks_id_score_direction.test.js b/test/api/v3/integration/tasks/POST-tasks_id_score_direction.test.js index b01a54775c..cdfb94947e 100644 --- a/test/api/v3/integration/tasks/POST-tasks_id_score_direction.test.js +++ b/test/api/v3/integration/tasks/POST-tasks_id_score_direction.test.js @@ -1,7 +1,7 @@ import { generateUser, translate as t, -} from '../../../../helpers/api-integration.helper'; +} from '../../../../helpers/api-v3-integration.helper'; import { v4 as generateUUID } from 'uuid'; describe('POST /tasks/:id/score/:direction', () => { diff --git a/test/api/v3/integration/tasks/PUT-tasks_id.test.js b/test/api/v3/integration/tasks/PUT-tasks_id.test.js index bbb42fa69b..ff42c9c3de 100644 --- a/test/api/v3/integration/tasks/PUT-tasks_id.test.js +++ b/test/api/v3/integration/tasks/PUT-tasks_id.test.js @@ -1,6 +1,6 @@ import { generateUser, -} from '../../../../helpers/api-integration.helper'; +} from '../../../../helpers/api-v3-integration.helper'; import { v4 as generateUUID } from 'uuid'; describe('PUT /tasks/:id', () => { diff --git a/test/api/v3/integration/tasks/checklists/DELETE-tasks_taskId_checklist_itemId.test.js b/test/api/v3/integration/tasks/checklists/DELETE-tasks_taskId_checklist_itemId.test.js index 6c690c6eb9..094e3ae21f 100644 --- a/test/api/v3/integration/tasks/checklists/DELETE-tasks_taskId_checklist_itemId.test.js +++ b/test/api/v3/integration/tasks/checklists/DELETE-tasks_taskId_checklist_itemId.test.js @@ -1,7 +1,7 @@ import { generateUser, translate as t, -} from '../../../../../helpers/api-integration.helper'; +} from '../../../../../helpers/api-v3-integration.helper'; import { v4 as generateUUID } from 'uuid'; describe('DELETE /tasks/:taskId/checklist/:itemId', () => { diff --git a/test/api/v3/integration/tasks/checklists/POST-tasks_taskId_checklist.test.js b/test/api/v3/integration/tasks/checklists/POST-tasks_taskId_checklist.test.js index 3e1dfb8494..45a6e94150 100644 --- a/test/api/v3/integration/tasks/checklists/POST-tasks_taskId_checklist.test.js +++ b/test/api/v3/integration/tasks/checklists/POST-tasks_taskId_checklist.test.js @@ -1,7 +1,7 @@ import { generateUser, translate as t, -} from '../../../../../helpers/api-integration.helper'; +} from '../../../../../helpers/api-v3-integration.helper'; import { v4 as generateUUID } from 'uuid'; describe('POST /tasks/:taskId/checklist/', () => { diff --git a/test/api/v3/integration/tasks/checklists/POST-tasks_taskId_checklist_itemId_score.test.js b/test/api/v3/integration/tasks/checklists/POST-tasks_taskId_checklist_itemId_score.test.js index 345e1c400e..84fbbe7562 100644 --- a/test/api/v3/integration/tasks/checklists/POST-tasks_taskId_checklist_itemId_score.test.js +++ b/test/api/v3/integration/tasks/checklists/POST-tasks_taskId_checklist_itemId_score.test.js @@ -1,7 +1,7 @@ import { generateUser, translate as t, -} from '../../../../../helpers/api-integration.helper'; +} from '../../../../../helpers/api-v3-integration.helper'; import { v4 as generateUUID } from 'uuid'; describe('POST /tasks/:taskId/checklist/:itemId/score', () => { diff --git a/test/api/v3/integration/tasks/checklists/PUT-tasks_taskId_checklist_itemId.test.js b/test/api/v3/integration/tasks/checklists/PUT-tasks_taskId_checklist_itemId.test.js index 0d93f6cc64..cbc4ff5b21 100644 --- a/test/api/v3/integration/tasks/checklists/PUT-tasks_taskId_checklist_itemId.test.js +++ b/test/api/v3/integration/tasks/checklists/PUT-tasks_taskId_checklist_itemId.test.js @@ -1,7 +1,7 @@ import { generateUser, translate as t, -} from '../../../../../helpers/api-integration.helper'; +} from '../../../../../helpers/api-v3-integration.helper'; import { v4 as generateUUID } from 'uuid'; describe('PUT /tasks/:taskId/checklist/:itemId', () => { diff --git a/test/api/v3/integration/tasks/tags/DELETE-tasks_taskId_tags_tagId.test.js b/test/api/v3/integration/tasks/tags/DELETE-tasks_taskId_tags_tagId.test.js index 7ddeb6fe14..e2b5c8b015 100644 --- a/test/api/v3/integration/tasks/tags/DELETE-tasks_taskId_tags_tagId.test.js +++ b/test/api/v3/integration/tasks/tags/DELETE-tasks_taskId_tags_tagId.test.js @@ -1,7 +1,7 @@ import { generateUser, translate as t, -} from '../../../../../helpers/api-integration.helper'; +} from '../../../../../helpers/api-v3-integration.helper'; import { v4 as generateUUID } from 'uuid'; describe('DELETE /tasks/:taskId/tags/:tagId', () => { diff --git a/test/api/v3/integration/tasks/tags/POST-tasks_taskId_tags_tagId.test.js b/test/api/v3/integration/tasks/tags/POST-tasks_taskId_tags_tagId.test.js index 8377d5a012..1466be73f1 100644 --- a/test/api/v3/integration/tasks/tags/POST-tasks_taskId_tags_tagId.test.js +++ b/test/api/v3/integration/tasks/tags/POST-tasks_taskId_tags_tagId.test.js @@ -1,7 +1,7 @@ import { generateUser, translate as t, -} from '../../../../../helpers/api-integration.helper'; +} from '../../../../../helpers/api-v3-integration.helper'; import { v4 as generateUUID } from 'uuid'; describe('POST /tasks/:taskId/tags/:tagId', () => { diff --git a/test/api/v3/integration/user/GET-user.test.js b/test/api/v3/integration/user/GET-user.test.js index 8fbedca8cc..692c4c2ab9 100644 --- a/test/api/v3/integration/user/GET-user.test.js +++ b/test/api/v3/integration/user/GET-user.test.js @@ -1,6 +1,6 @@ import { generateUser, -} from '../../../../helpers/api-integration.helper'; +} from '../../../../helpers/api-v3-integration.helper'; describe('GET /user', () => { let user; diff --git a/test/api/v3/integration/user/auth/POST-register_local.test.js b/test/api/v3/integration/user/auth/POST-register_local.test.js index 68ff003645..5255a31780 100644 --- a/test/api/v3/integration/user/auth/POST-register_local.test.js +++ b/test/api/v3/integration/user/auth/POST-register_local.test.js @@ -2,7 +2,7 @@ import { generateUser, requester, translate as t, -} from '../../../../../helpers/api-integration.helper'; +} from '../../../../../helpers/api-v3-integration.helper'; import { v4 as generateRandomUserName } from 'uuid'; import { each } from 'lodash'; diff --git a/test/helpers/api-v3-integration.helper.js b/test/helpers/api-v3-integration.helper.js new file mode 100644 index 0000000000..dba72ca99d --- /dev/null +++ b/test/helpers/api-v3-integration.helper.js @@ -0,0 +1,300 @@ +/* eslint-disable no-use-before-define */ + +import { + assign, + each, + isEmpty, + set, + times, +} from 'lodash'; +import { MongoClient as mongo } from 'mongodb'; +import { v4 as generateUUID } from 'uuid'; +import superagent from 'superagent'; +import i18n from '../../common/script/src/i18n'; +i18n.translations = require('../../website/src/libs/api-v3/i18n').translations; + +const API_TEST_SERVER_PORT = 3003; + +class ApiUser { + constructor (options) { + assign(this, options); + + this.get = _requestMaker(this, 'get'); + this.post = _requestMaker(this, 'post'); + this.put = _requestMaker(this, 'put'); + this.del = _requestMaker(this, 'del'); + } + + update (options) { + return new Promise((resolve) => { + _updateDocument('users', this, options, resolve); + }); + } +} + +// Sets up an abject that can make all REST requests +// If a user is passed in, the uuid and api token of +// the user are used to make the requests +export function requester (user = {}, additionalSets) { + return { + get: _requestMaker(user, 'get', additionalSets), + post: _requestMaker(user, 'post', additionalSets), + put: _requestMaker(user, 'put', additionalSets), + del: _requestMaker(user, 'del', additionalSets), + }; +} + +// Use this to verify error messages returned by the server +// That way, if the translated string changes, the test +// will not break. NOTE: it checks agains errors with string as well. +export function translate (key, variables) { + const STRING_ERROR_MSG = 'Error processing the string. Please see Help > Report a Bug.'; + const STRING_DOES_NOT_EXIST_MSG = /^String '.*' not found.$/; + + let translatedString = i18n.t(key, variables); + + expect(translatedString).to.not.be.empty; + expect(translatedString).to.not.eql(STRING_ERROR_MSG); + expect(translatedString).to.not.match(STRING_DOES_NOT_EXIST_MSG); + + return translatedString; +} + +// Useful for checking things that have been deleted, +// but you no longer have access to, +// like private parties or users +export function checkExistence (collectionName, id) { + return new Promise((resolve, reject) => { + mongo.connect('mongodb://localhost/habitrpg_test', (connectionError, db) => { + if (connectionError) return reject(connectionError); + let collection = db.collection(collectionName); + + collection.find({_id: id}, {_id: 1}).limit(1).toArray((findError, docs) => { + if (findError) return reject(findError); + + let exists = docs.length > 0; + + db.close(); + resolve(exists); + }); + }); + }); +} + +// Creates a new user and returns it +// If you need the user to have specific requirements, +// such as a balance > 0, just pass in the adjustment +// to the update object. If you want to adjust a nested +// paramter, such as the number of wolf eggs the user has, +// , you can do so by passing in the full path as a string: +// { 'items.eggs.Wolf': 10 } +export function generateUser (update = {}) { + let username = generateUUID(); + let password = 'password'; + let email = `${username}@example.com`; + + let request = _requestMaker({}, 'post'); + + return new Promise((resolve, reject) => { + request('/user/auth/local/register', { + username, + email, + password, + confirmPassword: password, + }).then((user) => { + _updateDocument('users', user, update, () => { + let apiUser = new ApiUser(user); + + resolve(apiUser); + }); + }).catch(reject); + }); +} + +// Generates a new group. Requires a user object, which +// will will become the groups leader. Takes an update +// argument which will update group +export function generateGroup (leader, update = {}) { + let request = _requestMaker(leader, 'post'); + + return new Promise((resolve, reject) => { + request('/groups').then((group) => { + _updateDocument('groups', group, update, () => { + resolve(group); + }).catch(reject); + }); + }); +} + +// This is generate group + the ability to create +// real users to populate it. The settings object +// takes in: +// members: Number - the number of group members to create. Defaults to 0. +// inivtes: Number - the number of users to create and invite to the group. Defaults to 0. +// groupDetails: Object - how to initialize the group +// leaderDetails: Object - defaults for the leader, defaults with a gem balance so the user +// can create the group +// +// Returns an object with +// members: an array of user objects that correspond to the members of the group +// invitees: an array of user objects that correspond to the invitees of the group +// leader: the leader user object +// group: the group object +export function createAndPopulateGroup (settings = {}) { + let request; + let leader; + let members; + let invitees; + let group; + + let numberOfMembers = settings.members || 0; + let numberOfInvites = settings.invites || 0; + let groupDetails = settings.groupDetails; + let leaderDetails = settings.leaderDetails || { balance: 10 }; + + let leaderPromise = generateUser(leaderDetails); + + let memberPromises = Promise.all( + times(numberOfMembers, () => { + return generateUser(); + }) + ); + + let invitePromises = Promise.all( + times(numberOfInvites, () => { + return generateUser(); + }) + ); + + return new Promise((resolve, reject) => { + return leaderPromise.then((user) => { + leader = user; + request = _requestMaker(leader, 'post'); + return memberPromises; + }).then((users) => { + members = users; + groupDetails.members = groupDetails.members || [leader._id]; + + each(members, (member) => { + groupDetails.members.push(member._id); + }); + + return generateGroup(leader, groupDetails); + }).then((createdGroup) => { + group = createdGroup; + return invitePromises; + }).then((users) => { + invitees = users; + + let invitationPromises = []; + + each(invitees, (invitee) => { + let invitePromise = request(`/groups/${group._id}/invite`, { + uuids: [invitee._id], + }); + + invitationPromises.push(invitePromise); + }); + + return Promise.all(invitationPromises); + }).then(() => { + resolve({ + leader, + group, + members, + invitees, + }); + }).catch(reject); + }); +} + +// Specifically helpful for the GET /groups tests, +// resets the db to an empty state and creates a tavern document +export function resetHabiticaDB () { + return new Promise((resolve, reject) => { + mongo.connect('mongodb://localhost/habitrpg_test', (err, db) => { + if (err) return reject(err); + + db.dropDatabase((dbErr) => { + if (dbErr) return reject(dbErr); + let groups = db.collection('groups'); + + groups.insertOne({ + _id: 'habitrpg', + chat: [], + leader: '9', + name: 'HabitRPG', + type: 'guild', + privacy: 'public', + members: [], + }, (insertErr) => { + if (insertErr) return reject(insertErr); + + db.close(); + resolve(); + }); + }); + }); + }); +} + +function _requestMaker (user, method, additionalSets) { + return (route, send, query) => { + return new Promise((resolve, reject) => { + let request = superagent[method](`http://localhost:${API_TEST_SERVER_PORT}/api/v3${route}`) + .accept('application/json'); + + if (user && user._id && user.apiToken) { + request + .set('x-api-user', user._id) + .set('x-api-key', user.apiToken); + } + + if (additionalSets) { + request.set(additionalSets); + } + + request + .query(query) + .send(send) + .end((err, response) => { + if (err) { + if (!err.response) return reject(err); + + return reject({ + code: err.status, + error: err.response.body.error, + message: err.response.body.message, + }); + } + + resolve(response.body); + }); + }); + }; +} + +function _updateDocument (collectionName, doc, update, cb) { + if (isEmpty(update)) { + return cb(); + } + + mongo.connect('mongodb://localhost/habitrpg_test', (connectErr, db) => { + if (connectErr) throw new Error(`Error connecting to database when updating ${collectionName} collection: ${connectErr}`); + + let collection = db.collection(collectionName); + + collection.updateOne({ _id: doc._id }, { $set: update }, (updateErr) => { + if (updateErr) throw new Error(`Error updating ${collectionName}: ${updateErr}`); + _updateLocalDocument(doc, update); + db.close(); + cb(); + }); + }); +} + +function _updateLocalDocument (doc, update) { + each(update, (value, param) => { + set(doc, param, value); + }); +}