Merge branch 'develop' into Hus274-7793

This commit is contained in:
Blade Barringer
2016-08-05 12:55:00 -05:00
333 changed files with 14467 additions and 24545 deletions

View File

@@ -46,7 +46,14 @@ describe('GET /challenges/:challengeId', () => {
id: groupLeader._id,
profile: {name: groupLeader.profile.name},
});
expect(chal.group).to.eql(_.pick(group, ['_id', 'id', 'name', 'type', 'privacy']));
expect(chal.group).to.eql({
_id: group._id,
id: group.id,
name: group.name,
type: group.type,
privacy: group.privacy,
leader: groupLeader.id,
});
});
});
@@ -91,7 +98,14 @@ describe('GET /challenges/:challengeId', () => {
id: groupLeader._id,
profile: {name: groupLeader.profile.name},
});
expect(chal.group).to.eql(_.pick(group, ['_id', 'id', 'name', 'type', 'privacy']));
expect(chal.group).to.eql({
_id: group._id,
id: group.id,
name: group.name,
type: group.type,
privacy: group.privacy,
leader: groupLeader.id,
});
});
});
@@ -136,7 +150,14 @@ describe('GET /challenges/:challengeId', () => {
id: groupLeader.id,
profile: {name: groupLeader.profile.name},
});
expect(chal.group).to.eql(_.pick(group, ['_id', 'id', 'name', 'type', 'privacy']));
expect(chal.group).to.eql({
_id: group._id,
id: group.id,
name: group.name,
type: group.type,
privacy: group.privacy,
leader: groupLeader.id,
});
});
});
});

View File

@@ -45,6 +45,7 @@ describe('GET challenges/user', () => {
type: publicGuild.type,
privacy: publicGuild.privacy,
name: publicGuild.name,
leader: publicGuild.leader._id,
});
});
@@ -64,6 +65,7 @@ describe('GET challenges/user', () => {
type: publicGuild.type,
privacy: publicGuild.privacy,
name: publicGuild.name,
leader: publicGuild.leader._id,
});
let foundChallenge2 = _.find(challenges, { _id: challenge2._id });
expect(foundChallenge2).to.exist;
@@ -78,6 +80,7 @@ describe('GET challenges/user', () => {
type: publicGuild.type,
privacy: publicGuild.privacy,
name: publicGuild.name,
leader: publicGuild.leader._id,
});
});
@@ -97,6 +100,7 @@ describe('GET challenges/user', () => {
type: publicGuild.type,
privacy: publicGuild.privacy,
name: publicGuild.name,
leader: publicGuild.leader._id,
});
let foundChallenge2 = _.find(challenges, { _id: challenge2._id });
expect(foundChallenge2).to.exist;
@@ -111,6 +115,7 @@ describe('GET challenges/user', () => {
type: publicGuild.type,
privacy: publicGuild.privacy,
name: publicGuild.name,
leader: publicGuild.leader._id,
});
});

View File

