Merge branch 'api-v3-groups' into sabrecat/quest-accept

This commit is contained in:
Blade Barringer
2016-01-30 11:10:33 -06:00
52 changed files with 2387 additions and 469 deletions
+5 -3
View File
@@ -1,5 +1,7 @@
# So you want to write API integration tests?
@TODO rewrite
That's great! This README will serve as a quick primer for style conventions and practices for these tests.
## What is this?
@@ -73,7 +75,7 @@ POST-groups_id_leave.test.js
To mitigate [callback hell](http://callbackhell.com/) :imp:, we've written a helper method to generate a user object that can make http requests that [return promises](https://babeljs.io/docs/learn-es2015/#promises). This makes it very easy to chain together commands. All you need to do to make a subsequent request is return another promise and then call `.then((result) => {})` on the surrounding block, like so:
```js
it('does something', () => {
it('does something', () => {
let user;
return generateUser().then((_user) => { // We return the initial promise so this test can be run asyncronously
@@ -97,7 +99,7 @@ it('does something', () => {
If the test is simple, you can use the [chai-as-promised](http://chaijs.com/plugins/chai-as-promised) `return expect(somePromise).to.eventually` syntax to make your assertion.
```js
it('makes the party creator the leader automatically', () => {
it('makes the party creator the leader automatically', () => {
return expect(user.post('/groups', {
type: 'party',
})).to.eventually.have.deep.property('leader._id', user._id);
@@ -107,7 +109,7 @@ it('makes the party creator the leader automatically', () => {
If the test is checking that the request returns an error, use the `.eventually.be.rejected.and.eql` syntax.
```js
it('returns an error', () => {
it('returns an error', () => {
return expect(user.get('/groups/id-of-a-party-that-user-does-not-belong-to'))
.to.eventually.be.rejected.and.eql({
code: 404,
+4
View File
@@ -0,0 +1,4 @@
# How to run tests:
1. `npm test` is equivalent to `gulp test:api-v3` which will run, in order, `gulp lint`, `gulp test:api-v3:unit` and `gulp test:api-v3:integration`. If one of these fails, the whole `npm test` command blocks and fails. Each of these commands can also be run as a standalone command.
2. To run the server and the integrations tests in two different terminals (to better inspect the output in the server) run `npm start` in one and `npm test:api-v3:integration:separate-server` in the other
@@ -0,0 +1,95 @@
import {
generateUser,
generateChallenge,
createAndPopulateGroup,
sleep,
checkExistence,
translate as t,
} from '../../../../helpers/api-v3-integration.helper';
import { v4 as generateUUID } from 'uuid';
describe('DELETE /challenges/:challengeId', () => {
it('returns error when challengeId is not a valid UUID', async () => {
let user = await generateUser();
await expect(user.del(`/challenges/test`)).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('invalidReqParams'),
});
});
it('returns error when challengeId is not for a valid challenge', async () => {
let user = await generateUser();
await expect(user.del(`/challenges/${generateUUID()}`)).to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
message: t('challengeNotFound'),
});
});
context('Deleting a valid challenge', () => {
let groupLeader;
let group;
let challenge;
let taskText = 'A challenge task text';
beforeEach(async () => {
let populatedGroup = await createAndPopulateGroup();
groupLeader = populatedGroup.groupLeader;
group = populatedGroup.group;
challenge = await generateChallenge(groupLeader, group);
await groupLeader.post(`/tasks/challenge/${challenge._id}`, [
{type: 'habit', text: taskText},
]);
await challenge.sync();
});
it('returns an error when user doesn\'t have permissions to delete the challenge', async () => {
let user = await generateUser();
await expect(user.del(`/challenges/${challenge._id}`)).to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('onlyLeaderDeleteChal'),
});
});
it('deletes challenge', async () => {
await groupLeader.del(`/challenges/${challenge._id}`);
await sleep(0.5);
await expect(checkExistence('challenges', challenge._id)).to.eventually.equal(false);
});
it('refunds gems to group leader', async () => {
let oldBalance = (await groupLeader.sync()).balance;
await groupLeader.del(`/challenges/${challenge._id}`);
await sleep(0.5);
await expect(groupLeader.sync()).to.eventually.have.property('balance', oldBalance + challenge.prize / 4);
});
it('sets broken and doesn\'t set winner flags for user\'s challenge tasks', async () => {
await groupLeader.del(`/challenges/${challenge._id}`);
await sleep(0.5);
let tasks = await groupLeader.get('/tasks/user');
let testTask = _.find(tasks, (task) => {
return task.text === taskText;
});
expect(testTask.challenge.broken).to.eql('CHALLENGE_DELETED');
expect(testTask.challenge.winner).to.be.null;
});
});
});
@@ -0,0 +1,71 @@
import {
generateUser,
createAndPopulateGroup,
generateChallenge,
translate as t,
sleep,
} from '../../../../helpers/api-v3-integration.helper';
import { v4 as generateUUID } from 'uuid';
describe('GET /challenges/:challengeId/export/csv', () => {
let groupLeader;
let group;
let challenge;
let members;
let user;
beforeEach(async () => {
user = await generateUser();
let populatedGroup = await createAndPopulateGroup({
members: 3,
});
groupLeader = populatedGroup.groupLeader;
group = populatedGroup.group;
members = populatedGroup.members;
challenge = await generateChallenge(groupLeader, group);
await members[0].post(`/challenges/${challenge._id}/join`);
await members[1].post(`/challenges/${challenge._id}/join`);
await members[2].post(`/challenges/${challenge._id}/join`);
await groupLeader.post(`/tasks/challenge/${challenge._id}`, [
{type: 'habit', text: 'Task 1'},
{type: 'todo', text: 'Task 2'},
]);
await sleep(0.5); // Make sure tasks are synced to the users
await members[0].sync();
await members[1].sync();
await members[2].sync();
});
it('fails if challenge doesn\'t exists', async () => {
await expect(user.get(`/challenges/${generateUUID()}/export/csv`)).to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
message: t('challengeNotFound'),
});
});
it('fails if user doesn\'t have access to the challenge', async () => {
await expect(user.get(`/challenges/${challenge._id}/export/csv`)).to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
message: t('challengeNotFound'),
});
});
it('should return a valid CSV file with export data', async () => {
let res = await members[0].get(`/challenges/${challenge._id}/export/csv`);
let sortedMembers = _.sortBy([members[0], members[1], members[2], groupLeader], '_id');
let splitRes = res.split('\n');
expect(splitRes[0]).to.equal('UUID,name,Task,Value,Notes,Task,Value,Notes');
expect(splitRes[1]).to.equal(`${sortedMembers[0]._id},${sortedMembers[0].profile.name},habit:Task 1,0,,todo:Task 2,0,`);
expect(splitRes[2]).to.equal(`${sortedMembers[1]._id},${sortedMembers[1].profile.name},habit:Task 1,0,,todo:Task 2,0,`);
expect(splitRes[3]).to.equal(`${sortedMembers[2]._id},${sortedMembers[2].profile.name},habit:Task 1,0,,todo:Task 2,0,`);
expect(splitRes[4]).to.equal(`${sortedMembers[3]._id},${sortedMembers[3].profile.name},habit:Task 1,0,,todo:Task 2,0,`);
expect(splitRes[5]).to.equal('');
});
});
@@ -0,0 +1,123 @@
import {
generateUser,
generateChallenge,
createAndPopulateGroup,
translate as t,
} from '../../../../helpers/api-v3-integration.helper';
import { v4 as generateUUID } from 'uuid';
describe('POST /challenges/:challengeId/leave', () => {
it('returns error when challengeId is not a valid UUID', async () => {
let user = await generateUser();
await expect(user.post('/challenges/test/leave')).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('invalidReqParams'),
});
});
it('returns error when challengeId is not for a valid challenge', async () => {
let user = await generateUser();
await expect(user.post(`/challenges/${generateUUID()}/leave`)).to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
message: t('challengeNotFound'),
});
});
context('Leaving a valid challenge', () => {
let groupLeader;
let group;
let challenge;
let notInChallengeUser;
let leavingUser;
let taskText;
beforeEach(async () => {
let populatedGroup = await createAndPopulateGroup({
members: 2,
});
groupLeader = populatedGroup.groupLeader;
group = populatedGroup.group;
leavingUser = populatedGroup.members[0];
notInChallengeUser = populatedGroup.members[1];
challenge = await generateChallenge(groupLeader, group);
taskText = 'A challenge task text';
await groupLeader.post(`/tasks/challenge/${challenge._id}`, [
{type: 'habit', text: taskText},
]);
await leavingUser.post(`/challenges/${challenge._id}/join`);
await challenge.sync();
});
it('returns an error when user doesn\'t have permissions to view the challenge', async () => {
let unauthorizedUser = await generateUser();
await expect(unauthorizedUser.post(`/challenges/${challenge._id}/leave`)).to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
message: t('challengeNotFound'),
});
});
it('returns an error when user isn\'t a member of the challenge', async () => {
await expect(notInChallengeUser.post(`/challenges/${challenge._id}/leave`)).to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('challengeMemberNotFound'),
});
});
it('removes challenge from user challenges', async () => {
await leavingUser.post(`/challenges/${challenge._id}/leave`);
await leavingUser.sync();
expect(leavingUser).to.have.property('challenges').to.not.include(challenge._id);
});
it('decreases memberCount of challenge', async () => {
let oldMemberCount = challenge.memberCount;
await leavingUser.post(`/challenges/${challenge._id}/leave`);
await challenge.sync();
expect(challenge).to.have.property('memberCount', oldMemberCount - 1);
});
it('unlinks challenge tasks from leaving user when remove-all is passed', async () => {
await leavingUser.post(`/challenges/${challenge._id}/leave`, {
keep: 'remove-all',
});
let tasks = await leavingUser.get('/tasks/user');
let tasksTexts = tasks.map((task) => {
return task.text;
});
expect(tasksTexts).to.not.include(taskText);
});
it('doesn\'t unlink challenge tasks from leaving user when remove-all isn\'t passed', async () => {
await leavingUser.post(`/challenges/${challenge._id}/leave`, {
keep: 'test',
});
let tasks = await leavingUser.get('/tasks/user');
let testTask = _.find(tasks, (task) => {
return task.text === taskText;
});
expect(testTask).to.not.be.undefined;
expect(testTask.challenge).to.be.undefined;
});
});
});
@@ -0,0 +1,139 @@
import {
generateUser,
generateChallenge,
createAndPopulateGroup,
sleep,
checkExistence,
translate as t,
} from '../../../../helpers/api-v3-integration.helper';
import { v4 as generateUUID } from 'uuid';
describe('POST /challenges/:challengeId/winner/:winnerId', () => {
it('returns error when challengeId is not a valid UUID', async () => {
let user = await generateUser();
await expect(user.post(`/challenges/test/selectWinner/${user._id}`)).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('invalidReqParams'),
});
});
it('returns error when winnerId is not a valid UUID', async () => {
let user = await generateUser();
await expect(user.post(`/challenges/${generateUUID()}/selectWinner/test`)).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('invalidReqParams'),
});
});
it('returns error when challengeId is not for a valid challenge', async () => {
let user = await generateUser();
await expect(user.post(`/challenges/${generateUUID()}/selectWinner/${user._id}`)).to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
message: t('challengeNotFound'),
});
});
context('Selecting winner for a valid challenge', () => {
let groupLeader;
let group;
let challenge;
let winningUser;
let taskText = 'A challenge task text';
beforeEach(async () => {
let populatedGroup = await createAndPopulateGroup({
members: 1,
});
groupLeader = populatedGroup.groupLeader;
group = populatedGroup.group;
winningUser = populatedGroup.members[0];
challenge = await generateChallenge(groupLeader, group, {
prize: 1,
});
await groupLeader.post(`/tasks/challenge/${challenge._id}`, [
{type: 'habit', text: taskText},
]);
await winningUser.post(`/challenges/${challenge._id}/join`);
await challenge.sync();
});
it('returns an error when user doesn\'t have permissions to select winner', async () => {
await expect(winningUser.post(`/challenges/${challenge._id}/selectWinner/${winningUser._id}`)).to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('onlyLeaderDeleteChal'),
});
});
it('returns an error when winning user isn\'t part of the challenge', async () => {
let notInChallengeUser = await generateUser();
await expect(groupLeader.post(`/challenges/${challenge._id}/selectWinner/${notInChallengeUser._id}`)).to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
message: t('winnerNotFound', {userId: notInChallengeUser._id}),
});
});
it('deletes challenge after winner is selected', async () => {
await groupLeader.post(`/challenges/${challenge._id}/selectWinner/${winningUser._id}`);
await sleep(0.5);
await expect(checkExistence('challenges', challenge._id)).to.eventually.equal(false);
});
it('adds challenge to winner\'s achievements', async () => {
await groupLeader.post(`/challenges/${challenge._id}/selectWinner/${winningUser._id}`);
await sleep(0.5);
await expect(winningUser.sync()).to.eventually.have.deep.property('achievements.challenges').to.include(challenge.name);
});
it('gives winner gems as reward', async () => {
let oldBalance = winningUser.balance;
await groupLeader.post(`/challenges/${challenge._id}/selectWinner/${winningUser._id}`);
await sleep(0.5);
await expect(winningUser.sync()).to.eventually.have.property('balance', oldBalance + challenge.prize / 4);
});
it('doesn\'t refund gems to group leader', async () => {
let oldBalance = (await groupLeader.sync()).balance;
await groupLeader.post(`/challenges/${challenge._id}/selectWinner/${winningUser._id}`);
await sleep(0.5);
await expect(groupLeader.sync()).to.eventually.have.property('balance', oldBalance);
});
it('sets broken and winner flags for user\'s challenge tasks', async () => {
await groupLeader.post(`/challenges/${challenge._id}/selectWinner/${winningUser._id}`);
await sleep(0.5);
let tasks = await winningUser.get('/tasks/user');
let testTask = _.find(tasks, (task) => {
return task.text === taskText;
});
expect(testTask.challenge.broken).to.eql('CHALLENGE_CLOSED');
expect(testTask.challenge.winner).to.eql(winningUser.profile.name);
});
});
});
@@ -0,0 +1,76 @@
import {
generateUser,
generateChallenge,
createAndPopulateGroup,
translate as t,
} from '../../../../helpers/api-v3-integration.helper';
describe('PUT /challenges/:challengeId', () => {
let privateGuild, user, nonMember, challenge, member;
beforeEach(async () => {
let { group, groupLeader, members } = await createAndPopulateGroup({
groupDetails: {
name: 'TestPrivateGuild',
type: 'guild',
privacy: 'private',
},
members: 1,
});
privateGuild = group;
user = groupLeader;
nonMember = await generateUser();
member = members[0];
challenge = await generateChallenge(user, group);
await member.post(`/challenges/${challenge._id}/join`);
});
it('fails if the user can\'t view the challenge', async () => {
await expect(nonMember.put(`/challenges/${challenge._id}`))
.to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
message: t('challengeNotFound'),
});
});
it('should only allow the leader or an admin to update the challenge', async () => {
await expect(member.put(`/challenges/${challenge._id}`))
.to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('onlyLeaderUpdateChal'),
});
});
it('only updates allowed fields', async () => {
let res = await user.put(`/challenges/${challenge._id}`, {
// ignored
prize: 33,
groupId: 'blabla',
memberCount: 33,
tasksOrder: 'new order',
official: true,
shortName: 'new short name',
// applied
name: 'New Challenge Name',
description: 'New challenge description.',
leader: member._id,
});
expect(res.prize).to.equal(0);
expect(res.groupId).to.equal(privateGuild._id);
expect(res.memberCount).to.equal(2);
expect(res.tasksOrder).not.to.equal('new order');
expect(res.official).to.equal(false);
expect(res.shortName).not.to.equal('new short name');
expect(res.leader).to.equal(member._id);
expect(res.name).to.equal('New Challenge Name');
expect(res.description).to.equal('New challenge description.');
});
});
@@ -0,0 +1,81 @@
import {
createAndPopulateGroup,
generateUser,
translate as t,
} from '../../../../helpers/api-v3-integration.helper';
import { v4 as generateUUID } from 'uuid';
describe('DELETE /groups/:groupId/chat/:chatId', () => {
let groupWithChat, message, user, userThatDidNotCreateChat, admin;
before(async () => {
let { group, groupLeader } = await createAndPopulateGroup({
groupDetails: {
type: 'guild',
privacy: 'public',
},
});
groupWithChat = group;
user = groupLeader;
message = await user.post(`/groups/${groupWithChat._id}/chat`, { message: 'Some message' });
message = message.message;
userThatDidNotCreateChat = await generateUser();
admin = await generateUser({'contributor.admin': true});
});
context('Chat errors', () => {
it('returns an error is message does not exist', async () => {
let fakeChatId = generateUUID();
await expect(user.del(`/groups/${groupWithChat._id}/chat/${fakeChatId}`)).to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
message: t('messageGroupChatNotFound'),
});
});
it('returns an error when user does not have permission to delete', async () => {
await expect(userThatDidNotCreateChat.del(`/groups/${groupWithChat._id}/chat/${message.id}`)).to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('onlyCreatorOrAdminCanDeleteChat'),
});
});
});
context('Chat success', () => {
let nextMessage;
beforeEach(async () => {
nextMessage = await user.post(`/groups/${groupWithChat._id}/chat`, { message: 'Some new message' });
nextMessage = nextMessage.message;
});
it('allows creator to delete a their message', async () => {
await user.del(`/groups/${groupWithChat._id}/chat/${nextMessage.id}`);
let messages = await user.get(`/groups/${groupWithChat._id}/chat/`);
expect(messages).is.an('array');
expect(messages).to.not.include(nextMessage);
});
it('allows admin to delete another user\'s message', async () => {
await admin.del(`/groups/${groupWithChat._id}/chat/${nextMessage.id}`);
let messages = await user.get(`/groups/${groupWithChat._id}/chat/`);
expect(messages).is.an('array');
expect(messages).to.not.include(nextMessage);
});
it('returns empty when previous message parameter is passed and the last message was deleted', async () => {
await expect(user.del(`/groups/${groupWithChat._id}/chat/${nextMessage.id}?previousMsg=${nextMessage.id}`))
.to.eventually.be.empty;
});
it('returns the update chat when previous message parameter is passed and the chat is updated', async () => {
await expect(user.del(`/groups/${groupWithChat._id}/chat/${nextMessage.id}?previousMsg=${message.id}`))
.eventually
.is.an('array')
.to.include(message)
.to.be.lengthOf(1);
});
});
});
@@ -5,11 +5,14 @@ import {
import { find } from 'lodash';
describe('POST /chat/:chatId/flag', () => {
let user, group;
let user, admin, anotherUser, group;
const TEST_MESSAGE = 'Test Message';
before(async () => {
user = await generateUser({balance: 1});
admin = await generateUser({balance: 1, 'contributor.admin': true});
anotherUser = await generateUser();
group = await user.post('/groups', {
name: 'Test Guild',
type: 'guild',
@@ -18,7 +21,7 @@ describe('POST /chat/:chatId/flag', () => {
});
it('Returns an error when chat message is not found', async () => {
return expect(user.post(`/groups/${group._id}/chat/incorrectMessage/flag`))
await expect(user.post(`/groups/${group._id}/chat/incorrectMessage/flag`))
.to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
@@ -27,79 +30,55 @@ describe('POST /chat/:chatId/flag', () => {
});
it('Returns an error when user tries to flag their own message', async () => {
return user.post(`/groups/${group._id}/chat`, { message: TEST_MESSAGE})
.then((result) => {
return expect(user.post(`/groups/${group._id}/chat/${result.message.id}/flag`))
.to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
message: t('messageGroupChatFlagOwnMessage'),
});
});
let message = await user.post(`/groups/${group._id}/chat`, { message: TEST_MESSAGE });
await expect(user.post(`/groups/${group._id}/chat/${message.message.id}/flag`))
.to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
message: t('messageGroupChatFlagOwnMessage'),
});
});
it('Flags a chat', async () => {
let message;
let message = await anotherUser.post(`/groups/${group._id}/chat`, { message: TEST_MESSAGE});
message = message.message;
return generateUser().then((anotherUser) => {
return anotherUser.post(`/groups/${group._id}/chat`, { message: TEST_MESSAGE});
})
.then((result) => {
message = result.message;
return user.post(`/groups/${group._id}/chat/${message.id}/flag`);
})
.then((result) => {
expect(result.flags[user._id]).to.equal(true);
expect(result.flagCount).to.equal(1);
return user.get(`/groups/${group._id}`);
})
.then((updatedGroup) => {
let messageToCheck = find(updatedGroup.chat, {id: message.id});
expect(messageToCheck.flags[user._id]).to.equal(true);
});
let flagResult = await user.post(`/groups/${group._id}/chat/${message.id}/flag`);
expect(flagResult.flags[user._id]).to.equal(true);
expect(flagResult.flagCount).to.equal(1);
let groupWithFlags = await admin.get(`/groups/${group._id}`);
let messageToCheck = find(groupWithFlags.chat, {id: message.id});
expect(messageToCheck.flags[user._id]).to.equal(true);
});
it('Flags a chat with a higher flag acount when an admin flags the message', async () => {
let secondUser;
let message;
let message = await user.post(`/groups/${group._id}/chat`, { message: TEST_MESSAGE});
message = message.message;
return generateUser({'contributor.admin': true}).then((generatedUser) => {
secondUser = generatedUser;
return user.post(`/groups/${group._id}/chat`, { message: TEST_MESSAGE});
})
.then((result) => {
message = result.message;
return secondUser.post(`/groups/${group._id}/chat/${message.id}/flag`);
})
.then((result) => {
expect(result.flags[secondUser._id]).to.equal(true);
expect(result.flagCount).to.equal(5);
return user.get(`/groups/${group._id}`);
})
.then((updatedGroup) => {
let messageToCheck = find(updatedGroup.chat, {id: message.id});
expect(messageToCheck.flags[secondUser._id]).to.equal(true);
expect(messageToCheck.flagCount).to.equal(5);
});
let flagResult = await admin.post(`/groups/${group._id}/chat/${message.id}/flag`);
expect(flagResult.flags[admin._id]).to.equal(true);
expect(flagResult.flagCount).to.equal(5);
let groupWithFlags = await admin.get(`/groups/${group._id}`);
let messageToCheck = find(groupWithFlags.chat, {id: message.id});
expect(messageToCheck.flags[admin._id]).to.equal(true);
expect(messageToCheck.flagCount).to.equal(5);
});
it('Returns an error when user tries to flag a message that is already flagged', async () => {
let message;
let message = await anotherUser.post(`/groups/${group._id}/chat`, { message: TEST_MESSAGE});
message = message.message;
return generateUser().then((anotherUser) => {
return anotherUser.post(`/groups/${group._id}/chat`, { message: TEST_MESSAGE});
})
.then((result) => {
message = result.message;
return user.post(`/groups/${group._id}/chat/${message.id}/flag`);
})
.then(() => {
return expect(user.post(`/groups/${group._id}/chat/${message.id}/flag`))
.to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
message: t('messageGroupChatFlagAlreadyReported'),
});
});
await user.post(`/groups/${group._id}/chat/${message.id}/flag`);
await expect(user.post(`/groups/${group._id}/chat/${message.id}/flag`))
.to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
message: t('messageGroupChatFlagAlreadyReported'),
});
});
});
@@ -1,36 +1,32 @@
import {
generateUser,
createAndPopulateGroup,
translate as t,
} from '../../../../helpers/api-v3-integration.helper';
import { find } from 'lodash';
describe('POST /chat/:chatId/like', () => {
let user;
let group;
let groupWithChat;
let testMessage = 'Test Message';
let anotherUser;
before(() => {
let groupName = 'Test Guild';
let groupType = 'guild';
let groupPrivacy = 'public';
return generateUser({balance: 1}).then((generatedUser) => {
user = generatedUser;
})
.then(() => {
return user.post('/groups', {
name: groupName,
type: groupType,
privacy: groupPrivacy,
});
})
.then((generatedGroup) => {
group = generatedGroup;
before(async () => {
let { group, groupLeader, members } = await createAndPopulateGroup({
groupDetails: {
name: 'Test Guild',
type: 'guild',
privacy: 'public',
},
members: 1,
});
user = groupLeader;
groupWithChat = group;
anotherUser = members[0];
});
it('Returns an error when chat message is not found', () => {
return expect(user.post(`/groups/${group._id}/chat/incorrectMessage/like`))
it('Returns an error when chat message is not found', async () => {
await expect(user.post(`/groups/${groupWithChat._id}/chat/incorrectMessage/like`))
.to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
@@ -38,59 +34,42 @@ describe('POST /chat/:chatId/like', () => {
});
});
it('Returns an error when user tries to like their own message', () => {
return user.post(`/groups/${group._id}/chat`, { message: testMessage})
.then((result) => {
return expect(user.post(`/groups/${group._id}/chat/${result.message.id}/like`))
.to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
message: t('messageGroupChatLikeOwnMessage'),
});
});
it('Returns an error when user tries to like their own message', async () => {
let message = await user.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage});
await expect(user.post(`/groups/${groupWithChat._id}/chat/${message.message.id}/like`))
.to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
message: t('messageGroupChatLikeOwnMessage'),
});
});
it('Likes a chat', () => {
let message;
it('Likes a chat', async () => {
let message = await anotherUser.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage});
return generateUser().then((anotherUser) => {
return anotherUser.post(`/groups/${group._id}/chat`, { message: testMessage});
})
.then((result) => {
message = result.message;
return user.post(`/groups/${group._id}/chat/${message.id}/like`);
})
.then((result) => {
expect(result.likes[user._id]).to.equal(true);
return user.get(`/groups/${group._id}`);
})
.then((updatedGroup) => {
let messageToCheck = find(updatedGroup.chat, {id: message.id});
expect(messageToCheck.likes[user._id]).to.equal(true);
});
let likeResult = await user.post(`/groups/${groupWithChat._id}/chat/${message.message.id}/like`);
expect(likeResult.likes[user._id]).to.equal(true);
let groupWithChatLikes = await user.get(`/groups/${groupWithChat._id}`);
let messageToCheck = find(groupWithChatLikes.chat, {id: message.message.id});
expect(messageToCheck.likes[user._id]).to.equal(true);
});
it('Unlikes a chat', () => {
let message;
it('Unlikes a chat', async () => {
let message = await anotherUser.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage});
return generateUser().then((anotherUser) => {
return anotherUser.post(`/groups/${group._id}/chat`, { message: testMessage});
})
.then((result) => {
message = result.message;
return user.post(`/groups/${group._id}/chat/${message.id}/like`);
})
.then((result) => {
expect(result.likes[user._id]).to.equal(true);
return user.post(`/groups/${group._id}/chat/${message.id}/like`);
})
.then((result) => {
expect(result.likes[user._id]).to.equal(false);
return user.get(`/groups/${group._id}`);
})
.then((updatedGroup) => {
let messageToCheck = find(updatedGroup.chat, {id: message.id});
expect(messageToCheck.likes[user._id]).to.equal(false);
});
let likeResult = await user.post(`/groups/${groupWithChat._id}/chat/${message.message.id}/like`);
expect(likeResult.likes[user._id]).to.equal(true);
let unlikeResult = await user.post(`/groups/${groupWithChat._id}/chat/${message.message.id}/like`);
expect(unlikeResult.likes[user._id]).to.equal(false);
let groupWithoutChatLikes = await user.get(`/groups/${groupWithChat._id}`);
let messageToCheck = find(groupWithoutChatLikes.chat, {id: message.message.id});
expect(messageToCheck.likes[user._id]).to.equal(false);
});
});
+55 -69
View File
@@ -1,95 +1,81 @@
import {
generateUser,
createAndPopulateGroup,
translate as t,
} from '../../../../helpers/api-v3-integration.helper';
describe('POST /chat', () => {
let user;
let user, groupWithChat, userWithChatRevoked, member;
let testMessage = 'Test Message';
before(() => {
return generateUser().then((generatedUser) => {
user = generatedUser;
before(async () => {
let { group, groupLeader, members } = await createAndPopulateGroup({
groupDetails: {
name: 'Test Guild',
type: 'guild',
privacy: 'public',
},
members: 2,
});
user = groupLeader;
groupWithChat = group;
userWithChatRevoked = await members[0].update({'flags.chatRevoked': true});
member = members[0];
});
it('Returns an error when no message is provided', () => {
let groupName = 'Test Guild';
let groupType = 'guild';
let groupPrivacy = 'public';
let testMessage = '';
return generateUser({balance: 1}).then((anotherUser) => {
return anotherUser.post('/groups', {
name: groupName,
type: groupType,
privacy: groupPrivacy,
it('Returns an error when no message is provided', async () => {
await expect(user.post(`/groups/${groupWithChat._id}/chat`, { message: ''}))
.to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('invalidReqParams'),
});
})
.then((group) => {
return expect(user.post(`/groups/${group._id}/chat`, { message: testMessage}))
.to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('invalidReqParams'),
});
});
});
it('Returns an error when group is not found', () => {
let testMessage = 'Test Message';
return expect(user.post('/groups/nvalidID/chat', { message: testMessage})).to.eventually.be.rejected.and.eql({
it('Returns an error when group is not found', async () => {
await expect(user.post('/groups/invalidID/chat', { message: testMessage})).to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
message: t('groupNotFound'),
});
});
it('Returns an error when chat privileges are revoked', () => {
let groupName = 'Test Guild';
let groupType = 'guild';
let groupPrivacy = 'public';
let testMessage = 'Test Message';
let userWithoutChat;
return generateUser({balance: 1, 'flags.chatRevoked': true}).then((generatedUser) => {
userWithoutChat = generatedUser;
return userWithoutChat.post('/groups', {
name: groupName,
type: groupType,
privacy: groupPrivacy,
});
})
.then((group) => {
return expect(userWithoutChat.post(`/groups/${group._id}/chat`, { message: testMessage})).to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
message: 'Your chat privileges have been revoked.',
});
it('Returns an error when chat privileges are revoked', async () => {
await expect(userWithChatRevoked.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage})).to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
message: 'Your chat privileges have been revoked.',
});
});
it('creates a chat', () => {
let groupName = 'Test Guild';
let groupType = 'guild';
let groupPrivacy = 'public';
let testMessage = 'Test Message';
let anotherUser;
it('creates a chat', async () => {
let message = await user.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage});
return generateUser({balance: 1}).then((generatedUser) => {
anotherUser = generatedUser;
expect(message.message.id).to.exist;
});
return anotherUser.post('/groups', {
name: groupName,
type: groupType,
privacy: groupPrivacy,
});
})
.then((group) => {
return anotherUser.post(`/groups/${group._id}/chat`, { message: testMessage});
})
.then((result) => {
expect(result.message.id).to.exist;
it('notifies other users of new messages for a guild', async () => {
let message = await user.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage});
let memberWithNotification = await member.get('/user');
expect(message.message.id).to.exist;
expect(memberWithNotification.newMessages[`${groupWithChat._id}`]).to.exist;
});
it('notifies other users of new messages for a party', async () => {
let { group, groupLeader, members } = await createAndPopulateGroup({
groupDetails: {
name: 'Test Party',
type: 'party',
privacy: 'private',
},
members: 1,
});
let message = await groupLeader.post(`/groups/${group._id}/chat`, { message: testMessage});
let memberWithNotification = await members[0].get('/user');
expect(message.message.id).to.exist;
expect(memberWithNotification.newMessages[`${group._id}`]).to.exist;
});
});
@@ -0,0 +1,63 @@
import {
createAndPopulateGroup,
} from '../../../../helpers/api-v3-integration.helper';
describe('POST /groups/:id/chat/seen', () => {
context('Guild', () => {
let guild, guildLeader, guildMember, guildMessage;
before(async () => {
let { group, groupLeader, members } = await createAndPopulateGroup({
groupDetails: {
type: 'guild',
privacy: 'public',
},
members: 1,
});
guild = group;
guildLeader = groupLeader;
guildMember = members[0];
guildMessage = await guildLeader.post(`/groups/${guild._id}/chat`, { message: 'Some guild message' });
guildMessage = guildMessage.message;
});
it('clears new messages for a guild', async () => {
await guildMember.post(`/groups/${guild._id}/chat/seen`);
let guildThatHasSeenChat = await guildMember.get('/user');
expect(guildThatHasSeenChat.newMessages).to.be.empty;
});
});
context('Party', () => {
let party, partyLeader, partyMember, partyMessage;
before(async () => {
let { group, groupLeader, members } = await createAndPopulateGroup({
groupDetails: {
type: 'party',
privacy: 'private',
},
members: 1,
});
party = group;
partyLeader = groupLeader;
partyMember = members[0];
partyMessage = await partyLeader.post(`/groups/${party._id}/chat`, { message: 'Some party message' });
partyMessage = partyMessage.message;
});
it('clears new messages for a party', async () => {
await partyMember.post(`/groups/${party._id}/chat/seen`);
let partyMemberThatHasSeenChat = await partyMember.get('/user');
expect(partyMemberThatHasSeenChat.newMessages).to.be.empty;
});
});
});
@@ -0,0 +1,101 @@
import {
createAndPopulateGroup,
generateUser,
translate as t,
} from '../../../../helpers/api-v3-integration.helper';
import { v4 as generateUUID } from 'uuid';
describe('POST /groups/:id/chat/:id/clearflags', () => {
let groupWithChat, message, author, nonAdmin, admin;
before(async () => {
let { group, groupLeader, members } = await createAndPopulateGroup({
groupDetails: {
type: 'guild',
privacy: 'public',
},
members: 1,
});
groupWithChat = group;
author = groupLeader;
nonAdmin = members[0];
admin = await generateUser({'contributor.admin': true});
message = await author.post(`/groups/${groupWithChat._id}/chat`, { message: 'Some message' });
message = message.message;
admin.post(`/groups/${groupWithChat._id}/chat/${message.id}/flag`);
});
context('Single Message', () => {
it('returns error when non-admin attempts to clear flags', async () => {
return expect(nonAdmin.post(`/groups/${groupWithChat._id}/chat/${message.id}/clearflags`))
.to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('messageGroupChatAdminClearFlagCount'),
});
});
it('returns error if message does not exist', async () => {
let fakeMessageID = generateUUID();
await expect(admin.post(`/groups/${groupWithChat._id}/chat/${fakeMessageID}/clearflags`))
.to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
message: t('messageGroupChatNotFound'),
});
});
it('clears flags and leaves old flags on the flag object', async () => {
await admin.post(`/groups/${groupWithChat._id}/chat/${message.id}/clearflags`);
let messages = await admin.get(`/groups/${groupWithChat._id}/chat`);
expect(messages[0].flagCount).to.eql(0);
expect(messages[0].flags).to.have.property(admin._id, true);
});
});
context('admin user, group with multiple messages', () => {
let message2, message3, message4;
before(async () => {
message2 = await author.post(`/groups/${groupWithChat._id}/chat`, { message: 'Some message 2' });
message2 = message2.message;
await admin.post(`/groups/${groupWithChat._id}/chat/${message2.id}/flag`);
message3 = await author.post(`/groups/${groupWithChat._id}/chat`, { message: 'Some message 3' });
message3 = message3.message;
await admin.post(`/groups/${groupWithChat._id}/chat/${message3.id}/flag`);
await nonAdmin.post(`/groups/${groupWithChat._id}/chat/${message3.id}/flag`);
message4 = await author.post(`/groups/${groupWithChat._id}/chat`, { message: 'Some message 4' });
message4 = message4.message;
});
it('changes only the message that is flagged', async () => {
await admin.post(`/groups/${groupWithChat._id}/chat/${message.id}/clearflags`);
let messages = await admin.get(`/groups/${groupWithChat._id}/chat`);
expect(messages).to.have.lengthOf(4);
let messageThatWasUnflagged = messages[3];
let messageWith1Flag = messages[2];
let messageWith2Flag = messages[1];
let messageWithoutFlags = messages[0];
expect(messageThatWasUnflagged.flagCount).to.eql(0);
expect(messageThatWasUnflagged.flags).to.have.property(admin._id, true);
expect(messageWith1Flag.flagCount).to.eql(5);
expect(messageWith1Flag.flags).to.have.property(admin._id, true);
expect(messageWith2Flag.flagCount).to.eql(6);
expect(messageWith2Flag.flags).to.have.property(admin._id, true);
expect(messageWith2Flag.flags).to.have.property(nonAdmin._id, true);
expect(messageWithoutFlags.flagCount).to.eql(0);
expect(messageWithoutFlags.flags).to.eql({});
});
});
});
@@ -0,0 +1,96 @@
import {
generateUser,
resetHabiticaDB,
generateGroup,
} from '../../../../helpers/api-v3-integration.helper';
describe('GET /groups', () => {
let user;
const NUMBER_OF_PUBLIC_GUILDS = 3;
const NUMBER_OF_USERS_PRIVATE_GUILDS = 1;
const NUMBER_OF_GROUPS_USER_CAN_VIEW = 5;
before(async () => {
await resetHabiticaDB();
let leader = await generateUser({ balance: 10 });
user = await generateUser({balance: 4});
let publicGuildUserIsMemberOf = await generateGroup(leader, {
name: 'public guild - is member',
type: 'guild',
privacy: 'public',
});
await leader.post(`/groups/${publicGuildUserIsMemberOf._id}/invite`, { uuids: [user._id]});
await user.post(`/groups/${publicGuildUserIsMemberOf._id}/join`);
await generateGroup(leader, {
name: 'public guild - is not member',
type: 'guild',
privacy: 'public',
});
let privateGuildUserIsMemberOf = await generateGroup(leader, {
name: 'private guild - is member',
type: 'guild',
privacy: 'private',
});
await leader.post(`/groups/${privateGuildUserIsMemberOf._id}/invite`, { uuids: [user._id]});
await user.post(`/groups/${privateGuildUserIsMemberOf._id}/join`);
await generateGroup(leader, {
name: 'private guild - is not member',
type: 'guild',
privacy: 'private',
});
await generateGroup(leader, {
name: 'party - is not member',
type: 'party',
privacy: 'private',
});
await user.post('/groups', {
name: 'party - is member',
type: 'party',
privacy: 'private',
});
});
it('returns error when no query passed in', async () => {
await expect(user.get('/groups'))
.to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: 'Invalid request parameters.',
});
});
it('returns only the tavern when tavern passed in as query', async () => {
await expect(user.get('/groups?type=tavern'))
.to.eventually.have.a.lengthOf(1)
.and.to.have.deep.property('[0]')
.and.to.have.property('_id', 'habitrpg');
});
it('returns only the user\'s party when party passed in as query', async () => {
await expect(user.get('/groups?type=party'))
.to.eventually.have.a.lengthOf(1)
.and.to.have.deep.property('[0]');
});
it('returns all public guilds when publicGuilds passed in as query', async () => {
await expect(user.get('/groups?type=publicGuilds'))
.to.eventually.have.a.lengthOf(NUMBER_OF_PUBLIC_GUILDS);
});
it('returns all private guilds user is a part of when privateGuilds passed in as query', async () => {
await expect(user.get('/groups?type=privateGuilds'))
.to.eventually.have.a.lengthOf(NUMBER_OF_USERS_PRIVATE_GUILDS);
});
it('returns a list of groups user has access to', async () => {
await expect(user.get('/groups?type=privateGuilds,publicGuilds,party,tavern'))
.to.eventually.have.lengthOf(NUMBER_OF_GROUPS_USER_CAN_VIEW);
});
});
@@ -0,0 +1,302 @@
import {
generateUser,
createAndPopulateGroup,
translate as t,
} from '../../../../helpers/api-v3-integration.helper';
import {
each,
} from 'lodash';
describe('GET /groups/:id', () => {
let typesOfGroups = {};
typesOfGroups['public guild'] = { type: 'guild', privacy: 'public' };
typesOfGroups['private guild'] = { type: 'guild', privacy: 'private' };
typesOfGroups.party = { type: 'party', privacy: 'private' };
each(typesOfGroups, (groupDetails, groupType) => {
context(`Member of a ${groupType}`, () => {
let leader, member, createdGroup;
before(async () => {
let groupData = await createAndPopulateGroup({
members: 30,
groupDetails,
});
leader = groupData.groupLeader;
member = groupData.members[0];
createdGroup = groupData.group;
});
it('returns the group object', async () => {
let group = await member.get(`/groups/${createdGroup._id}`);
expect(group._id).to.eql(createdGroup._id);
expect(group.name).to.eql(createdGroup.name);
expect(group.type).to.eql(createdGroup.type);
expect(group.privacy).to.eql(createdGroup.privacy);
});
it('transforms leader id to leader object', async () => {
let group = await member.get(`/groups/${createdGroup._id}`);
expect(group.leader._id).to.eql(leader._id);
expect(group.leader.profile.name).to.eql(leader.profile.name);
expect(group.leader.items).to.exist;
expect(group.leader.stats).to.exist;
expect(group.leader.achievements).to.exist;
expect(group.leader.contributor).to.exist;
});
});
});
context('Non-member of a public guild', () => {
let nonMember, createdGroup;
before(async () => {
let groupData = await createAndPopulateGroup({
members: 1,
groupDetails: {
name: 'test guild',
type: 'guild',
privacy: 'public',
},
});
createdGroup = groupData.group;
nonMember = await generateUser();
});
it('returns the group object for a non-member', async () => {
let group = await nonMember.get(`/groups/${createdGroup._id}`);
expect(group._id).to.eql(createdGroup._id);
expect(group.name).to.eql(createdGroup.name);
expect(group.type).to.eql(createdGroup.type);
expect(group.privacy).to.eql(createdGroup.privacy);
});
});
context('Non-member of a private guild', () => {
let nonMember, createdGroup;
before(async () => {
let groupData = await createAndPopulateGroup({
members: 1,
groupDetails: {
name: 'test guild',
type: 'guild',
privacy: 'private',
},
});
createdGroup = groupData.group;
nonMember = await generateUser();
});
it('does not return the group object for a non-member', async () => {
await expect(nonMember.get(`/groups/${createdGroup._id}`))
.to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
message: t('groupNotFound'),
});
});
});
context('Non-member of a party', () => {
let nonMember, createdGroup;
before(async () => {
let groupData = await createAndPopulateGroup({
members: 1,
groupDetails: {
name: 'test party',
type: 'party',
privacy: 'private',
},
});
createdGroup = groupData.group;
nonMember = await generateUser();
});
it('does not return the group object for a non-member', async () => {
await expect(nonMember.get(`/groups/${createdGroup._id}`))
.to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
message: t('groupNotFound'),
});
});
});
context('Member of a party', () => {
let member, createdGroup;
before(async () => {
let groupData = await createAndPopulateGroup({
members: 1,
groupDetails: {
name: 'test party',
type: 'party',
privacy: 'private',
},
});
createdGroup = groupData.group;
member = groupData.members[0];
});
it('returns the user\'s party if an id of "party" is passed in', async () => {
let group = await member.get('/groups/party');
expect(group._id).to.eql(createdGroup._id);
expect(group.name).to.eql(createdGroup.name);
expect(group.type).to.eql(createdGroup.type);
expect(group.privacy).to.eql(createdGroup.privacy);
});
});
context('Non-existent group', () => {
let user;
beforeEach(async () => {
user = await generateUser();
});
it('returns error if group does not exist', async () => {
await expect(user.get('/groups/group-that-does-not-exist'))
.to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
message: t('groupNotFound'),
});
});
});
context('Flagged messages', () => {
let group;
let chat1 = {
id: 'chat1',
text: 'chat 1',
flags: {},
};
let chat2 = {
id: 'chat2',
text: 'chat 2',
flags: {},
flagCount: 0,
};
let chat3 = {
id: 'chat3',
text: 'chat 3',
flags: {
'user-id': true,
},
flagCount: 1,
};
let chat4 = {
id: 'chat4',
text: 'chat 4',
flags: {
'user-id': true,
'other-user-id': true,
},
flagCount: 2,
};
let chat5 = {
id: 'chat5',
text: 'chat 5',
flags: {
'user-id': true,
'other-user-id': true,
'yet-another-user-id': true,
},
flagCount: 3,
};
beforeEach(async () => {
let groupData = await createAndPopulateGroup({
groupDetails: {
name: 'test guild',
type: 'guild',
privacy: 'public',
chat: [
chat1,
chat2,
chat3,
chat4,
chat5,
],
},
});
group = groupData.group;
await group.addChat([chat1, chat2, chat3, chat4, chat5]);
});
context('non-admin', () => {
let nonAdmin;
beforeEach(async () => {
nonAdmin = await generateUser();
});
it('does not include messages with a flag count of 2 or greater', async () => {
let fetchedGroup = await nonAdmin.get(`/groups/${group._id}`);
expect(fetchedGroup.chat).to.have.lengthOf(3);
expect(fetchedGroup.chat[0].id).to.eql(chat1.id);
expect(fetchedGroup.chat[1].id).to.eql(chat2.id);
expect(fetchedGroup.chat[2].id).to.eql(chat3.id);
});
it('does not include user ids in flags object', async () => {
let fetchedGroup = await nonAdmin.get(`/groups/${group._id}`);
let chatWithOneFlag = fetchedGroup.chat[2];
expect(chatWithOneFlag.id).to.eql(chat3.id);
expect(chat3.flags).to.eql({ 'user-id': true });
expect(chatWithOneFlag.flags).to.eql({});
});
});
context('admin', () => {
let admin;
beforeEach(async () => {
admin = await generateUser({
'contributor.admin': true,
});
});
it('includes all messages', async () => {
let fetchedGroup = await admin.get(`/groups/${group._id}`);
expect(fetchedGroup.chat).to.have.lengthOf(5);
expect(fetchedGroup.chat[0].id).to.eql(chat1.id);
expect(fetchedGroup.chat[1].id).to.eql(chat2.id);
expect(fetchedGroup.chat[2].id).to.eql(chat3.id);
expect(fetchedGroup.chat[3].id).to.eql(chat4.id);
expect(fetchedGroup.chat[4].id).to.eql(chat5.id);
});
it('includes user ids in flags object', async () => {
let fetchedGroup = await admin.get(`/groups/${group._id}`);
let chatWithOneFlag = fetchedGroup.chat[2];
expect(chatWithOneFlag.id).to.eql(chat3.id);
expect(chat3.flags).to.eql({ 'user-id': true });
expect(chatWithOneFlag.flags).to.eql(chat3.flags);
});
});
});
});
@@ -1,6 +1,7 @@
import {
generateUser,
createAndPopulateGroup,
checkExistence,
translate as t,
} from '../../../../helpers/api-v3-integration.helper';
import { v4 as generateUUID } from 'uuid';
@@ -16,7 +17,47 @@ describe('POST /group/:groupId/join', () => {
});
});
context('Accepting invitation to a private guild', () => {
context('Joining a public guild', () => {
let user, joiningUser, publicGuild;
beforeEach(async () => {
let {group, groupLeader} = await createAndPopulateGroup({
groupDetails: {
name: 'Test Guild',
type: 'guild',
privacy: 'public',
},
});
publicGuild = group;
user = groupLeader;
joiningUser = await generateUser();
});
it('allows non-invited users to join public guilds', async () => {
await joiningUser.post(`/groups/${publicGuild._id}/join`);
await expect(joiningUser.get('/user')).to.eventually.have.property('guilds').to.include(publicGuild._id);
});
it('promotes joining member in a public empty guild to leader', async () => {
await user.post(`/groups/${publicGuild._id}/leave`);
await joiningUser.post(`/groups/${publicGuild._id}/join`);
await expect(joiningUser.get(`/groups/${publicGuild._id}`)).to.eventually.have.deep.property('leader._id', joiningUser._id);
});
it('increments memberCount when joining guilds', async () => {
let oldMemberCount = publicGuild.memberCount;
await joiningUser.post(`/groups/${publicGuild._id}/join`);
await expect(joiningUser.get(`/groups/${publicGuild._id}`)).to.eventually.have.property('memberCount', oldMemberCount + 1);
});
});
context('Joining a private guild', () => {
let user, invitedUser, guild;
beforeEach(async () => {
@@ -44,21 +85,6 @@ describe('POST /group/:groupId/join', () => {
});
});
it('allows non-invited users to join public guilds', async () => {
let publicGuild = (await createAndPopulateGroup({
groupDetails: {
name: 'Test Guild',
type: 'guild',
privacy: 'public',
},
})).group;
let joiningUser = await generateUser();
await joiningUser.post(`/groups/${publicGuild._id}/join`);
await expect(joiningUser.get('/user')).to.eventually.have.property('guilds').and.to.include(publicGuild._id);
});
context('User is invited', () => {
it('allows invited user to join private guilds', async () => {
await invitedUser.post(`/groups/${guild._id}/join`);
@@ -74,6 +100,14 @@ describe('POST /group/:groupId/join', () => {
.to.not.include({id: guild._id});
});
it('increments memberCount when joining guilds', async () => {
let oldMemberCount = guild.memberCount;
await invitedUser.post(`/groups/${guild._id}/join`);
await expect(invitedUser.get(`/groups/${guild._id}`)).to.eventually.have.property('memberCount', oldMemberCount + 1);
});
it('does not give basilist quest to inviter when joining a guild', async () => {
await invitedUser.post(`/groups/${guild._id}/join`);
@@ -90,7 +124,7 @@ describe('POST /group/:groupId/join', () => {
});
});
context('Accepting invitation to a party', () => {
context('Joining a party', () => {
let user, invitedUser, party;
beforeEach(async () => {
@@ -130,6 +164,14 @@ describe('POST /group/:groupId/join', () => {
await expect(invitedUser.get('/user')).to.eventually.not.have.deep.property('invitations.party.id');
});
it('increments memberCount when joining party', async () => {
let oldMemberCount = party.memberCount;
await invitedUser.post(`/groups/${party._id}/join`);
await expect(invitedUser.get(`/groups/${party._id}`)).to.eventually.have.property('memberCount', oldMemberCount + 1);
});
it('gives basilist quest item to the inviter when joining a party', async () => {
await invitedUser.post(`/groups/${party._id}/join`);
@@ -143,6 +185,37 @@ describe('POST /group/:groupId/join', () => {
await expect(user.get('/user')).to.eventually.have.deep.property('items.quests.basilist', 2);
});
it('deletes previous party where the user was the only member', async () => {
let userToInvite = await generateUser();
let oldParty = await userToInvite.post('/groups', { // add user to a party
name: 'Another Test Party',
type: 'party',
});
await expect(checkExistence('groups', oldParty._id)).to.eventually.equal(true);
await user.post(`/groups/${party._id}/invite`, {
uuids: [userToInvite._id],
});
await userToInvite.post(`/groups/${party._id}/join`);
await expect(user.get('/user')).to.eventually.have.deep.property('party._id', party._id);
await expect(checkExistence('groups', oldParty._id)).to.eventually.equal(false);
});
xit('invites joining member to active quest', async () => {
// TODO start quest
await invitedUser.post(`/groups/${party._id}/join`);
invitedUser = await user.get('/user');
party = await user.get(`/groups/${party._id}`);
expect(user).to.have.deep.property('party.quest.RSVPNeeded', true);
expect(user).to.have.deep.property('party.quest.key', party.quest.key);
expect(party.quest.members[invitedUser._id]).to.be.undefined;
});
});
});
});
@@ -0,0 +1,130 @@
import {
generateUser,
createAndPopulateGroup,
translate as t,
} from '../../../../helpers/api-v3-integration.helper';
describe('POST /groups/:groupId/removeMember/:memberId', () => {
let leader;
let invitedUser;
let guild;
let member;
let member2;
beforeEach(async () => {
let { group, groupLeader, invitees, members } = await createAndPopulateGroup({
groupDetails: {
name: 'Test Guild',
type: 'guild',
privacy: 'private',
},
invites: 1,
members: 2,
});
guild = group;
leader = groupLeader;
invitedUser = invitees[0];
member = members[0];
member2 = members[1];
});
context('All Groups', () => {
it('returns an error when user is not member of the group', async () => {
let nonMember = await generateUser();
expect(nonMember.post(`/groups/${guild._id}/removeMember/${member._id}`))
.to.eventually.be.rejected.and.eql({
code: 401,
type: 'NotAuthorized',
message: t('onlyLeaderCanRemoveMember'),
});
});
it('returns an error when user is a non-leader member of a group', async () => {
expect(member2.post(`/groups/${guild._id}/removeMember/${member._id}`))
.to.eventually.be.rejected.and.eql({
code: 401,
type: 'NotAuthorized',
message: t('onlyLeaderCanRemoveMember'),
});
});
it('does not allow leader to remove themselves', async () => {
expect(leader.post(`/groups/${guild._id}/removeMember/${leader._id}`))
.to.eventually.be.rejected.and.eql({
code: 401,
text: t('messageGroupCannotRemoveSelf'),
});
});
});
context('Guilds', () => {
it('can remove other members', async () => {
await leader.post(`/groups/${guild._id}/removeMember/${member._id}`);
let memberRemoved = await member.get('/user');
expect(memberRemoved.guilds.indexOf(guild._id)).eql(-1);
});
it('updates memberCount', async () => {
let oldMemberCount = guild.memberCount;
await leader.post(`/groups/${guild._id}/removeMember/${member._id}`);
await expect(leader.get(`/groups/${guild._id}`)).to.eventually.have.property('memberCount', oldMemberCount - 1);
});
it('can remove other invites', async () => {
await leader.post(`/groups/${guild._id}/removeMember/${invitedUser._id}`);
let invitedUserWithoutInvite = await invitedUser.get('/user');
expect(_.findIndex(invitedUserWithoutInvite.invitations.guilds, {id: guild._id})).eql(-1);
});
});
context('Party', () => {
let party;
let partyleader;
let partyInvitedUser;
let partyMember;
beforeEach(async () => {
let { group, groupLeader, invitees, members } = await createAndPopulateGroup({
groupDetails: {
name: 'Test Party',
type: 'party',
privacy: 'private',
},
invites: 1,
members: 1,
});
party = group;
partyleader = groupLeader;
partyInvitedUser = invitees[0];
partyMember = members[0];
});
it('can remove other members', async () => {
await partyleader.post(`/groups/${party._id}/removeMember/${partyMember._id}`);
let memberRemoved = await partyMember.get('/user');
expect(memberRemoved.party._id).eql(undefined);
});
it('updates memberCount', async () => {
let oldMemberCount = party.memberCount;
await partyleader.post(`/groups/${party._id}/removeMember/${partyMember._id}`);
await expect(partyleader.get(`/groups/${party._id}`)).to.eventually.have.property('memberCount', oldMemberCount - 1);
});
it('can remove other invites', async () => {
await partyleader.post(`/groups/${party._id}/removeMember/${partyInvitedUser._id}`);
let invitedUserWithoutInvite = await partyInvitedUser.get('/user');
expect(_.findIndex(invitedUserWithoutInvite.invitations.party, {id: party._id})).eql(-1);
});
});
});
@@ -290,12 +290,14 @@ describe('Post /groups/:groupId/invite', () => {
});
});
it('returns an error when invited user is already in the party', async () => {
it('returns an error when invited user is already in a party of more than 1 member', async () => {
let userToInvite = await generateUser();
let userToInvite2 = await generateUser();
await inviter.post(`/groups/${party._id}/invite`, {
uuids: [userToInvite._id],
uuids: [userToInvite._id, userToInvite2._id],
});
await userToInvite.post(`/groups/${party._id}/join`);
await userToInvite2.post(`/groups/${party._id}/join`);
await expect(inviter.post(`/groups/${party._id}/invite`, {
uuids: [userToInvite._id],
@@ -306,5 +308,18 @@ describe('Post /groups/:groupId/invite', () => {
message: t('userAlreadyInAParty'),
});
});
it('allow inviting an user to a party if he\'s partying solo', async () => {
let userToInvite = await generateUser();
await userToInvite.post('/groups', { // add user to a party
name: 'Another Test Party',
type: 'party',
});
await inviter.post(`/groups/${party._id}/invite`, {
uuids: [userToInvite._id],
});
expect((await userToInvite.get('/user')).invitations.party.id).to.equal(party._id);
});
});
});
@@ -22,6 +22,21 @@ describe('GET /tasks/user', () => {
expect(tasks[0]._id).to.equal(createdTasks[0]._id);
});
// TODO complete after task scoring is done
it('returns completed todos sorted by creation date if req.query.includeCompletedTodos is specified');
it('returns completed todos sorted by completion date if req.query.includeCompletedTodos is specified', async () => {
let todo1 = await user.post('/tasks/user', {text: 'todo to complete 1', type: 'todo'});
let todo2 = await user.post('/tasks/user', {text: 'todo to complete 2', type: 'todo'});
await user.sync();
let initialTodoCount = user.tasksOrder.todos.length;
await user.post(`/tasks/${todo2._id}/score/up`);
await user.post(`/tasks/${todo1._id}/score/up`);
await user.sync();
expect(user.tasksOrder.todos.length).to.equal(initialTodoCount - 2);
let allTodos = await user.get('/tasks/user?type=todo&includeCompletedTodos=true');
expect(allTodos.length).to.equal(initialTodoCount);
expect(allTodos[allTodos.length - 1].text).to.equal('todo to complete 1'); // last is the todo that was completed later
});
});
@@ -0,0 +1,41 @@
import {
generateUser,
generateGroup,
generateChallenge,
} from '../../../../helpers/api-integration/v3';
describe('POST /tasks/clearCompletedTodos', () => {
it('deletes all completed todos except the ones from a challenge', async () => {
let user = await generateUser({balance: 1});
let guild = await generateGroup(user);
let challenge = await generateChallenge(user, guild);
let initialTodoCount = user.tasksOrder.todos.length;
await user.post('/tasks/user', [
{text: 'todo 1', type: 'todo'},
{text: 'todo 2', type: 'todo'},
{text: 'todo 3', type: 'todo'},
{text: 'todo 4', type: 'todo'},
{text: 'todo 5', type: 'todo'},
]);
await user.post(`/tasks/challenge/${challenge._id}`, {
text: 'todo 6',
type: 'todo',
});
let tasks = await user.get('/tasks/user?type=todo');
expect(tasks.length).to.equal(initialTodoCount + 6);
for (let task of tasks) {
if (['todo 2', 'todo 3', 'todo 6'].indexOf(task.text) !== -1) {
await user.post(`/tasks/${task._id}/score/up`); // eslint-disable-line babel/no-await-in-loop
}
}
await user.post('/tasks/clearCompletedTodos');
let tasksUpdated = await user.get('/tasks/user?type=todo&includeCompletedTodos=true');
expect(tasksUpdated.length).to.equal(initialTodoCount + 4); // + 6 - 3 completed (but one is from challenge)
expect(tasksUpdated[tasksUpdated.length - 1].text).to.equal('todo 6');
});
});
+10 -17
View File
@@ -1,23 +1,16 @@
import baseModel from '../../../../../website/src/libs/api-v3/baseModel';
import mongoose from 'mongoose';
describe('Base model plugin', () => {
let schema = {
add () {
return true;
},
statics: {},
options: {},
pre () {
return true;
},
};
let schema;
beforeEach(() => {
schema = new mongoose.Schema();
sandbox.stub(schema, 'add');
});
it('adds a _id field to the schema', () => {
baseModel(schema);
schema.plugin(baseModel);
expect(schema.add).to.be.calledWith(sinon.match({
_id: sinon.match.object,
@@ -25,13 +18,13 @@ describe('Base model plugin', () => {
});
it('can add timestamps fields', () => {
baseModel(schema, {timestamps: true});
schema.plugin(baseModel, {timestamps: true});
expect(schema.add).to.be.calledTwice;
});
it('can sanitize input objects', () => {
baseModel(schema, {
schema.plugin(baseModel, {
noSet: ['noUpdateForMe'],
});
@@ -44,7 +37,7 @@ describe('Base model plugin', () => {
});
it('accepts an array of additional fields to sanitize at runtime', () => {
baseModel(schema, {
schema.plugin(baseModel, {
noSet: ['noUpdateForMe'],
});
@@ -58,7 +51,7 @@ describe('Base model plugin', () => {
it('can make fields private', () => {
baseModel(schema, {
schema.plugin(baseModel, {
private: ['amPrivate'],
});
@@ -76,7 +69,7 @@ describe('Base model plugin', () => {
toJSONTransform: sandbox.stub().returns(true),
};
baseModel(schema, options);
schema.plugin(baseModel, options);
let objToTransform = {ok: true, amPrivate: true};
let privatized = schema.options.toJSON.transform({}, objToTransform);
@@ -91,7 +84,7 @@ describe('Base model plugin', () => {
sanitizeTransform: sandbox.stub().returns(true),
};
baseModel(schema, options);
schema.plugin(baseModel, options);
expect(schema.options.toJSON.transform).to.exist;
let objToSanitize = {ok: true, noUpdateForMe: true};
@@ -0,0 +1,88 @@
import mongoose from 'mongoose';
import {
removeFromArray,
} from '../../../../../website/src/libs/api-v3/collectionManipulators';
describe('Collection Manipulators', () => {
describe('removeFromArray', () => {
it('removes element from array', () => {
let array = ['a', 'b', 'c', 'd'];
removeFromArray(array, 'c');
expect(array).to.not.include('c');
});
it('removes object from array', () => {
let array = [
{ id: 'a', foo: 'bar' },
{ id: 'b', foo: 'bar' },
{ id: 'c', foo: 'bar' },
{ id: 'd', foo: 'bar' },
{ id: 'e', foo: 'bar' },
];
removeFromArray(array, { id: 'c' });
expect(array).to.not.include({ id: 'c', foo: 'bar' });
});
it('does not change array if value is not found', () => {
let array = ['a', 'b', 'c', 'd'];
removeFromArray(array, 'z');
expect(array).to.have.a.lengthOf(4);
expect(array[0]).to.eql('a');
expect(array[1]).to.eql('b');
expect(array[2]).to.eql('c');
expect(array[3]).to.eql('d');
});
it('returns the removed element', () => {
let array = ['a', 'b', 'c'];
let result = removeFromArray(array, 'b');
expect(result).to.eql('b');
});
it('returns the removed object element', () => {
let array = [
{ id: 'a', foo: 'bar' },
{ id: 'b', foo: 'bar' },
{ id: 'c', foo: 'bar' },
{ id: 'd', foo: 'bar' },
{ id: 'e', foo: 'bar' },
];
let result = removeFromArray(array, { id: 'c' });
expect(result).to.eql({ id: 'c', foo: 'bar' });
});
it('returns false if item is not found', () => {
let array = ['a', 'b', 'c'];
let result = removeFromArray(array, 'z');
expect(result).to.eql(false);
});
it('persists removal of element when mongoose document is saved', async () => {
let schema = new mongoose.Schema({
array: Array,
});
let Model = mongoose.model('ModelToTestRemoveFromArray', schema);
let model = await new Model({
array: ['a', 'b', 'c'],
}).save(); // Initial creation
removeFromArray(model.array, 'b');
let savedModel = await model.save();
expect(savedModel.array).to.not.include('b');
});
});
});
-7
View File
@@ -31,13 +31,6 @@ describe('i18n', () => {
});
});
describe('localePath', () => {
it('is an absolute path to common/locales/', () => {
expect(localePath).to.match(/.*\/common\/locales\//);
expect(localePath);
});
});
describe('langCodes', () => {
it('is a list of all the language codes', () => {
expect(langCodes.sort()).to.eql(listOfLocales);
+4 -1
View File
@@ -1,5 +1,6 @@
import setupNconf from '../../../../../website/src/libs/api-v3/setupNconf';
import path from 'path';
import nconf from 'nconf';
describe('setupNconf', () => {
@@ -19,7 +20,9 @@ describe('setupNconf', () => {
expect(nconf.argv).to.be.calledOnce;
expect(nconf.env).to.be.calledOnce;
expect(nconf.file).to.be.calledOnce;
expect(nconf.file).to.be.calledWithMatch('user', /\/config.json$/);
let regexString = `\\${path.sep}config.json$`;
expect(nconf.file).to.be.calledWithMatch('user', new RegExp(regexString));
});
it('sets IS_PROD variable', () => {
@@ -53,4 +53,3 @@ describe('analytics middleware', () => {
expect(res.analytics.trackPurchase).to.eql(analyticsService.trackPurchase);
});
});