3 var proxyquireNoCallThru = require('proxyquire').noCallThru();
4 var assert = require('assert');
5 const mongoUtils = require('gitter-web-persistence-utils/lib/mongo-utils');
6 var fixtureLoader = require('gitter-web-test-utils/lib/test-fixtures');
7 var Promise = require('bluebird');
8 var ObjectID = require('mongodb').ObjectID;
9 var StatusError = require('statuserror');
11 var mockito = require('jsmockito').JsMockito;
12 var times = mockito.Verifiers.times;
15 var persistence = require('gitter-web-persistence');
17 require('../lib/room-service');
19 // to work around proxyquire caching bugs...
20 require('../lib/room-service');
22 describe('room-service', function() {
23 describe('addUserToRoom', function() {
24 function createRoomServiceWithStubs(stubs) {
25 return proxyquireNoCallThru('../lib/room-service', {
26 'gitter-web-permissions/lib/add-invite-policy-factory': {
27 createPolicyForRoomAdd: function() {
28 return Promise.resolve({
30 return Promise.resolve(stubs.canBeInvited);
35 'gitter-web-email-notifications': {
36 sendInvitation: stubs.onInviteEmail,
37 addedToRoomNotification: function() {
38 return Promise.resolve();
41 'gitter-web-email-addresses': function() {
42 return Promise.resolve('a@b.com');
47 it('adds a user to the troupe', function() {
48 var service = createRoomServiceWithStubs({
50 onInviteEmail: function() {
51 return Promise.resolve();
55 var _troupId = new ObjectID();
56 var _userId = new ObjectID();
57 var _userToAddId = new ObjectID();
61 id: _troupId.toString(),
70 id: _userId.toString()
75 id: _userToAddId.toString(),
79 return service.addUserToRoom(troupe, user, userToAdd);
82 it('saves troupe changes', function() {
83 var service = createRoomServiceWithStubs({
85 onInviteEmail: function() {
86 return Promise.resolve();
90 var _troupId = new ObjectID();
91 var _userId = new ObjectID();
92 var _userToAddId = new ObjectID();
96 id: _troupId.toString(),
105 id: _userId.toString()
110 id: _userToAddId.toString(),
111 username: 'test-user'
114 return service.addUserToRoom(troupe, user, userToAdd);
117 it('returns the added user and sets the date the user was added', function() {
118 var service = createRoomServiceWithStubs({
120 onInviteEmail: function() {
121 return Promise.resolve();
125 var _troupId = new ObjectID();
126 var _userId = new ObjectID();
127 var _userToAddId = new ObjectID();
139 id: _userId.toString()
144 id: _userToAddId.toString(),
145 username: 'test-user'
149 .addUserToRoom(troupe, user, userToAdd)
150 .then(function(user) {
151 assert.equal(user.id, _userToAddId);
152 assert.equal(user.username, 'test-user');
154 return persistence.UserTroupeLastAccess.findOne({ userId: user.id }).exec();
156 .then(function(lastAccess) {
158 assert(lastAccess.added);
159 assert(lastAccess.added[troupe.id]);
160 assert(Date.now() - lastAccess.added[troupe.id] <= 30000);
164 it('attempts an email invite for new users', function() {
165 var service = createRoomServiceWithStubs({
167 onInviteEmail: function() {
168 return Promise.resolve();
172 var _troupId = new ObjectID();
173 var _userId = new ObjectID();
174 var _userToAddId = new ObjectID();
186 id: _userId.toString()
191 id: _userToAddId.toString(),
192 username: 'test-user'
195 return service.addUserToRoom(troupe, user, userToAdd);
198 it('fails with 403 when adding someone to who cant be invited', function() {
199 var service = createRoomServiceWithStubs({
200 findByUsernameResult: null,
201 createInvitedUserResult: { username: 'test-user', id: 'test-user-id', state: 'INVITED' },
203 onInviteEmail: function() {
204 return Promise.resolve();
208 var _userToAddId = new ObjectID();
216 id: _userToAddId.toString(),
217 username: 'test-user'
220 return service.addUserToRoom(troupe, {}, userToAdd).then(
222 assert.ok(false, 'Expected exception');
225 assert.equal(err.status, 403);
230 it('should not fail when adding someone who is already in the room', function() {
231 var service = createRoomServiceWithStubs({
233 onInviteEmail: function() {
234 return Promise.resolve();
238 var _troupId = new ObjectID();
239 var _userId = new ObjectID();
240 var _userToAddId = new ObjectID();
252 id: _userId.toString()
257 id: _userToAddId.toString(),
258 username: 'test-user'
261 return service.addUserToRoom(troupe, user, userToAdd);
265 describe('removals', function() {
266 var fixture = fixtureLoader.setup({
270 users: ['userToRemove', 'userRemoveNonAdmin', 'userRemoveAdmin']
272 troupeCannotRemove: {
274 users: ['userToRemove', 'userRemoveAdmin']
280 var roomService = proxyquireNoCallThru('../lib/room-service', {
281 'gitter-web-permissions/lib/policy-factory': {
282 createPolicyForRoom: function(user /*, room*/) {
283 return Promise.resolve({
284 canAdmin: function() {
285 if (user.id === fixture.userRemoveNonAdmin.id) {
286 return Promise.resolve(false);
287 } else if (user.id === fixture.userRemoveAdmin.id) {
288 return Promise.resolve(true);
290 assert(false, 'Unknown user');
292 return Promise.resolve(true);
299 var roomMembershipService = require('../lib/room-membership-service');
301 it('should prevent from removing users from one-to-one rooms', function() {
302 return roomMembershipService
303 .checkRoomMembership(fixture.troupeCannotRemove._id, fixture.userToRemove._id)
304 .then(function(here) {
306 return roomService.removeUserFromRoom(
307 fixture.troupeCannotRemove,
308 fixture.userToRemove,
309 fixture.userRemoveAdmin
312 .catch(function(err) {
313 assert.equal(err.status, 400);
314 assert.equal(err.message, 'This room does not support removing.');
317 return roomMembershipService.checkRoomMembership(
318 fixture.troupeCannotRemove._id,
319 fixture.userToRemove._id
322 .then(function(here) {
327 it('should remove users from rooms', function() {
328 return roomMembershipService
329 .checkRoomMembership(fixture.troupeCanRemove._id, fixture.userToRemove._id)
330 .then(function(here) {
332 return roomService.removeUserFromRoom(
333 fixture.troupeCanRemove,
334 fixture.userToRemove,
335 fixture.userRemoveAdmin
339 return roomMembershipService.checkRoomMembership(
340 fixture.troupeCanRemove._id,
341 fixture.userToRemove._id
344 .then(function(here) {
350 describe('remove and hide #slow', function() {
351 var troupeService = require('../lib/troupe-service');
352 var recentRoomService = require('../lib/recent-room-service');
353 var roomFavouritesCore = require('../lib/room-favourites-core');
354 var roomMembershipService = require('../lib/room-membership-service');
355 var appEvents = require('gitter-web-appevents');
357 describe('room-service #slow', function() {
358 var fixture = fixtureLoader.setup({
366 'userRemoveNonAdmin',
370 troupeCannotRemove: {
372 githubType: 'ONETOONE',
373 users: ['userToRemove', 'userRemoveAdmin']
383 userRemoveNonAdmin: {},
387 describe('#removeFavourite', function() {
388 var roomService = require('../lib/room-service');
390 var getFavs = function() {
391 return roomFavouritesCore.findFavouriteTroupesForUser(fixture.userFavourite.id);
394 var createFav = function() {
395 return recentRoomService
396 .updateFavourite(fixture.userFavourite.id, fixture.troupeCanRemove.id, true)
398 .then(function(favs) {
399 assert(favs[fixture.troupeCanRemove.id]); // Favourite is created
403 var checkHere = function() {
404 return roomMembershipService.checkRoomMembership(
405 fixture.troupeCanRemove._id,
406 fixture.userFavourite._id
410 beforeEach(function() {
411 if (this._skipFixtureSetup) return;
416 it('should remove favourite', function() {
417 var checkEvent = appEvents.addListener('dataChange2', {
418 url: '/user/' + fixture.userFavourite.id + '/rooms',
421 id: fixture.troupeCanRemove.id,
423 lastAccessTime: null,
431 .hideRoomFromUser(fixture.troupeCanRemove, fixture.userFavourite.id)
432 .then(checkEvent) // Ensure event was emitted
434 .then(function(favs) {
435 assert(!favs[fixture.troupeCanRemove.id]); // Favourite is removed
438 .then(function(here) {
439 assert(here); // User is still in room
443 it('should remove user from the room if mode=mute', function() {
444 // Set user as lurking
445 return roomMembershipService
446 .setMembershipMode(fixture.userFavourite.id, fixture.troupeCanRemove.id, 'mute', false)
448 // Get updated troupe
449 return troupeService.findById(fixture.troupeCanRemove.id);
451 .then(function(troupe) {
452 return roomService.hideRoomFromUser(troupe, fixture.userFavourite.id);
455 .then(function(favs) {
456 assert(!favs[fixture.troupeCanRemove.id]); // Favourite is removed
459 .then(function(here) {
460 assert(!here); // User has been removed
464 it('should remove user from the room if mode=mute', function() {
465 // Set user as lurking
466 return roomMembershipService
467 .setMembershipMode(fixture.userFavourite.id, fixture.troupeCanRemove.id, 'mute', false)
469 // Get updated troupe
470 return troupeService.findById(fixture.troupeCanRemove.id);
472 .then(function(troupe) {
473 return roomService.hideRoomFromUser(troupe, fixture.userFavourite.id);
476 .then(function(favs) {
477 assert(!favs[fixture.troupeCanRemove.id]); // Favourite is removed
480 .then(function(here) {
481 assert(!here); // User has been removed
485 it('should check if the proper event is emitted when the favourite is removed', function() {
486 var checkEvent = appEvents.addListener('dataChange2', {
487 url: '/user/' + fixture.userFavourite.id + '/rooms',
489 model: { id: fixture.troupeEmpty.id }
492 return roomMembershipService
493 .checkRoomMembership(fixture.troupeEmpty._id, fixture.userFavourite._id)
494 .then(function(here) {
495 assert(!here); // Check that user is not in the room
498 return roomService.hideRoomFromUser(fixture.troupeEmpty, fixture.userFavourite.id);
500 .then(checkEvent) // Ensure event was emitted
502 .then(function(favs) {
503 assert(!favs[fixture.troupeEmpty.id]); // Favourite is removed
508 describe('#removeUserFromRoom', function() {
509 var roomService = require('../lib/room-service');
511 it('should remove user from room', function() {
512 return roomMembershipService
513 .checkRoomMembership(fixture.troupeCanRemove._id, fixture.userLeave._id)
514 .then(function(here) {
516 return roomService.removeUserFromRoom(
517 fixture.troupeCanRemove,
523 return roomMembershipService.checkRoomMembership(
524 fixture.troupeCanRemove._id,
525 fixture.userLeave._id
528 .then(function(here) {
534 describe('#removeUserFromRoom', function() {
535 var roomService = require('../lib/room-service');
537 it('should remove users from rooms', function() {
538 return roomMembershipService
539 .checkRoomMembership(fixture.troupeCanRemove._id, fixture.userToRemove._id)
540 .then(function(here) {
542 return roomService.removeUserFromRoom(
543 fixture.troupeCanRemove,
544 fixture.userToRemove,
545 fixture.userRemoveAdmin
549 return roomMembershipService.checkRoomMembership(
550 fixture.troupeCanRemove._id,
551 fixture.userToRemove._id
554 .then(function(here) {
562 describe('findBanByUsername', () => {
563 const roomService = require('../lib/room-service');
565 const banFixtures = fixtureLoader.setup({
568 troupeWithBannedUsers1: {
572 dateBanned: Date.now(),
573 bannedBy: 'userAdmin1'
577 troupeWithBannedVirtualUsers1: {
582 externalId: 'banned-user:matrix.org'
584 dateBanned: Date.now(),
585 bannedBy: 'userAdmin1'
591 it('finds ban for normal user', async () => {
592 const ban = await roomService.findBanByUsername(
593 banFixtures.troupeWithBannedUsers1._id,
594 banFixtures.userBanned1.username
597 assert.strictEqual(mongoUtils.objectIDsEqual(ban.userId, banFixtures.userBanned1._id), true);
600 it('finds ban for virtual user', async () => {
601 const ban = await roomService.findBanByUsername(
602 banFixtures.troupeWithBannedVirtualUsers1._id,
603 'banned-user:matrix.org'
606 assert.strictEqual(ban.virtualUser.type, 'matrix', true);
607 assert.strictEqual(ban.virtualUser.externalId, 'banned-user:matrix.org', true);
610 it('does not find ban when no matching user', async () => {
611 const ban = await roomService.findBanByUsername(
612 banFixtures.troupeWithBannedUsers1._id,
616 assert.strictEqual(ban, undefined);
619 it('does not find ban when virtualUser.externalId does not match', async () => {
620 const ban = await roomService.findBanByUsername(
621 banFixtures.troupeWithBannedVirtualUsers1._id,
622 'some-other-user:matrix.org'
625 assert.strictEqual(ban, undefined);
629 describe('findAllRoomsIdsForUserIncludingMentions', function() {
630 var getRoomIdsMentioningUserMock, findRoomIdsForUserMock, roomService;
632 beforeEach(function() {
633 getRoomIdsMentioningUserMock = mockito.mockFunction();
634 findRoomIdsForUserMock = mockito.mockFunction();
635 roomService = proxyquireNoCallThru('../lib/room-service', {
636 'gitter-web-unread-items': {
637 getRoomIdsMentioningUser: getRoomIdsMentioningUserMock
639 './room-membership-service': {
640 findRoomIdsForUser: findRoomIdsForUserMock
645 function runWithValues(roomIdsForUser, roomIdsMentioningUser, expected, expectedNonMembers) {
646 var userId = 'user1';
649 .when(getRoomIdsMentioningUserMock)()
650 .then(function(pUserId) {
651 assert.strictEqual(pUserId, userId);
652 return Promise.resolve(roomIdsMentioningUser);
656 .when(findRoomIdsForUserMock)()
657 .then(function(pUserId) {
658 assert.strictEqual(pUserId, userId);
659 return Promise.resolve(roomIdsForUser);
663 .findAllRoomsIdsForUserIncludingMentions(userId)
664 .spread(function(allTroupeIds, nonMemberTroupeIds) {
666 nonMemberTroupeIds.sort();
668 expectedNonMembers.sort();
669 assert.deepEqual(allTroupeIds, expected);
670 assert.deepEqual(nonMemberTroupeIds, expectedNonMembers);
674 it('should handle the trivial case of no rooms', function() {
675 return runWithValues([], [], [], []);
678 it('should handle the non member rooms only case', function() {
679 return runWithValues([], ['1'], ['1'], ['1']);
682 it('should handle the member rooms only case', function() {
683 return runWithValues(['1'], [], ['1'], []);
686 it('should handle the member rooms only case with mentions', function() {
687 return runWithValues(['1'], ['1'], ['1'], []);
690 it('should handle the mixed cases', function() {
691 return runWithValues(['1', '2', '3'], ['2', '3', '4'], ['1', '2', '3', '4'], ['4']);
695 describe('joinRoom', function() {
696 describe('unit tests', function() {
698 var assertJoinRoomChecks;
699 var recentRoomServiceSaveLastVisitedTroupeforUserId;
700 var roomMembershipServiceAddRoomMember;
702 var joinRoomCheckFailed;
707 beforeEach(function() {
709 troupeId = 'troupeId1';
719 assertJoinRoomChecks = mockito.mockFunction();
720 recentRoomServiceSaveLastVisitedTroupeforUserId = mockito.mockFunction();
721 roomMembershipServiceAddRoomMember = mockito.mockFunction();
724 .when(assertJoinRoomChecks)()
725 .then(function(pRoom, pUser) {
726 assert.strictEqual(pUser, user);
727 assert.strictEqual(pRoom, troupe);
728 if (joinRoomCheckFailed) return Promise.reject(new StatusError());
729 return Promise.resolve();
733 .when(recentRoomServiceSaveLastVisitedTroupeforUserId)()
734 .then(function(pUserId, pRoomId, pOptions) {
735 assert.strictEqual(pUserId, userId);
736 assert.strictEqual(pRoomId, troupeId);
737 assert.deepEqual(pOptions, { skipFayeUpdate: true });
738 return Promise.resolve();
742 .when(roomMembershipServiceAddRoomMember)()
743 .then(function(pRoomId, pUserId) {
744 assert.strictEqual(pUserId, userId);
745 assert.strictEqual(pRoomId, troupeId);
746 return Promise.resolve();
749 roomService = proxyquireNoCallThru('../lib/room-service', {
750 './room-membership-service': {
751 addRoomMember: roomMembershipServiceAddRoomMember
753 './assert-join-room-checks': assertJoinRoomChecks,
754 './recent-room-service': {
755 saveLastVisitedTroupeforUserId: recentRoomServiceSaveLastVisitedTroupeforUserId
760 it('should allow a user to join a room', function() {
761 joinRoomCheckFailed = false;
763 return roomService.joinRoom(troupe, user).then(function() {
764 mockito.verify(assertJoinRoomChecks, once)();
765 mockito.verify(recentRoomServiceSaveLastVisitedTroupeforUserId, once)();
766 mockito.verify(roomMembershipServiceAddRoomMember, once)();
770 it('should deny a user join room there are too many people in the room', function() {
771 joinRoomCheckFailed = true;
774 .joinRoom(troupe, user)
776 assert.ok(false, 'Expected an exception');
779 // This is what we want...
782 mockito.verify(assertJoinRoomChecks, once)();
787 describe('integration tests #slow', function() {
789 var createPolicyForRoom;
791 var roomMembershipService;
793 var fixture = fixtureLoader.setup({
801 beforeEach(function() {
802 roomMembershipService = require('../lib/room-membership-service');
803 createPolicyForRoom = mockito.mockFunction();
806 .when(createPolicyForRoom)()
807 .then(function(pUser, pRoom) {
808 assert.strictEqual(pUser, fixture.user1);
809 assert.strictEqual(pRoom.id, fixture.troupeOrg1.id);
810 return Promise.resolve({
811 canJoin: function() {
812 return Promise.resolve(access);
817 roomService = proxyquireNoCallThru('../lib/room-service', {
818 'gitter-web-permissions/lib/policy-factory': {
819 createPolicyForRoom: createPolicyForRoom
824 it('should add a member to the room', function() {
828 .joinRoom(fixture.troupeOrg1, fixture.user1)
830 return roomMembershipService.checkRoomMembership(
831 fixture.troupeOrg1.id,
835 .then(function(isMember) {
836 assert.strictEqual(isMember, true);
840 it('should be idempotent', function() {
844 .joinRoom(fixture.troupeOrg1, fixture.user1)
846 return roomMembershipService.checkRoomMembership(
847 fixture.troupeOrg1.id,
851 .then(function(isMember) {
852 assert.strictEqual(isMember, true);
853 return roomService.joinRoom(fixture.troupeOrg1, fixture.user1);
856 return roomMembershipService.checkRoomMembership(
857 fixture.troupeOrg1.id,
861 .then(function(isMember) {
862 assert.strictEqual(isMember, true);
868 describe('updateTopic #slow', function() {
869 var roomService = require('../lib/room-service');
871 var fixture = fixtureLoader.setup({
875 it('should update the topic', function() {
877 .updateTopic(fixture.troupe1.id, 'THE TOPIC')
879 return persistence.Troupe.findById(fixture.troupe1.id).exec();
881 .then(function(troupe) {
882 assert.strictEqual(troupe.topic, 'THE TOPIC');
886 it('null should update the topic', function() {
888 .updateTopic(fixture.troupe1.id, null)
890 return persistence.Troupe.findById(fixture.troupe1.id).exec();
892 .then(function(troupe) {
893 assert.strictEqual(troupe.topic, '');