@@ -2,7 +2,7 @@ import {
generateUser,
translate as t,
} from '../../../../helpers/api-v3-integration.helper';
import { encrypt } from '../../../../../website/server/libs/api-v3/encryption';
import { encrypt } from '../../../../../website/server/libs/encryption';
import { v4 as generateUUID } from 'uuid';
describe('GET /email/unsubscribe', () => {

View File

@@ -4,12 +4,21 @@ import {
translate as t,
} from '../../../../helpers/api-v3-integration.helper';
import { v4 as generateUUID } from 'uuid';
import common from '../../../../../common';
describe('GET /groups/:groupId/members', () => {
let user;
beforeEach(async () => {
user = await generateUser();
user = await generateUser({
balance: 10,
contributor: {level: 1},
backer: {tier: 3},
preferences: {
costume: false,
background: 'volcano',
},
});
});
it('validates optional req.query.lastId to be an UUID', async () => {
@@ -57,6 +66,30 @@ describe('GET /groups/:groupId/members', () => {
expect(res[0].profile).to.have.all.keys(['name']);
});
it('req.query.includeAllPublicFields === true only works with parties', async () => {
let group = await generateGroup(user, {type: 'guild', name: generateUUID()});
let res = await user.get(`/groups/${group._id}/members?includeAllPublicFields=true`);
expect(res[0]).to.have.all.keys(['_id', 'id', 'profile']);
expect(res[0].profile).to.have.all.keys(['name']);
});
it('populates all public fields if req.query.includeAllPublicFields === true and it is a party', async () => {
await generateGroup(user, {type: 'party', name: generateUUID()});
let [memberRes] = await user.get('/groups/party/members?includeAllPublicFields=true');
expect(memberRes).to.have.all.keys([ // works as: object has all and only these keys
'_id', 'id', 'preferences', 'profile', 'stats', 'achievements', 'party',
'backer', 'contributor', 'auth', 'items',
]);
expect(Object.keys(memberRes.auth)).to.eql(['timestamps']);
expect(Object.keys(memberRes.preferences).sort()).to.eql(['size', 'hair', 'skin', 'shirt',
'chair', 'costume', 'sleep', 'background'].sort());
expect(memberRes.stats.maxMP).to.exists;
expect(memberRes.stats.maxHealth).to.equal(common.maxHealth);
expect(memberRes.stats.toNextLevel).to.equal(common.tnl(memberRes.stats.lvl));
});
it('returns only first 30 members', async () => {
let group = await generateGroup(user, {type: 'party', name: generateUUID()});

View File

@@ -84,7 +84,7 @@ describe('POST /groups/:groupId/removeMember/:memberId', () => {
context('Party', () => {
let party;
let partyleader;
let partyLeader;
let partyInvitedUser;
let partyMember;
@@ -100,13 +100,13 @@ describe('POST /groups/:groupId/removeMember/:memberId', () => {
});
party = group;
partyleader = groupLeader;
partyLeader = groupLeader;
partyInvitedUser = invitees[0];
partyMember = members[0];
});
it('can remove other members', async () => {
await partyleader.post(`/groups/${party._id}/removeMember/${partyMember._id}`);
await partyLeader.post(`/groups/${party._id}/removeMember/${partyMember._id}`);
let memberRemoved = await partyMember.get('/user');
@@ -115,18 +115,63 @@ describe('POST /groups/:groupId/removeMember/:memberId', () => {
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);
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 () => {
expect(partyInvitedUser.invitations.party).to.not.be.empty;
await partyleader.post(`/groups/${party._id}/removeMember/${partyInvitedUser._id}`);
await partyLeader.post(`/groups/${party._id}/removeMember/${partyInvitedUser._id}`);
let invitedUserWithoutInvite = await partyInvitedUser.get('/user');
expect(invitedUserWithoutInvite.invitations.party).to.be.empty;
});
it('removes user from quest when removing user from party after quest starts', async () => {
let petQuest = 'whale';
await partyLeader.update({
[`items.quests.${petQuest}`]: 1,
});
await partyLeader.post(`/groups/${party._id}/quests/invite/${petQuest}`);
await partyMember.post(`/groups/${party._id}/quests/accept`);
await party.sync();
expect(party.quest.members[partyLeader._id]).to.be.true;
expect(party.quest.members[partyMember._id]).to.be.true;
await partyLeader.post(`/groups/${party._id}/removeMember/${partyMember._id}`);
await party.sync();
expect(party.quest.members[partyLeader._id]).to.be.true;
expect(party.quest.members[partyMember._id]).to.not.exist;
});
it('removes user from quest when removing user from party before quest starts', async () => {
let petQuest = 'whale';
await partyLeader.update({
[`items.quests.${petQuest}`]: 1,
});
await partyInvitedUser.post(`/groups/${party._id}/join`);
await partyLeader.post(`/groups/${party._id}/quests/invite/${petQuest}`);
await partyMember.post(`/groups/${party._id}/quests/accept`);
await party.sync();
expect(party.quest.active).to.be.false;
expect(party.quest.members[partyLeader._id]).to.be.true;
expect(party.quest.members[partyMember._id]).to.be.true;
await partyLeader.post(`/groups/${party._id}/removeMember/${partyMember._id}`);
await party.sync();
expect(party.quest.members[partyLeader._id]).to.be.true;
expect(party.quest.members[partyMember._id]).to.not.exist;
});
});
});

View File

@@ -1,9 +1,12 @@
import {
generateUser,
resetHabiticaDB,
} from '../../../../helpers/api-v3-integration.helper';
describe('GET /hall/heroes', () => {
it('returns all heroes sorted by -contributor.level and with correct fields', async () => {
await resetHabiticaDB();
let nonHero = await generateUser();
let hero1 = await generateUser({
contributor: {level: 1},

View File

@@ -3,6 +3,7 @@ import {
translate as t,
} from '../../../../helpers/api-v3-integration.helper';
import { v4 as generateUUID } from 'uuid';
import common from '../../../../../common';
describe('GET /members/:memberId', () => {
let user;
@@ -36,6 +37,10 @@ describe('GET /members/:memberId', () => {
expect(Object.keys(memberRes.auth)).to.eql(['timestamps']);
expect(Object.keys(memberRes.preferences).sort()).to.eql(['size', 'hair', 'skin', 'shirt',
'chair', 'costume', 'sleep', 'background'].sort());
expect(memberRes.stats.maxMP).to.exists;
expect(memberRes.stats.maxHealth).to.equal(common.maxHealth);
expect(memberRes.stats.toNextLevel).to.equal(common.tnl(memberRes.stats.lvl));
});
it('handles non-existing members', async () => {

View File

@@ -74,6 +74,21 @@ describe('POST /groups/:groupId/quests/accept', () => {
});
});
it('clears the invalid invite from the user when the request fails', async () => {
await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`);
await partyMembers[0].post(`/groups/${questingGroup._id}/quests/accept`);
await expect(partyMembers[0].post(`/groups/${questingGroup._id}/quests/accept`))
.to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('questAlreadyAccepted'),
});
await partyMembers[0].sync();
expect(partyMembers[0].party.quest.RSVPNeeded).to.be.false;
});
it('does not accept invite for a quest already underway', async () => {
await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`);
await partyMembers[0].post(`/groups/${questingGroup._id}/quests/accept`);

View File

@@ -83,6 +83,21 @@ describe('POST /groups/:groupId/quests/reject', () => {
});
});
it('clears the user rsvp needed if the request fails because the request is invalid', async () => {
await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`);
await partyMembers[0].post(`/groups/${questingGroup._id}/quests/reject`);
await expect(partyMembers[0].post(`/groups/${questingGroup._id}/quests/reject`))
.to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('questAlreadyRejected'),
});
await partyMembers[0].sync();
expect(partyMembers[0].party.quest.RSVPNeeded).to.be.false;
});
it('return an error when a user rejects an invite already accepted', async () => {
await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`);
await partyMembers[0].post(`/groups/${questingGroup._id}/quests/accept`);

View File

@@ -0,0 +1,28 @@
import {
generateUser,
translate as t,
} from '../../../../helpers/api-integration/v3';
describe('GET /shops/market', () => {
let user;
beforeEach(async () => {
user = await generateUser();
});
it('returns a valid shop object', async () => {
let shop = await user.get('/shops/market');
expect(shop.identifier).to.equal('market');
expect(shop.text).to.eql(t('market'));
expect(shop.notes).to.eql(t('welcomeMarketMobile'));
expect(shop.imageName).to.be.a('string');
expect(shop.categories).to.be.an('array');
let categories = shop.categories.map(cat => cat.identifier);
expect(categories).to.include('eggs');
expect(categories).to.include('hatchingPotions');
expect(categories).to.include('food');
});
});

View File

@@ -0,0 +1,28 @@
import {
generateUser,
translate as t,
} from '../../../../helpers/api-integration/v3';
describe('GET /shops/quests', () => {
let user;
beforeEach(async () => {
user = await generateUser();
});
it('returns a valid shop object', async () => {
let shop = await user.get('/shops/quests');
expect(shop.identifier).to.equal('questShop');
expect(shop.text).to.eql(t('quests'));
expect(shop.notes).to.eql(t('ianTextMobile'));
expect(shop.imageName).to.be.a('string');
expect(shop.categories).to.be.an('array');
let categories = shop.categories.map(cat => cat.identifier);
expect(categories).to.include('unlockable');
expect(categories).to.include('gold');
expect(categories).to.include('pet');
});
});

View File

@@ -0,0 +1,22 @@
import {
generateUser,
translate as t,
} from '../../../../helpers/api-integration/v3';
describe('GET /shops/seasonal', () => {
let user;
beforeEach(async () => {
user = await generateUser();
});
it('returns a valid shop object', async () => {
let shop = await user.get('/shops/seasonal');
expect(shop.identifier).to.equal('seasonalShop');
expect(shop.text).to.eql(t('seasonalShop'));
expect(shop.notes).to.eql(t('seasonalShopClosedText'));
expect(shop.imageName).to.be.a('string');
expect(shop.categories).to.be.an('array');
});
});

View File

@@ -0,0 +1,98 @@
import {
generateUser,
translate as t,
} from '../../../../helpers/api-integration/v3';
describe('GET /shops/time-travelers', () => {
let user;
beforeEach(async () => {
user = await generateUser();
});
it('returns a valid shop object', async () => {
let shop = await user.get('/shops/time-travelers');
expect(shop.identifier).to.equal('timeTravelersShop');
expect(shop.text).to.eql(t('timeTravelers'));
expect(shop.notes).to.be.a('string');
expect(shop.imageName).to.be.a('string');
expect(shop.categories).to.be.an('array');
let categories = shop.categories.map(cat => cat.identifier);
expect(categories).to.include('pets');
expect(categories).to.include('mounts');
expect(categories).to.include('201606');
let mammothPet = shop.categories
.find(cat => cat.identifier === 'pets')
.items
.find(pet => pet.key === 'Mammoth-Base');
let mantisShrimp = shop.categories
.find(cat => cat.identifier === 'mounts')
.items
.find(pet => pet.key === 'MantisShrimp-Base');
expect(mammothPet).to.exist;
expect(mantisShrimp).to.exist;
});
it('returns active shop notes and imageName if user has trinkets', async () => {
await user.update({
'purchased.plan.consecutive.trinkets': 1,
});
let shop = await user.get('/shops/time-travelers');
expect(shop.notes).to.eql(t('timeTravelersPopover'));
expect(shop.imageName).to.eql('npc_timetravelers_active');
});
it('returns inactive shop notes and imageName if user has trinkets', async () => {
let shop = await user.get('/shops/time-travelers');
expect(shop.notes).to.eql(t('timeTravelersPopoverNoSubMobile'));
expect(shop.imageName).to.eql('npc_timetravelers');
});
it('does not return mystery sets that are already owned', async () => {
await user.update({
'items.gear.owned': {
head_mystery_201606: true, // eslint-disable-line camelcase
armor_mystery_201606: true, // eslint-disable-line camelcase
},
});
let shop = await user.get('/shops/time-travelers');
let categories = shop.categories.map(cat => cat.identifier);
expect(categories).to.not.include('201606');
});
it('does not return pets and mounts that user already owns', async () => {
await user.update({
'items.mounts': {
'MantisShrimp-Base': true,
},
'items.pets': {
'Mammoth-Base': 5,
},
});
let shop = await user.get('/shops/time-travelers');
let mammothPet = shop.categories
.find(cat => cat.identifier === 'pets')
.items
.find(pet => pet.key === 'Mammoth-Base');
let mantisShrimp = shop.categories
.find(cat => cat.identifier === 'mounts')
.items
.find(pet => pet.key === 'MantisShrimp-Base');
expect(mammothPet).to.not.exist;
expect(mantisShrimp).to.not.exist;
});
});

View File

@@ -1,6 +1,7 @@
import {
generateUser,
} from '../../../../helpers/api-integration/v3';
import common from '../../../../../common';
describe('GET /user', () => {
let user;
@@ -9,9 +10,13 @@ describe('GET /user', () => {
user = await generateUser();
});
it('returns the authenticated user', async () => {
it('returns the authenticated user with computed stats', async () => {
let returnedUser = await user.get('/user');
expect(returnedUser._id).to.equal(user._id);
expect(returnedUser.stats.maxMP).to.exists;
expect(returnedUser.stats.maxHealth).to.equal(common.maxHealth);
expect(returnedUser.stats.toNextLevel).to.equal(common.tnl(returnedUser.stats.lvl));
});
it('does not return private paths (and apiToken)', async () => {

View File

@@ -13,6 +13,7 @@ describe('POST /user/auth/local/login', () => {
api = requester();
user = await generateUser();
});
it('success with username', async () => {
let response = await api.post(endpoint, {
username: user.auth.local.username,
@@ -20,6 +21,7 @@ describe('POST /user/auth/local/login', () => {
});
expect(response.apiToken).to.eql(user.apiToken);
});
it('success with email', async () => {
let response = await api.post(endpoint, {
username: user.auth.local.email,
@@ -27,6 +29,7 @@ describe('POST /user/auth/local/login', () => {
});
expect(response.apiToken).to.eql(user.apiToken);
});
it('user is blocked', async () => {
await user.update({ 'auth.blocked': 1 });
await expect(api.post(endpoint, {
@@ -38,6 +41,7 @@ describe('POST /user/auth/local/login', () => {
message: t('accountSuspended', { userId: user._id }),
});
});
it('wrong password', async () => {
await expect(api.post(endpoint, {
username: user.auth.local.username,
@@ -48,6 +52,7 @@ describe('POST /user/auth/local/login', () => {
message: t('invalidLoginCredentialsLong'),
});
});
it('missing username', async () => {
await expect(api.post(endpoint, {
password: 'wrong-password',
@@ -57,6 +62,7 @@ describe('POST /user/auth/local/login', () => {
message: t('invalidReqParams'),
});
});
it('missing password', async () => {
await expect(api.post(endpoint, {
username: user.auth.local.username,

View File

@@ -6,7 +6,7 @@ import {
} from '../../../../../helpers/api-integration/v3';
import { v4 as generateRandomUserName } from 'uuid';
import { each } from 'lodash';
import { encrypt } from '../../../../../../website/server/libs/api-v3/encryption';
import { encrypt } from '../../../../../../website/server/libs/encryption';
describe('POST /user/auth/local/register', () => {
context('username and email are free', () => {

View File

@@ -0,0 +1,59 @@
import {
generateUser,
requester,
translate as t,
} from '../../../../../helpers/api-integration/v3';
import passport from 'passport';
describe('POST /user/auth/social', () => {
let api;
let user;
let endpoint = '/user/auth/social';
let randomAccessToken = '123456';
let facebookId = 'facebookId';
let network = 'facebook';
before(async () => {
api = requester();
user = await generateUser();
let expectedResult = {id: facebookId};
let passportFacebookProfile = sinon.stub(passport._strategies.facebook, 'userProfile');
passportFacebookProfile.yields(null, expectedResult);
});
it('fails if network is not facebook', async () => {
await expect(api.post(endpoint, {
authResponse: {access_token: randomAccessToken}, // eslint-disable-line camelcase
network: 'NotFacebook',
})).to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('onlyFbSupported'),
});
});
it('registers a new user', async () => {
let response = await api.post(endpoint, {
authResponse: {access_token: randomAccessToken}, // eslint-disable-line camelcase
network,
});
expect(response.apiToken).to.exist;
expect(response.id).to.exist;
expect(response.newUser).to.be.true;
});
it('logs an existing user in', async () => {
await user.update({ 'auth.facebook.id': facebookId });
let response = await api.post(endpoint, {
authResponse: {access_token: randomAccessToken}, // eslint-disable-line camelcase
network,
});
expect(response.apiToken).to.eql(user.apiToken);
expect(response.id).to.eql(user._id);
expect(response.newUser).to.be.false;
});
});