Merge branch 'develop' into flag_messages_in_api

This commit is contained in:
Blade Barringer
2015-11-04 07:56:34 -06:00
78 changed files with 8023 additions and 7778 deletions
+106
View File
@@ -0,0 +1,106 @@
# So you want to write API integration tests?
That's great! This README will serve as a quick primer for style conventions and practices for these tests.
## What is this?
These are integration tests for the Habitica API. They are performed by making REST requests to the API's endpoints and asserting on the data that is returned.
If the javascript looks weird to you, that's because it's written in [ES2015](http://www.ecma-international.org/ecma-262/6.0/) and transpiled by [Babel](https://babeljs.io/docs/learn-es2015/).
## How to run the tests
First, install gulp.
```bash
$ npm install -g gulp
```
To run the api tests, make sure the mongo db is up and running and then type this on the command line:
```bash
$ gulp test:api-v2
```
It may take a little while to get going, since it requires the web server to start up before the tests can run.
You can also run a watch command for the api tests. This will allow the tests to re-run automatically when you make changes in the `/test/api/v2/`.
```bash
$ gulp test:api-v2:watch
```
One caveat. If you have a severe syntax error in your files, the tests may fail to run, but they won't alert you that they are not running. If you ever find your test hanging for a while, run this to get the stackstrace. Once you've fixed the problem, you can resume running the watch comand.
```bash
$ gulp test:api:safe
```
## Structure
Each top level route has it's own directory. So, all the routes that begin with `/groups/` live in `/test/api/groups/`.
Each test should:
* encompase a single route
* begin with the REST parameter for the route
* display the full name of the route, swapping out `/` for `_`
* end with test.js
So, for the `POST` route `/groups/:id/leave`, it would be
```bash
POST-groups_id_leave.test.js
```
## Promises
To mitigate [callback hell](http://callbackhell.com/) :imp:, we've written helper methods 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', () => {
let request;
return generateUser().then((user) => { // We return the initial promise so this test can be run asyncronously
request = requester(user);
return request.post('/groups', {
type: 'party',
});
}).then((party) => { // the result of the promise above is the argument of the function
return request.put(`/groups/${party._id}`, {
name: 'My party',
});
}).then((result) => {
return request.get('/groups/party');
}).then((party) => {
expect(party.name).to.eql('My party');
});
});
```
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', () => {
let api = requester(user);
return expect(request.post('/groups', {
type: 'party',
})).to.eventually.have.deep.property('leader._id', user._id);
});
```
If the test is checking that the request returns an error, use the `.eventually.be.rejected.and.eql` syntax.
```js
it('returns an error', () => {
let api = requester(user);
return expect(request.get('/groups/id-of-a-party-that-user-does-not-belong-to'))
.to.eventually.be.rejected.and.eql({
code: 404,
text: t('messageGroupNotFound'),
});
});
```
## Questions?
Ask in the [Aspiring Coder's Guild](https://habitica.com/#/options/groups/guilds/68d4a01e-db97-4786-8ee3-05d612c5af6f)!
+122
View File
@@ -0,0 +1,122 @@
import {
generateGroup,
generateUser,
resetHabiticaDB,
requester,
} from '../../../helpers/api-integration.helper';
describe('GET /groups', () => {
const NUMBER_OF_PUBLIC_GUILDS = 3;
const NUMBER_OF_USERS_GUILDS = 2;
let user, api;
before(() => {
let leader, createdGroup;
// Set up a world with a mixture of public and private guilds
// Invite user to a few of them
return resetHabiticaDB().then(() => {
return generateUser();
}).then((_user) => {
user = _user;
return generateUser({
balance: 10,
});
}).then((_user) => {
leader = _user;
api = requester(leader);
let publicGuildWithUserAsMember = generateGroup(leader, {
name: 'public guild - is member',
type: 'guild',
privacy: 'public',
members: [leader._id, user._id],
});
let publicGuildWithoutUserAsMember = generateGroup(leader, {
name: 'public guild - is not member',
type: 'guild',
privacy: 'public',
});
let privateGuildWithUserAsMember = generateGroup(leader, {
name: 'private guild - is member',
type: 'guild',
privacy: 'private',
members: [leader._id, user._id],
});
let privateGuildWithoutUserAsMember = generateGroup(leader, {
name: 'private guild - is not member',
type: 'guild',
privacy: 'private',
});
let partyWithoutUserAsMember = generateGroup(leader, {
name: 'party name',
type: 'party',
privacy: 'private',
});
let promises = [
publicGuildWithUserAsMember,
publicGuildWithoutUserAsMember,
privateGuildWithUserAsMember,
privateGuildWithoutUserAsMember,
partyWithoutUserAsMember,
];
return Promise.all(promises);
}).then((groups) => {
api = requester(user);
return api.post('/groups', {
type: 'party',
name: 'user\'s party',
privacy: 'private',
});
});
});
context('no query passed in', () => {
xit('lists all public guilds, the tavern, user\'s party, and any private guilds that user is a part of - TODO query includes duplicates - IE, tavern is included as tavern and part of public guilds. Refactor so this is not the case');
});
context('tavern passed in as query', () => {
it('returns only the tavern', () => {
return expect(api.get('/groups', null, {type: 'tavern'}))
.to.eventually.have.a.lengthOf(1)
.and.to.have.deep.property('[0]')
.and.to.have.property('_id', 'habitrpg');
});
});
context('party passed in as query', () => {
it('returns only the user\'s party', () => {
return expect(api.get('/groups', null, {type: 'party'}))
.to.eventually.have.a.lengthOf(1)
.and.to.have.deep.property('[0]')
.and.to.have.property('leader', user._id);
});
});
context('public passed in as query', () => {
it('returns all public guilds', () => {
return expect(api.get('/groups', null, {type: 'public'}))
.to.eventually.have.a.lengthOf(NUMBER_OF_PUBLIC_GUILDS);
});
});
context('guilds passed in as query', () => {
it('returns all guilds user is a part of ', () => {
return expect(api.get('/groups', null, {type: 'guilds'}))
.to.eventually.have.a.lengthOf(NUMBER_OF_USERS_GUILDS);
});
});
});
+361
View File
@@ -0,0 +1,361 @@
import {
createAndPopulateGroup,
generateUser,
requester,
translate as t,
} from '../../../helpers/api-integration.helper';
import {
find,
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, (groupData, groupType) => {
context(`Member of a ${groupType}`, () => {
let leader, member, createdGroup;
before(() => {
return createAndPopulateGroup({
members: 30,
groupDetails: {
name: 'test guild',
type: 'guild',
privacy: 'public',
},
}).then((res) => {
leader = res.leader;
member = res.members[0];
createdGroup = res.group;
});
});
it('returns the group object', () => {
let api = requester(member);
return api.get(`/groups/${createdGroup._id}`).then((group) => {
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 members array to an array of user objects', () => {
let api = requester(member);
return api.get(`/groups/${createdGroup._id}`).then((group) => {
let member = group.members[0];
expect(member._id).to.exist;
expect(member.profile.name).to.exist;
expect(member.contributor).to.exist;
expect(member.achievements).to.exist;
expect(member.items).to.exist;
});
});
it('transforms leader id to leader object', () => {
let api = requester(member);
return api.get(`/groups/${createdGroup._id}`).then((group) => {
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;
});
});
it('includes the user in the members list', () => {
let api = requester(member);
return api.get(`/groups/${createdGroup._id}`).then((group) => {
let members = group.members;
let userInGroup = find(members, (user) => {
return member._id === user._id;
});
expect(userInGroup).to.be.ok;
});
});
});
});
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(() => {
return createAndPopulateGroup({
groupDetails: {
name: 'test guild',
type: 'guild',
privacy: 'public',
chat: [
chat1,
chat2,
chat3,
chat4,
chat5,
],
},
}).then((res) => {
group = res.group;
});
});
context('non-admin', () => {
let api;
beforeEach(() => {
return generateUser().then((user) => {
api = requester(user);
});
});
it('does not include messages with a flag count of 2 or greater', () => {
return api.get(`/groups/${group._id}`).then((_group) => {
expect(_group.chat).to.have.lengthOf(3);
expect(_group.chat[0].id).to.eql(chat1.id);
expect(_group.chat[1].id).to.eql(chat2.id);
expect(_group.chat[2].id).to.eql(chat3.id);
});
});
it('does not include user ids in flags object', () => {
return api.get(`/groups/${group._id}`).then((_group) => {
let chatWithOneFlag = _group.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 api;
beforeEach(() => {
return generateUser({
'contributor.admin': true,
}).then((user) => {
api = requester(user);
});
});
it('includes all messages', () => {
return api.get(`/groups/${group._id}`).then((_group) => {
expect(_group.chat).to.have.lengthOf(5);
expect(_group.chat[0].id).to.eql(chat1.id);
expect(_group.chat[1].id).to.eql(chat2.id);
expect(_group.chat[2].id).to.eql(chat3.id);
expect(_group.chat[3].id).to.eql(chat4.id);
expect(_group.chat[4].id).to.eql(chat5.id);
});
});
it('includes user ids in flags object', () => {
return api.get(`/groups/${group._id}`).then((_group) => {
let chatWithOneFlag = _group.chat[2];
expect(chatWithOneFlag.id).to.eql(chat3.id);
expect(chat3.flags).to.eql({ 'user-id': true });
expect(chatWithOneFlag.flags).to.eql(chat3.flags);
});
});
});
});
context('Non-member of a public guild', () => {
let leader, nonMember, createdGroup;
before(() => {
return createAndPopulateGroup({
members: 1,
groupDetails: {
name: 'test guild',
type: 'guild',
privacy: 'public',
},
}).then((res) => {
leader = res.leader;
createdGroup = res.group;
return generateUser();
}).then((user) => {
nonMember = user;
});
});
it('returns the group object for a non-member', () => {
let api = requester(nonMember);
return api.get(`/groups/${createdGroup._id}`)
.then((group) => {
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('does not include user in members list', () => {
let api = requester(nonMember);
return api.get(`/groups/${createdGroup._id}`).then((group) => {
let userInGroup = find(group.members, (user) => {
return nonMember._id === user._id;
});
expect(userInGroup).to.not.be.ok;
});
});
});
context('Private Guilds', () => {
let leader, nonMember, createdGroup;
before(() => {
return createAndPopulateGroup({
members: 1,
groupDetails: {
name: 'test guild',
type: 'guild',
privacy: 'private',
},
}).then((res) => {
leader = res.leader;
createdGroup = res.group;
return generateUser();
}).then((user) => {
nonMember = user;
});
});
it('does not return the group object for a non-member', () => {
let api = requester(nonMember);
return expect(api.get(`/groups/${createdGroup._id}`))
.to.eventually.be.rejected.and.eql({
code: 404,
text: t('messageGroupNotFound'),
});
});
});
context('Non-member of a party', () => {
let leader, nonMember, createdGroup;
before(() => {
return createAndPopulateGroup({
members: 1,
groupDetails: {
name: 'test party',
type: 'party',
privacy: 'private',
},
}).then((res) => {
leader = res.leader;
createdGroup = res.group;
return generateUser();
}).then((user) => {
nonMember = user;
});
});
it('does not return the group object for a non-member', () => {
let api = requester(nonMember);
return expect(api.get(`/groups/${createdGroup._id}`))
.to.eventually.be.rejected.and.eql({
code: 404,
text: t('messageGroupNotFound'),
});
});
});
context('Member of a party', () => {
let leader, member, createdGroup;
before(() => {
return createAndPopulateGroup({
members: 1,
groupDetails: {
name: 'test party',
type: 'party',
privacy: 'private',
},
}).then((res) => {
leader = res.leader;
createdGroup = res.group;
member = res.members[0];
});
});
it('returns the user\'s party if an id of "party" is passed in', () => {
let api = requester(member);
return api.get('/groups/party')
.then((group) => {
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(() => {
return generateUser().then((_user) => {
user = _user;
});
});
it('returns error if group does not exist', () => {
let api = requester(user);
return expect(api.get('/groups/group-that-does-not-exist'))
.to.eventually.be.rejected.and.eql({
code: 404,
text: t('messageGroupNotFound'),
});
});
});
});
+151
View File
@@ -0,0 +1,151 @@
import {
generateGroup,
generateUser,
requester,
translate as t,
} from '../../../helpers/api-integration.helper';
describe('POST /groups', () => {
context('All groups', () => {
let api, leader;
beforeEach(() => {
return generateUser().then((user) => {
leader = user;
api = requester(user);
});
});
xit('returns defaults? (TODO: it\'s possible to create a group without a type. Should the group default to party? Should we require type to be set?', () => {
return api.post('/groups').then((group) => {
expect(group._id).to.exist;
expect(group.name).to.eql(`${leader.profile.name}'s group`);
expect(group.type).to.eql('party');
expect(group.privacy).to.eql('private');
});
});
it('returns a group object', () => {
let group = {
name: 'Test Group',
type: 'party',
leaderOnly: { challenges: true },
description: 'Test Group Description',
leaderMessage: 'Test Group Message',
};
return api.post('/groups', group).then((createdGroup) => {
expect(createdGroup._id).to.exist;
expect(createdGroup.leader).to.eql(leader._id);
expect(createdGroup.name).to.eql(group.name);
expect(createdGroup.description).to.eql(group.description);
expect(createdGroup.leaderMessage).to.eql(group.leaderMessage);
expect(createdGroup.leaderOnly).to.eql(group.leaderOnly);
expect(createdGroup.memberCount).to.eql(1);
});
});
it('returns a populated members array', () => {
return api.post('/groups', {
type: 'party',
}).then((party) => {
let member = party.members[0];
expect(member._id).to.eql(leader._id);
expect(member.profile).to.eql(leader.profile);
expect(member.contributor).to.eql(leader.contributor);
});
});
});
context('Parties', () => {
let api, leader;
beforeEach(() => {
return generateUser().then((user) => {
leader = user;
api = requester(user);
});
});
it('allows party creation without gems', () => {
return expect(api.post('/groups', {
type: 'party',
})).to.eventually.have.property('_id');
});
it('prevents party creation if user is already in party', () => {
return expect(generateGroup(leader, {
type: 'party',
}).then((group) => {
return api.post('/groups', {
type: 'party',
});
})).to.eventually.be.rejected.and.eql({
code: 400,
text: t('messageGroupAlreadyInParty'),
});
});
xit('prevents creating a public party. TODO: it is possible to create a public party. Should we send back an error? Automatically switch the privacy to private?', () => {
return expect(api.post('/groups', {
type: 'party',
privacy: 'public',
})).to.eventually.be.rejected.and.eql({
code: 400,
text: 'Parties must be private',
});
});
});
context('Guilds', () => {
let api, leader;
beforeEach(() => {
return generateUser({
balance: 2,
}).then((user) => {
leader = user;
api = requester(user);
});
});
it('prevents guild creation when user does not have enough gems', () => {
return expect(generateUser({
balance: 0.75,
}).then((user) => {
api = requester(user);
return api.post('/groups', {
type: 'guild',
});
})).to.eventually.be.rejected.and.eql({
code: 401,
text: t('messageInsufficientGems'),
});
});
it('can create a public guild', () => {
return expect(api.post('/groups', {
type: 'guild',
privacy: 'public',
})).to.eventually.have.property('leader', leader._id);
});
it('can create a private guild', () => {
return expect(api.post('/groups', {
type: 'guild',
privacy: 'private',
})).to.eventually.have.property('leader', leader._id);
});
it('deducts gems from user and adds them to guild bank', () => {
return expect(api.post('/groups', {
type: 'guild',
privacy: 'private',
}).then((group) => {
expect(group.balance).to.eql(1);
return api.get('/user');
})).to.eventually.have.deep.property('balance', 1);
});
});
});
+75
View File
@@ -0,0 +1,75 @@
import {
generateGroup,
generateUser,
requester,
translate as t,
} from '../../../helpers/api-integration.helper';
describe('POST /groups/:id', () => {
context('user is not the leader of the group', () => {
let api, user, otherUser, groupUserDoesNotOwn;
beforeEach(() => {
return Promise.all([
generateUser({ balance: 10 }),
generateUser({ balance: 10 }),
]).then((users) => {
user = users[0];
otherUser = users[1];
api = requester(user);
return generateGroup(otherUser, {
name: 'Group not Owned By User',
type: 'guild',
privacy: 'public',
members: [user, otherUser],
});
}).then((group) => {
groupUserDoesNotOwn = group;
});
});
it('does not allow user to update group', () => {
return expect(api.post(`/groups/${groupUserDoesNotOwn._id}`, {
name: 'Change'
})).to.eventually.be.rejected.and.eql({
code: 401,
text: t('messageGroupOnlyLeaderCanUpdate'),
});
});
});
context('user is the leader of the group', () => {
let api, user, usersGroup;
beforeEach(() => {
return generateUser({
balance: 10,
}).then((_user) => {
user = _user;
api = requester(user);
return generateGroup(user, {
name: 'Original Group Title',
type: 'guild',
privacy: 'public',
});
}).then((group) => {
usersGroup = group;
});
});
it('allows user to update group', () => {
return api.post(`/groups/${usersGroup._id}`, {
name: 'New Group Title',
description: 'New group description',
}).then((group) => {
return api.get(`/groups/${usersGroup._id}`);
}).then((group) => {
expect(group.name).to.eql('New Group Title');
expect(group.description).to.eql('New group description');
});
});
});
});
@@ -0,0 +1,144 @@
import {
createAndPopulateGroup,
generateUser,
requester,
translate as t,
} from '../../../helpers/api-integration.helper';
import { each, find } from 'lodash';
describe('POST /groups/:id/join', () => {
context('user is already a member of the group', () => {
it('returns an error');
});
each({
'public guild': {type: 'guild', privacy: 'public'},
'private guild': {type: 'guild', privacy: 'private'},
'party': {type: 'party', privacy: 'private'},
}, (data, groupType) => {
context(`user has invitation to a ${groupType}`, () => {
let api, group, invitee;
beforeEach(() => {
return createAndPopulateGroup({
groupDetails: {
type: data.type,
privacy: data.privacy,
},
invites: 1,
}).then((res) => {
group = res.group;
invitee = res.invitees[0];
api = requester(invitee);
});
});
it(`allows user to join a ${groupType}`, () => {
return api.post(`/groups/${group._id}/join`).then((res) => {
return api.get(`/groups/${group._id}`);
}).then((_group) => {
let members = _group.members;
let userInGroup = find(members, (user) => {
return user._id === invitee._id;
});
expect(userInGroup).to.exist;
});
});
});
});
each({
'private guild': {type: 'guild', privacy: 'private'},
'party': {type: 'party', privacy: 'private'},
}, (data, groupType) => {
context(`user does not have an invitation to a ${groupType}`, () => {
let api, group, user;
beforeEach(() => {
return createAndPopulateGroup({
groupDetails: {
type: data.type,
privacy: data.privacy,
},
}).then((res) => {
group = res.group;
return generateUser();
}).then((generatedUser) => {
user = generatedUser;
api = requester(user);
});
});
it(`does not allow user to join a ${groupType}`, () => {
return expect(api.post(`/groups/${group._id}/join`).then((res) => {
return api.get(`/groups/${group._id}`);
})).to.eventually.be.rejected.and.eql({
code: 401,
text: t('messageGroupRequiresInvite'),
});
});
});
});
context('user does not have an invitation to a public group', () => {
let api, group, user;
beforeEach(() => {
return createAndPopulateGroup({
groupDetails: {
type: 'guild',
privacy: 'public',
},
}).then((res) => {
group = res.group;
return generateUser();
}).then((generatedUser) => {
user = generatedUser;
api = requester(user);
});
});
it('allows user to join a public guild', () => {
return api.post(`/groups/${group._id}/join`).then((res) => {
return api.get(`/groups/${group._id}`);
}).then((_group) => {
let members = _group.members;
let userInGroup = find(members, (member) => {
return user._id === user._id;
});
expect(userInGroup).to.exist;
});
});
});
context('public guild has no leader', () => {
let user, group;
beforeEach(() => {
return createAndPopulateGroup({
groupDetails: {
name: 'test guild',
type: 'guild',
privacy: 'public',
},
}).then((res) => {
group = res.group;
return requester(res.leader).post(`/groups/${group._id}/leave`);
}).then((res) => {
return generateUser();
}).then((generatedUser) => {
user = generatedUser;
});
});
it('makes the joining user the leader', () => {
let api = requester(user);
return expect(api.post(`/groups/${group._id}/join`).then((result) => {
return api.get(`/groups/${group._id}`);
})).to.eventually.have.deep.property('leader._id', user._id);
});
});
});
@@ -0,0 +1,161 @@
import {
checkExistence,
createAndPopulateGroup,
generateUser,
requester,
translate as t,
} from '../../../helpers/api-integration.helper';
import { find } from 'lodash';
describe('POST /groups/:id/leave', () => {
context('user is not member of the group', () => {
it('returns an error');
});
context('user is a non-leader member of a guild', () => {
let api, user, group;
beforeEach(() => {
return createAndPopulateGroup({
members: 3,
groupDetails: {
name: 'test guild',
type: 'guild',
privacy: 'public',
},
}).then((res) => {
user = res.members[0];
api = requester(user);
group = res.group;
});
});
it('leaves the group', () => {
return api.post(`/groups/${group._id}/leave`).then((result) => {
return api.get(`/groups/${group._id}`);
}).then((group) => {
let userInGroup = find(group.members, (member) => {
return member._id === user._id;
});
expect(userInGroup).to.not.be.ok;
});
});
});
context('user is the last member of a public guild', () => {
let api, user, group;
beforeEach(() => {
return createAndPopulateGroup({
groupDetails: {
name: 'test guild',
type: 'guild',
privacy: 'public',
},
}).then((res) => {
user = res.leader;
api = requester(user);
group = res.group;
});
});
it('leaves the group accessible', () => {
return expect(api.post(`/groups/${group._id}/leave`).then((result) => {
return api.get(`/groups/${group._id}`);
})).to.eventually.have.property('_id', group._id);
});
});
context('user is the last member of a private group', () => {
let api, user, group;
beforeEach(() => {
return createAndPopulateGroup({
groupDetails: {
name: 'test guild',
type: 'guild',
privacy: 'private',
},
}).then((res) => {
user = res.leader;
api = requester(user);
group = res.group;
});
});
it('group is deleted', () => {
return expect(api.post(`/groups/${group._id}/leave`).then((result) => {
return checkExistence('groups', group._id);
})).to.eventually.eql(false);
});
});
context('user is the last member of a private group with pending invites', () => {
let api, user, inviteeRequest1, inviteeRequest2, group;
beforeEach(() => {
return createAndPopulateGroup({
invites: 2,
groupDetails: {
name: 'test guild',
type: 'guild',
privacy: 'private',
},
}).then((res) => {
user = res.leader;
inviteeRequest1 = requester(res.invitees[0]);
inviteeRequest2 = requester(res.invitees[1]);
api = requester(user);
group = res.group;
});
});
it('deletes the group invitations from users', () => {
return api.post(`/groups/${group._id}/leave`).then((result) => {
return Promise.all([
expect(inviteeRequest1.get(`/user`))
.to.eventually.have.deep.property('invitations.guilds')
.and.to.be.empty,
expect(inviteeRequest2.get(`/user`))
.to.eventually.have.deep.property('invitations.guilds')
.and.to.be.empty,
]);
});
});
});
context('user is the last member of a party with pending invites', () => {
let api, user, inviteeRequest1, inviteeRequest2, group;
beforeEach(() => {
return createAndPopulateGroup({
invites: 2,
groupDetails: {
name: 'test party',
type: 'party',
privacy: 'private',
},
}).then((res) => {
user = res.leader;
inviteeRequest1 = requester(res.invitees[0]);
inviteeRequest2 = requester(res.invitees[1]);
api = requester(user);
group = res.group;
});
});
it('deletes the group invitations from users', () => {
return api.post(`/groups/${group._id}/leave`).then((result) => {
return Promise.all([
expect(inviteeRequest1.get(`/user`))
.to.eventually.have.deep.property('invitations.party')
.and.to.be.empty,
expect(inviteeRequest2.get(`/user`))
.to.eventually.have.deep.property('invitations.party')
.and.to.be.empty,
]);
});
});
});
});
@@ -0,0 +1,59 @@
import {
createAndPopulateGroup,
generateGroup,
generateUser,
requester,
translate as t,
} from '../../../helpers/api-integration.helper';
describe('POST /groups/:id/removeMember', () => {
context('user is not member of the group', () => {
it('returns an error');
});
context('user is a non-leader member of a guild', () => {
it('returns an error');
});
context('user is the leader of a guild', () => {
let api, leader, member, group;
beforeEach(() => {
return createAndPopulateGroup({
members: 1,
groupDetails: {
name: 'test group',
type: 'guild',
privacy: 'public',
},
}).then((res) => {
leader = res.leader;
member = res.members[0];
group = res.group;
api = requester(leader);
});
});
it('does not allow leader to remove themselves', () => {
return expect(api.post(`/groups/${group._id}/removeMember`, null, {
uuid: leader._id,
})).to.eventually.be.rejected.and.eql({
code: 401,
text: t('messageGroupCannotRemoveSelf'),
});
});
it('can remove other members of guild', () => {
return api.post(`/groups/${group._id}/removeMember`, null, {
uuid: member._id,
}).then((res) => {
return api.get(`/groups/${group._id}`);
}).then((guild) => {
expect(guild.members).to.have.a.lengthOf(1);
expect(guild.members[0]._id).to.not.eql(member._id);
});
});
});
});
@@ -0,0 +1,42 @@
import {
createAndPopulateGroup,
generateUser,
requester,
translate as t,
} from '../../../../helpers/api-integration.helper';
describe('DELETE /groups/:id/chat', () => {
let api, group, message, user;
beforeEach(() => {
return createAndPopulateGroup({
groupDetails: {
type: 'guild',
privacy: 'public',
},
}).then((res) => {
group = res.group;
user = res.leader;
api = requester(user);
return api.post(`/groups/${group._id}/chat`, null, { message: 'Some message', });
}).then((res) => {
message = res.message;
});
});
it('deletes a message', () => {
return api.del(`/groups/${group._id}/chat/${message.id}`).then((res) => {
return api.get(`/groups/${group._id}/chat/`);
}).then((messages) => {
expect(messages).to.have.length(0);
});
});
it('returns an error is message does not exist', () => {
return expect(api.del(`/groups/${group._id}/chat/some-fake-id`)).to.eventually.be.rejected.and.eql({
code: 404,
text: t('messageGroupChatNotFound'),
});
});
});
@@ -0,0 +1,48 @@
import {
createAndPopulateGroup,
generateUser,
requester,
translate as t,
} from '../../../../helpers/api-integration.helper';
describe('GET /groups/:id/chat', () => {
context('group with multiple messages', () => {
let group, member, message1, message2, user;
beforeEach(() => {
return createAndPopulateGroup({
groupDetails: {
type: 'guild',
privacy: 'public',
},
members: 1,
}).then((res) => {
group = res.group;
user = res.leader;
member = res.members[0];
return requester(member).post(`/groups/${group._id}/chat`, null, { message: 'Group member message' });
}).then((res) => {
message1 = res.message;
return requester(user).post(`/groups/${group._id}/chat`, null, { message: 'User message' });
}).then((res) => {
message2 = res.message;
});
});
it('gets messages', () => {
let api = requester(user);
return api.get(`/groups/${group._id}/chat`).then((messages) => {
expect(messages).to.have.length(2);
let message = messages[0];
expect(message.id).to.exist;
expect(message.text).to.exist;
expect(message.uuid).to.exist;
});
});
});
});
@@ -0,0 +1,46 @@
import {
createAndPopulateGroup,
generateUser,
requester,
translate as t,
} from '../../../../helpers/api-integration.helper';
describe('POST /groups/:id/chat', () => {
let api, group, user;
beforeEach(() => {
return createAndPopulateGroup({
groupDetails: {
type: 'guild',
privacy: 'public',
},
}).then((res) => {
group = res.group;
user = res.leader;
api = requester(user);
});
});
it('creates a chat message', () => {
return api.post(`/groups/${group._id}/chat`, null, {
message: 'Test Message',
}).then((res) => {
let message = res.message;
expect(message.id).to.exist;
expect(message.timestamp).to.exist;
expect(message.text).to.eql('Test Message');
expect(message.uuid).to.eql(user._id);
});
});
it('does not post an empty message', () => {
return expect(api.post(`/groups/${group._id}/chat`, null, {
message: '',
})).to.eventually.be.rejected.and.eql({
code: 400,
text: t('messageGroupChatBlankMessage'),
});
});
});
@@ -0,0 +1,102 @@
import {
createAndPopulateGroup,
generateUser,
requester,
translate as t,
} from '../../../../helpers/api-integration.helper';
describe('POST /groups/:id/chat/:id/flag', () => {
context('another member\'s message', () => {
let group, member, message, user;
beforeEach(() => {
return createAndPopulateGroup({
groupDetails: {
type: 'guild',
privacy: 'public',
},
members: 1,
}).then((res) => {
group = res.group;
user = res.leader;
member = res.members[0];
return requester(member)
.post(`/groups/${group._id}/chat`, null, { message: 'Group member message', });
}).then((res) => {
message = res.message;
});
});
it('flags message', () => {
let api = requester(user);
return api.post(`/groups/${group._id}/chat/${message.id}/flag`).then((messages) => {
return api.get(`/groups/${group._id}/chat`);
}).then((messages) => {
let message = messages[0];
expect(message.flagCount).to.eql(1);
});
});
});
context('own message', () => {
let api, group, message, user;
beforeEach(() => {
return createAndPopulateGroup({
groupDetails: {
type: 'guild',
privacy: 'public',
members: 1,
},
}).then((res) => {
group = res.group;
user = res.leader;
api = requester(user);
return api.post(`/groups/${group._id}/chat`, null, { message: 'User\'s own message', });
}).then((res) => {
message = res.message;
});
});
it('cannot flag message', () => {
let api = requester(user);
return expect(api.post(`/groups/${group._id}/chat/${message.id}/flag`))
.to.eventually.be.rejected.and.eql({
code: 401,
text: t('messageGroupChatFlagOwnMessage'),
});
});
});
context('nonexistant message', () => {
let api, group, message, user;
beforeEach(() => {
return createAndPopulateGroup({
groupDetails: {
type: 'guild',
privacy: 'public',
},
}).then((res) => {
group = res.group;
user = res.leader;
api = requester(user);
});
});
it('returns error', () => {
let api = requester(user);
return expect(api.post(`/groups/${group._id}/chat/non-existant-message/flag`))
.to.eventually.be.rejected.and.eql({
code: 404,
text: t('messageGroupChatNotFound'),
});
});
});
});
@@ -0,0 +1,111 @@
import {
createAndPopulateGroup,
generateUser,
requester,
translate as t,
} from '../../../../helpers/api-integration.helper';
describe('POST /groups/:id/chat/:id/like', () => {
context('another member\'s message', () => {
let group, member, message, user;
beforeEach(() => {
return createAndPopulateGroup({
groupDetails: {
type: 'guild',
privacy: 'public',
},
members: 1,
}).then((res) => {
group = res.group;
user = res.leader;
member = res.members[0];
return requester(member)
.post(`/groups/${group._id}/chat`, null, { message: 'Group member message', });
}).then((res) => {
message = res.message;
});
});
it('likes message', () => {
let api = requester(user);
return api.post(`/groups/${group._id}/chat/${message.id}/like`).then((messages) => {
let message = messages[0];
expect(message.likes[user._id]).to.eql(true);
});
});
it('returns the message object', () => {
let api = requester(user);
return api.post(`/groups/${group._id}/chat/${message.id}/like`).then((messages) => {
let message = messages[0];
expect(message.text).to.eql('Group member message');
expect(message.uuid).to.eql(member._id);
expect(message.user).to.eql(member.profile.name);
});
});
});
context('own message', () => {
let api, group, message, user;
beforeEach(() => {
return createAndPopulateGroup({
groupDetails: {
type: 'guild',
privacy: 'public',
members: 1,
},
}).then((res) => {
group = res.group;
user = res.leader;
api = requester(user);
return api.post(`/groups/${group._id}/chat`, null, { message: 'User\'s own message', });
}).then((res) => {
message = res.message;
});
});
it('cannot like message', () => {
let api = requester(user);
return expect(api.post(`/groups/${group._id}/chat/${message.id}/like`))
.to.eventually.be.rejected.and.eql({
code: 401,
text: t('messageGroupChatLikeOwnMessage'),
});
});
});
context('nonexistant message', () => {
let api, group, message, user;
beforeEach(() => {
return createAndPopulateGroup({
groupDetails: {
type: 'guild',
privacy: 'public',
},
}).then((res) => {
group = res.group;
user = res.leader;
api = requester(user);
});
});
it('returns error', () => {
let api = requester(user);
return expect(api.post(`/groups/${group._id}/chat/non-existant-message/like`))
.to.eventually.be.rejected.and.eql({
code: 404,
text: t('messageGroupChatNotFound'),
});
});
});
});
+140
View File
@@ -0,0 +1,140 @@
import {
generateUser,
requester,
translate as t,
} from '../../../helpers/api-integration.helper';
import {v4 as generateRandomUserName} from 'uuid';
describe('POST /register', () => {
context('username and email are free', () => {
it('registers a new user', () => {
let api = requester();
let username = generateRandomUserName();
let email = `${username}@example.com`;
let password = 'password';
return api.post('/register', {
username: username,
email: email,
password: password,
confirmPassword: password,
}).then((user) => {
expect(user._id).to.exist;
expect(user.apiToken).to.exist;
expect(user.auth.local.username).to.eql(username);
});
});
it('requires password and confirmPassword to match', () => {
let api = requester();
let username = generateRandomUserName();
let email = `${username}@example.com`;
let password = 'password';
let confirmPassword = 'not password';
return expect(api.post('/register', {
username: username,
email: email,
password: password,
confirmPassword: confirmPassword,
})).to.eventually.be.rejected.and.eql({
code: 401,
text: t('messageAuthPasswordMustMatch'),
});
});
it('requires a username', () => {
let api = requester();
let email = `${generateRandomUserName()}@example.com`;
let password = 'password';
let confirmPassword = 'password';
return expect(api.post('/register', {
email: email,
password: password,
confirmPassword: confirmPassword,
})).to.eventually.be.rejected.and.eql({
code: 401,
text: t('messageAuthCredentialsRequired'),
});
});
it('requires an email', () => {
let api = requester();
let username = generateRandomUserName();
let password = 'password';
let confirmPassword = 'password';
return expect(api.post('/register', {
username: username,
password: password,
confirmPassword: confirmPassword,
})).to.eventually.be.rejected.and.eql({
code: 401,
text: t('messageAuthCredentialsRequired'),
});
});
it('requires a password', () => {
let api = requester();
let username = generateRandomUserName();
let email = `${username}@example.com`;
let confirmPassword = 'password';
return expect(api.post('/register', {
username: username,
email: email,
confirmPassword: confirmPassword,
})).to.eventually.be.rejected.and.eql({
code: 401,
text: t('messageAuthCredentialsRequired'),
});
});
});
context('login is already taken', () => {
let username, email;
beforeEach(() => {
username = generateRandomUserName();
email = `${username}@example.com`;
return generateUser({
'auth.local.username': username,
'auth.local.lowerCaseUsername': username,
'auth.local.email': email
});
});
it('rejects if username is already taken', () => {
let api = requester();
let uniqueEmail = `${generateRandomUserName()}@exampe.com`;
let password = 'password';
return expect(api.post('/register', {
username: username,
email: uniqueEmail,
password: password,
confirmPassword: password,
})).to.eventually.be.rejected.and.eql({
code: 401,
text: t('messageAuthUsernameTaken'),
});
});
it('rejects if email is already taken', () => {
let api = requester();
let uniqueUsername = generateRandomUserName();
let password = 'password';
return expect(api.post('/register', {
username: uniqueUsername,
email: email,
password: password,
confirmPassword: password,
})).to.eventually.be.rejected.and.eql({
code: 401,
text: t('messageAuthEmailTaken'),
});
});
});
});
+10
View File
@@ -0,0 +1,10 @@
import {requester} from '../../../helpers/api-integration.helper';
describe('Status', () => {
it('returns a status of up when server is up', () => {
let api = requester();
return expect(api.get('/status'))
.to.eventually.eql({status: 'up'});
});
});
+76
View File
@@ -0,0 +1,76 @@
import {
checkExistence,
generateGroup,
generateUser,
requester,
translate as t,
} from '../../../helpers/api-integration.helper';
describe('DELETE /user', () => {
let api, user;
beforeEach(() => {
return generateUser().then((usr) => {
api = requester(usr);
user = usr;
});
});
it('deletes the user', () => {
return expect(api.del('/user').then((fetchedUser) => {
return checkExistence('users', user._id);
})).to.eventually.eql(false);
});
context('user has active subscription', () => {
it('does not delete account');
});
context('last member of a party', () => {
let party;
beforeEach(() => {
return generateGroup(user, {
type: 'party',
privacy: 'private'
}).then((group) => {
party = group;
});
});
it('deletes party when user is the only member', () => {
return expect(api.del('/user').then((result) => {
return checkExistence('groups', party._id);
})).to.eventually.eql(false);
});
});
context('last member of a private guild', () => {
let guild;
beforeEach(() => {
return generateGroup(user, {
type: 'guild',
privacy: 'private'
}).then((group) => {
guild = group;
});
});
it('deletes guild when user is the only member', () => {
return expect(api.del('/user').then((result) => {
return checkExistence('groups', guild._id);
})).to.eventually.eql(false);
});
});
context('groups with multiple members', () => {
it('removes user from all groups user was a part of');
it('chooses new group leader for any group user was the leader of');
});
context('pending invitation to group', () => {
it('removes invitations from groups');
});
});
+33
View File
@@ -0,0 +1,33 @@
import {
generateUser,
requester,
} from '../../../helpers/api-integration.helper';
describe('GET /user', () => {
let user;
before(() => {
return generateUser().then((usr) => {
let api = requester(usr);
return api.get('/user');
}).then((fetchedUser) => {
user = fetchedUser;
});
});
it('gets the user object', () => {
expect(user._id).to.eql(user._id);
expect(user.auth.local.username).to.eql(user.auth.local.username);
expect(user.todos).to.eql(user.todos);
expect(user.items).to.eql(user.items);
});
it('does not include password information', () => {
expect(user.auth.local.hashed_password).to.not.exist
expect(user.auth.local.salt).to.not.exist
});
it('does not include api token', () => {
expect(user.apiToken).to.not.exist
});
});
+20
View File
@@ -0,0 +1,20 @@
import {
generateUser,
requester,
} from '../../../helpers/api-integration.helper';
describe('GET /user/tags', () => {
let api, user;
beforeEach(() => {
return generateUser().then((usr) => {
user = usr;
api = requester(usr);
});
});
it('gets the user\'s tags', () => {
return expect(api.get('/user/tags'))
.to.eventually.eql(user.tags);
});
});
+29
View File
@@ -0,0 +1,29 @@
import {
generateUser,
requester,
translate as t,
} from '../../../helpers/api-integration.helper';
describe('GET /user/tags/id', () => {
let api, user;
beforeEach(() => {
return generateUser().then((usr) => {
user = usr;
api = requester(usr);
});
});
it('gets a user\'s tag by id', () => {
return expect(api.get('/user/tags/' + user.tags[0].id))
.to.eventually.eql(user.tags[0]);
});
it('fails for non-existent tags', () => {
return expect(api.get('/user/tags/not-an-id'))
.to.eventually.be.rejected.and.eql({
code: 404,
text: t('messageTagNotFound'),
});
});
});
+76
View File
@@ -0,0 +1,76 @@
import {
generateUser,
requester,
translate as t,
} from '../../../helpers/api-integration.helper';
import { each } from 'lodash';
describe('PUT /user', () => {
let api, user;
beforeEach(() => {
return generateUser().then((usr) => {
user = usr;
api = requester(user);
});
});
context('allowed operations', () => {
it('updates the user', () => {
return api.put('/user', {
'profile.name' : 'Frodo',
'preferences.costume': true,
'stats.hp': 14,
}).then((updatedUser) => {
expect(updatedUser.profile.name).to.eql('Frodo');
expect(updatedUser.preferences.costume).to.eql(true);
expect(updatedUser.stats.hp).to.eql(14);
});
});
});
context('top level protected operations', () => {
let protectedOperations = {
'gem balance': {balance: 100},
'auth': {'auth.blocked': true, 'auth.timestamps.created': new Date()},
'contributor': {'contributor.level': 9, 'contributor.admin': true, 'contributor.text': 'some text'},
'backer': {'backer.tier': 10, 'backer.npc': 'Bilbo'},
'subscriptions': {'purchased.plan.extraMonths': 500, 'purchased.plan.consecutive.trinkets': 1000},
'customization gem purchases': {'purchased.background.tavern': true, 'purchased.skin.bear': true},
'tasks': {todos: [], habits: [], dailys: [], rewards: []},
};
each(protectedOperations, (data, testName) => {
it(`does not allow updating ${testName}`, () => {
let errorText = [];
each(data, (value, operation) => {
errorText.push(t('messageUserOperationProtected', { operation: operation }));
});
return expect(api.put('/user', data)).to.eventually.be.rejected.and.eql({
code: 401,
text: errorText,
});
});
});
});
context('sub-level protected operations', () => {
let protectedOperations = {
'class stat': {'stats.class': 'wizard'},
};
each(protectedOperations, (data, testName) => {
it(`does not allow updating ${testName}`, () => {
let errorText = [];
each(data, (value, operation) => {
errorText.push(t('messageUserOperationProtected', { operation: operation }));
});
return expect(api.put('/user', data)).to.eventually.be.rejected.and.eql({
code: 401,
text: errorText,
});
});
});
});
});
@@ -0,0 +1,102 @@
import {
generateUser,
requester,
} from '../../../../helpers/api-integration.helper';
import { each } from 'lodash';
describe('GET /user/anonymized', () => {
let api, user;
before(() => {
return generateUser({
'inbox.messages' : {
'the-message-id' : {
sort : 214,
user : 'Some user',
backer : {},
contributor : {
text : 'Blacksmith',
level : 2,
contributions : 'Made some contributions',
admin : false
},
uuid : 'some-users-uuid',
flagCount : 0,
flags : {},
likes : {},
timestamp : 1444154258699.0000000000000000,
text : 'Lorem ipsum',
id : 'the-messages-id',
sent : true
}
}
}).then((usr) => {
api = requester(usr);
return api.post('/user/tasks', {
text: 'some private text',
notes: 'some private notes',
checklist: [
{text: 'a private checklist'},
{text: 'another private checklist'},
],
type: 'daily',
});
}).then((result) => {
return api.get('/user/anonymized');
}).then((anonymizedUser) => {
user = anonymizedUser;
});
});
it('retains user id', () => {
expect(user._id).to.exist;
});
it('removes credentials and financial information', () => {
expect(user.apiToken).to.not.exist;
expect(user.auth.local).to.not.exist;
expect(user.auth.facebook).to.not.exist;
expect(user.purchased.plan).to.not.exist;
});
it('removes profile information', () => {
expect(user.profile).to.not.exist;
expect(user.contributor).to.not.exist;
expect(user.achievements.challenges).to.not.exist;
});
it('removes social information', () => {
expect(user.newMessages).to.not.exist;
expect(user.invitations).to.not.exist;
expect(user.items.special.nyeReceived).to.not.exist;
expect(user.items.special.valentineReceived).to.not.exist;
each(user.inbox.messages, (msg) => {
expect(msg.text).to.eql('inbox message text');
});
});
it('anonymizes task info', () => {
each(['habits', 'todos', 'dailys', 'rewards'], (tasks) => {
each(user[tasks], (task) => {
expect(task.text).to.eql('task text');
expect(task.notes).to.eql('task notes');
each(task.checklist, (box) => {
expect(box.text).to.match(/item\d*/);
});
});
});
});
it('anonymizes tags', () => {
each(user.tags, (tag) => {
expect(tag.name).to.eql('tag');
expect(tag.challenge).to.eql('challenge');
});
});
it('removes webhooks', () => {
expect(user.webhooks).to.not.exist;
});
});
@@ -0,0 +1,69 @@
import {
generateUser,
requester,
translate as t,
} from '../../../../helpers/api-integration.helper';
import { each } from 'lodash';
describe('POST /user/batch-update', () => {
let api, user;
beforeEach(() => {
return generateUser().then((usr) => {
user = usr;
api = requester(user);
});
});
context('allowed operations', () => {
it('makes batch operations', () => {
let task;
return api.get('/user/tasks').then((tasks) => {
task = tasks[0];
return api.post('/user/batch-update', [
{op: 'update', body: {'stats.hp': 30}},
{op: 'update', body: {'profile.name': 'Samwise'}},
{op: 'score', params: { direction: 'up', id: task.id }},
]);
}).then((user) => {
expect(user.stats.hp).to.eql(30);
expect(user.profile.name).to.eql('Samwise');
return api.get(`/user/tasks/${task.id}`);
}).then((task) => {
expect(task.value).to.be.greaterThan(0);
});
});
});
context('development only operations', () => {
let protectedOperations = {
'Add Ten Gems': 'addTenGems',
'Add Hourglass': 'addHourglass',
};
each(protectedOperations, (operation, description) => {
it(`it sends back a 500 error for ${description} operation`, () => {
return expect(api.post('/user/batch-update', [
{op: operation},
])).to.eventually.be.rejected.and.eql({
code: 500,
text: t('messageUserOperationNotFound', { operation: operation}),
});
});
});
});
context('unknown operations', () => {
it('sends back a 500 error', () => {
return expect(api.post('/user/batch-update', [
{op: 'aNotRealOperation'},
])).to.eventually.be.rejected.and.eql({
code: 500,
text: t('messageUserOperationNotFound', { operation: 'aNotRealOperation' }),
});
});
});
});
@@ -0,0 +1,26 @@
import {
generateUser,
requester,
} from '../../../../helpers/api-integration.helper';
describe('POST /user/pushDevice', () => {
let api;
beforeEach(() => {
return generateUser().then((user) => {
api = requester(user);
});
});
it('registers a device id', () => {
return api.post('/user/pushDevice', {
regId: '123123',
type: 'android',
}).then((devices) => {
let device = devices[0];
expect(device._id).to.exist;
expect(device.regId).to.eql('123123');
expect(device.type).to.eql('android');
});
});
});
@@ -0,0 +1,45 @@
import {
generateUser,
requester,
translate as t,
} from '../../../../helpers/api-integration.helper';
describe('DELETE /user/tasks/:id', () => {
let api, user, task;
beforeEach(() => {
return generateUser().then((_user) => {
user = _user;
task = user.todos[0];
api = requester(user);
});
});
it('deletes a task', () => {
return expect(api.del(`/user/tasks/${task.id}`)
.then((res) => {
return api.get(`/user/tasks/${task.id}`);
})).to.eventually.be.rejected.and.eql({
code: 404,
text: t('messageTaskNotFound'),
});
});
it('returns an error if the task does not exist', () => {
return expect(api.del('/user/tasks/task-that-does-not-exist'))
.to.eventually.be.rejected.and.eql({
code: 404,
text: t('messageTaskNotFound'),
});
});
it('does not delete another user\'s task', () => {
return expect(generateUser().then((otherUser) => {
let otherUsersTask = otherUser.todos[0];
return api.del(`/user/tasks/${otherUsersTask.id}`);
})).to.eventually.be.rejected.and.eql({
code: 404,
text: 'Task not found.',
});
});
});
+28
View File
@@ -0,0 +1,28 @@
import {
generateUser,
requester,
translate as t,
} from '../../../../helpers/api-integration.helper';
describe('GET /user/tasks/', () => {
let api, user;
beforeEach(() => {
return generateUser().then((_user) => {
user = _user;
api = requester(user);
});
});
it('gets all tasks', () => {
return api.get(`/user/tasks/`).then((tasks) => {
expect(tasks).to.be.an('array');
expect(tasks.length).to.be.greaterThan(4);
let task = tasks[0];
expect(task.id).to.exist;
expect(task.type).to.exist;
expect(task.text).to.exist;
});
});
});
@@ -0,0 +1,45 @@
import {
generateUser,
requester,
translate as t,
} from '../../../../helpers/api-integration.helper';
describe('GET /user/tasks/:id', () => {
let api, user, task;
beforeEach(() => {
return generateUser().then((_user) => {
user = _user;
task = user.todos[0];
api = requester(user);
});
});
it('gets a task', () => {
return api.get(`/user/tasks/${task.id}`).then((foundTask) => {
expect(foundTask.id).to.eql(task.id);
expect(foundTask.text).to.eql(task.text);
expect(foundTask.notes).to.eql(task.notes);
expect(foundTask.value).to.eql(task.value);
expect(foundTask.type).to.eql(task.type);
});
});
it('returns an error if the task does not exist', () => {
return expect(api.get('/user/tasks/task-that-does-not-exist'))
.to.eventually.be.rejected.and.eql({
code: 404,
text: t('messageTaskNotFound'),
});
});
it('does not get another user\'s task', () => {
return expect(generateUser().then((otherUser) => {
let otherUsersTask = otherUser.todos[0];
return api.get(`/user/tasks/${otherUsersTask.id}`);
})).to.eventually.be.rejected.and.eql({
code: 404,
text: t('messageTaskNotFound'),
});
});
});
+71
View File
@@ -0,0 +1,71 @@
import {
generateUser,
requester,
translate as t,
} from '../../../../helpers/api-integration.helper';
describe('POST /user/tasks', () => {
let api, user;
beforeEach(() => {
return generateUser().then((_user) => {
user = _user;
api = requester(user);
});
});
it('creates a task', () => {
return api.post('/user/tasks').then((task) => {
expect(task.id).to.exist;
});
});
it('creates a habit by default', () => {
return expect(api.post('/user/tasks'))
.to.eventually.have.property('type', 'habit');
});
it('creates a task with specified values', () => {
return api.post('/user/tasks', {
type: 'daily',
text: 'My task',
notes: 'My notes',
frequency: 'daily',
}).then((task) => {
expect(task.type).to.eql('daily');
expect(task.text).to.eql('My task');
expect(task.notes).to.eql('My notes');
expect(task.frequency).to.eql('daily');
});
});
it('does not create a task with an id that already exists', () => {
let todo = user.todos[0];
return expect(api.post('/user/tasks', {
id: todo.id,
})).to.eventually.be.rejected.and.eql({
code: 409,
text: t('messageDuplicateTaskID'),
});
});
xit('TODO: no error is thrown - throws a 500 validation error if invalid type is posted', () => {
return expect(api.post('/user/tasks', {
type: 'not-valid',
})).to.eventually.be.rejected.and.eql({
code: 500,
text: 'Cannot call method \'indexOf\' of undefined',
});
});
xit('TODO: no error is thrown - throws a 500 validation error if invalid data is posted', () => {
return expect(api.post('/user/tasks', {
frequency: 'not-valid',
})).to.eventually.be.rejected.and.eql({
code: 500,
text: 'Task validation failed',
});
});
});
@@ -0,0 +1,71 @@
import {
generateUser,
requester,
translate as t,
} from '../../../../helpers/api-integration.helper';
describe('PUT /user/tasks/:id', () => {
let api, user, task;
beforeEach(() => {
return generateUser().then((_user) => {
user = _user;
api = requester(user);
task = user.todos[0];
});
});
it('does not update the id of the task', () => {
return api.put(`/user/tasks/${task.id}`, {
id: 'some-thing',
}).then((updatedTask) => {
expect(updatedTask.id).to.eql(task.id);
expect(updatedTask.id).to.not.eql('some-thing');
});
});
it('does not update the type of the task', () => {
return api.put(`/user/tasks/${task.id}`, {
type: 'habit',
}).then((updatedTask) => {
expect(updatedTask.type).to.eql(task.type);
expect(updatedTask.type).to.not.eql('habit');
});
});
it('updates text, attribute, priority, value and notes', () => {
return api.put(`/user/tasks/${task.id}`, {
text: 'new text',
notes: 'new notes',
value: 10000,
priority: .5,
attribute: 'str',
}).then((updatedTask) => {
expect(updatedTask.text).to.eql('new text');
expect(updatedTask.notes).to.eql('new notes');
expect(updatedTask.value).to.eql(10000);
expect(updatedTask.priority).to.eql(.5);
expect(updatedTask.attribute).to.eql('str');
});
});
it('returns an error if the task does not exist', () => {
return expect(api.put('/user/tasks/task-id-that-does-not-exist'))
.to.eventually.be.rejected.and.eql({
code: 404,
text: 'Task not found.',
});
});
it('does not update another user\'s task', () => {
return expect(generateUser().then((otherUser) => {
let otherUsersTask = otherUser.todos[0];
return api.put(`/user/tasks/${otherUsersTask._id}`, {
name: 'some name',
});
})).to.eventually.be.rejected.and.eql({
code: 404,
text: 'Task not found.',
});
});
});