Skip to content

Commit

Permalink
back-end:
Browse files Browse the repository at this point in the history
- added group tests that checks group status invariants.
- added patch route tests.
- added updateMemberStatus method to apartment model.
- added PATCH /apartments/:id/groups route.
- added new errors: groupNotFound, groupMemberNotFound.
issues: #526 #529 #537 #151
  • Loading branch information
alonttal committed Jun 6, 2018
1 parent 767f506 commit 3db5773
Show file tree
Hide file tree
Showing 9 changed files with 200 additions and 32 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
"supertest": "^3.1.0"
},
"scripts": {
"test": "export NODE_ENV=test || SET \"NODE_ENV=test\" && nyc --reporter=html --reporter=text _mocha --recursive tests/server/server.test.js --exit",
"test": "export NODE_ENV=test || SET \"NODE_ENV=test\" && nyc --reporter=html --reporter=text _mocha --recursive tests/server/models/apartment.test.js --exit",
"start": "node server/server.js",
"coverage": "nyc report --reporter=lcov > coverage.lcov",
"view": "node views/build/build.js && node server/server.js"
Expand Down
6 changes: 5 additions & 1 deletion server/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ const MULTI_RATING = 600000;
const IMAGE_UPLOAD_FAILURE = 666000;
const APARTMENT_NOT_FOUND = 160000;
const GROUP_CREATION_FAILED = 160001;
const GROUP_NOT_FOUND = 160002;
const GROUP_MEMBER_NOT_FOUND = 160003;
const USER_NOT_FOUND = 170000;

const Error = function (code, message) {
Expand All @@ -29,5 +31,7 @@ module.exports = {
imageUploadFailure: Error(IMAGE_UPLOAD_FAILURE, 'Error occurred while trying to upload images.'),
apartmentNotFound: Error(APARTMENT_NOT_FOUND, 'The apartment was not found.'),
groupCreationFailed: Error(GROUP_CREATION_FAILED, 'Couldn\'t create new group for the apartment.'),
userNotFound: Error(USER_NOT_FOUND, 'Couldn\'t find user.')
groupNotFound: Error(GROUP_NOT_FOUND, 'The group was not found.'),
groupMemberNotFound: Error(GROUP_MEMBER_NOT_FOUND, 'The group member was not found.'),
userNotFound: Error(USER_NOT_FOUND, 'The user was not found.')
};
24 changes: 24 additions & 0 deletions server/models/apartment.js
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,30 @@ ApartmentSchema.methods.createGroup = function (id) {
return apartment.save();
};

/**
* @author: Alon Talmor
* @date: 6/5/18
*
* This methods finds the appropriate group and updates the status of the specified member.
* Properties available:
* @param groupId - the id of the group to update.
* @param memberId - the id of the member to update.
* @param status - the new status of the member.
* @returns Promise object which includes the updated apartment
* @throws groupNotFound exception if the group does not exist.
* @throws userNotFound exception if the user in not a member of the group.
*/
ApartmentSchema.methods.updateMemberStatus = function (groupId, memberId, status) {
const apartment = this;

const group = apartment.groups.id(groupId);
if (!group) {
return Promise.reject(errors.groupNotFound);
}
group.updateStatus(memberId, status);
return apartment.save();
};

/**
* remove the interested user from the apartment's interested list.
*
Expand Down
46 changes: 42 additions & 4 deletions server/models/group.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
const _ = require('lodash');
const mongoose = require('mongoose');

const errors = require('../errors');

const groupStatus = {
PENDING: 1,
DECLINED: 2,
ACCEPTED: 3,
COMPLETED: 4
};

const userStatus = {
const memberStatus = {
PENDING: 1,
ACCEPTED: 2,
DECLINED: 3,
Expand All @@ -25,11 +27,11 @@ const GroupSchema = new mongoose.Schema({
status: {
type: Number,
validate: {
validator: (value) => (_.includes(userStatus, value)),
validator: (value) => (_.includes(memberStatus, value)),
message: '{VALUE} is not a supported User Status'
},
required: true,
default: userStatus.PENDING
default: memberStatus.PENDING
}
}],
createdAt: {
Expand Down Expand Up @@ -57,10 +59,46 @@ const GroupSchema = new mongoose.Schema({
}
});

/**
* @author: Alon Talmor
* @date: 7/5/18
* Updated the status of the group according to the other group members.
* This methods puts some invariant on the schema.
*/
GroupSchema.pre('save', function (next) {
const group = this;
// check whether anyone declined - then set the group status to declined
if (group.members.some($ => $.status === memberStatus.DECLINED)) {
group.status = groupStatus.DECLINED;
} // eslint-disable-line
// check whether all members accepted - then set the froup status to accepted
else if (group.members.every($ => $.status === memberStatus.ACCEPTED)) {
group.status = groupStatus.ACCEPTED;
}
next();
});

/**
* @author: Alon Talmor
* @date: 6/5/18
* updated the status of the specified member id.
*
*/
GroupSchema.methods.updateStatus = function (memberId, status) {
const group = this;

for (let i = 0; i < group.members.length; i++) {
if (group.members[i].id.equals(memberId)) {
group.members[i].status = status;
return;
}
}
throw errors.groupMemberNotFound;
};
// const Group = mongoose.model('Group', GroupSchema);

module.exports = {
Group: GroupSchema,
groupStatus,
userStatus
memberStatus
};
24 changes: 24 additions & 0 deletions server/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -1421,6 +1421,30 @@ app.post('/apartments/:id/groups', authenticate, async (req, res) => {
return res.status(BAD_REQUEST).send(error);
}
});

/**
* @author: Alon Talmor
* @date: 6/5/18
*
* Update a group.
* The initial version of this route will only support updating of the member status.
* The body should include the id of the group and the new status in the
* following way: {id, status}. A user is allowed to update ONLY his own status.
* Returns the updated apartment.
*/
app.patch('/apartments/:id/groups', authenticate, async (req, res) => {
const body = _.pick(req.body, ['id', 'status']);
try {
let apartment = await Apartment.findById(req.params.id);
if (!apartment) {
return res.status(BAD_REQUEST).send(errors.apartmentNotFound);
}
apartment = await apartment.updateMemberStatus(body.id, req.user._id, body.status);
res.send({ apartment });
} catch (error) {
res.status(BAD_REQUEST).send(error);
}
});
/**
* @author: Alon Talmor
* @date: 28/3/18
Expand Down
3 changes: 2 additions & 1 deletion tests/seed/seed.js
Original file line number Diff line number Diff line change
Expand Up @@ -161,8 +161,9 @@ const apartment3 = new Apartment({
_apartmentId: apartment3Id,
},
{
members: [{ id: user2Id }, { id: user3Id }],
members: [{ id: user3Id }, { id: user2Id }],
_apartmentId: apartment3Id,
_id: new ObjectID()
}]
});

Expand Down
22 changes: 21 additions & 1 deletion tests/server/models/apartment.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ const expect = require('expect');
const geolib = require('geolib');

const { Apartment } = require('../../../server/models/apartment');
const { memberStatus, groupStatus } = require('../../../server/models/group');
const {
coords,
populateApartments,
populateUsers
populateUsers,
apartments
} = require('../../seed/seed');

describe('Apartment Tests', () => {
Expand Down Expand Up @@ -49,5 +51,23 @@ describe('Apartment Tests', () => {
}).catch(done);
});
});

describe('#updateMemberStatus Tests', () => {
it('should keep group declined invariant', async () => {
let apartment = await Apartment.findById(apartments[2]._id);
apartment = await apartment.updateMemberStatus(apartments[2].groups[1]._id, apartments[2].groups[1].members[0].id, memberStatus.DECLINED);
expect(apartment.groups[1].status).toBe(groupStatus.DECLINED);
});
it('should keep group accepted invariant', async () => {
let apartment = await Apartment.findById(apartments[2]._id);
apartment = await apartment.updateMemberStatus(apartments[2].groups[0]._id, apartments[2].groups[0].members[0].id, memberStatus.ACCEPTED);
expect(apartment.groups[0].status).toBe(groupStatus.ACCEPTED);
});
it('should not change group status to accepted when some members have not accepted', async () => {
let apartment = await Apartment.findById(apartments[2]._id);
apartment = await apartment.updateMemberStatus(apartments[2].groups[1]._id, apartments[2].groups[1].members[0].id, memberStatus.ACCEPTED);
expect(apartment.groups[1].status).toBe(groupStatus.PENDING);
});
});
});

39 changes: 16 additions & 23 deletions tests/server/models/review.test.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
const expect = require('expect');
const geolib = require('geolib');
const { ObjectID } = require('mongodb');

const { Review } = require('../../../server/models/review');
const {
reviews,
coords,
populateApartments,
populateUsers,
populateReviews,
apartments,
users
Review
} = require('../../../server/models/review');
const {
coords,
populateApartments,
populateUsers,
populateReviews,
} = require('../../seed/seed');

describe('Review Tests', () => {
Expand All @@ -28,20 +26,17 @@ describe('Review Tests', () => {
});

it('should return apartments near technion', (done) => {
Review.findInRange(coords.technionIsrael[0], coords.technionIsrael[1], 1)
Review.findInRange(coords.technionIsrael[0], coords.technionIsrael[1], 1)
.then((result) => {
expect(result.length).toBe(2);
result.forEach((review) => {
expect(geolib.getDistance(
{
longitude: review.geolocation[0],
latitude: review.geolocation[1]
},
{
longitude: coords.technionIsrael[0],
latitude: coords.technionIsrael[1]
}
)).toBeLessThanOrEqual(1000);
expect(geolib.getDistance({
longitude: review.geolocation[0],
latitude: review.geolocation[1]
}, {
longitude: coords.technionIsrael[0],
latitude: coords.technionIsrael[1]
})).toBeLessThanOrEqual(1000);
});
done();
}).catch(done);
Expand All @@ -54,7 +49,5 @@ describe('Review Tests', () => {
done();
}).catch(done);
});

});
});

});
66 changes: 65 additions & 1 deletion tests/server/server.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ const {
const {
buildPrivateMessageJSON
} = require('../../server/models/privateMessage');

const { memberStatus } = require('../../server/models/group');

const {
getVisitStatusCodes,
Expand Down Expand Up @@ -2675,6 +2675,70 @@ describe('#Server Tests', () => {
});
});

describe('#PATCH /apartments/:id/groups', () => {
it('should updated the member\'s status', (done) => {
const apartmentId = apartments[2]._id.toHexString();
const groupId = apartments[2].groups[1]._id.toHexString();
request(app)
.patch(`/apartments/${apartmentId}/groups`)
.set(XAUTH, users[1].tokens[0].token) // need to be authorized
.send({ id: groupId, status: memberStatus.ACCEPTED })
.expect(OK)
.expect((res) => {
expect((res.body.apartment.groups[1].members.find(m => m.id === (users[1]._id.toHexString()))).status).toBe(memberStatus.ACCEPTED);
})
.end((error) => {
if (error) {
return done(error);
}
return Apartment.findById(apartmentId)
.then((apartment) => {
expect((apartment.groups[1].members.find(m => m.id.equals(users[1]._id))).status).toBe(memberStatus.ACCEPTED);
done();
});
})
});
it('should fail when member does not exist in group', (done) => {
const apartmentId = apartments[2]._id.toHexString();
const groupId = apartments[2].groups[0]._id.toHexString();
request(app)
.patch(`/apartments/${apartmentId}/groups`)
.set(XAUTH, users[1].tokens[0].token) // need to be authorized
.send({ id: groupId, status: memberStatus.ACCEPTED })
.expect(BAD_REQUEST)
.end(done);
});
it('should fail when group does not exist in apartment', (done) => {
const apartmentId = apartments[2]._id.toHexString();
const groupId = new ObjectID().toHexString();
request(app)
.patch(`/apartments/${apartmentId}/groups`)
.set(XAUTH, users[1].tokens[0].token) // need to be authorized
.send({ id: groupId, status: memberStatus.ACCEPTED })
.expect(BAD_REQUEST)
.end(done);
});
it('should fail when apartment does not exist', (done) => {
const apartmentId = new ObjectID().toHexString();
const groupId = apartments[2].groups[0]._id.toHexString();
request(app)
.patch(`/apartments/${apartmentId}/groups`)
.set(XAUTH, users[1].tokens[0].token) // need to be authorized
.send({ id: groupId, status: memberStatus.ACCEPTED })
.expect(BAD_REQUEST)
.end(done);
});
it('should fail when not authorized', (done) => {
const apartmentId = apartments[2]._id.toHexString();
const groupId = apartments[2].groups[1]._id.toHexString();
request(app)
.patch(`/apartments/${apartmentId}/groups`)
.send({ id: groupId, status: memberStatus.ACCEPTED })
.expect(UNAUTHORIZED)
.end(done);
});
});

describe('#GET *', () => {
it('should return 404 on invalid route requests', (done) => {
request(app)
Expand Down

0 comments on commit 3db5773

Please sign in to comment.