3 const assert = require('assert');
4 const sinon = require('sinon');
5 const fixtureLoader = require('gitter-web-test-utils/lib/test-fixtures');
6 const chatService = require('gitter-web-chats');
7 const restSerializer = require('../../../server/serializers/rest-serializer');
8 const GitterBridge = require('../lib/gitter-bridge');
9 const GitterUtils = require('../lib/gitter-utils');
10 const store = require('../lib/store');
11 const getMxidForGitterUser = require('../lib/get-mxid-for-gitter-user');
12 const { getCanonicalAliasLocalpartForGitterRoomUri } = require('../lib/matrix-alias-utils');
14 const strategy = new restSerializer.ChatStrategy();
16 describe('gitter-bridge', () => {
17 const overallFixtures = fixtureLoader.setupEach({
27 redactEvent: sinon.spy(),
28 resolveRoom: sinon.spy(),
29 deleteRoomAlias: sinon.spy(),
30 getDirectoryVisibility: sinon.spy(),
31 setDirectoryVisibility: sinon.spy(),
32 getRoomMembers: sinon.spy(),
34 getRoomAliases: sinon.spy()
39 matrixClient: clientSpies,
40 getStateEvent: sinon.spy(),
41 sendStateEvent: sinon.spy(),
42 getEvent: sinon.spy(() => ({
43 event_id: `$${fixtureLoader.generateGithubId()}:localhost`,
44 sender: '@alice:localhost'
46 sendMessage: sinon.spy(() => ({
47 event_id: `$${fixtureLoader.generateGithubId()}:localhost`
49 createRoom: sinon.spy(() => ({
50 room_id: `!${fixtureLoader.generateGithubId()}:localhost`
52 createAlias: sinon.spy(),
53 setRoomAvatar: sinon.spy(),
54 getProfileInfo: sinon.spy(() => ({})),
55 setDisplayName: sinon.spy(),
56 uploadContent: sinon.spy(),
57 setAvatarUrl: sinon.spy(),
66 getIntent: sinon.spy((/*userId*/) => intentSpies)
69 gitterBridge = new GitterBridge(matrixBridge, overallFixtures.userBridge1.username);
71 gitterUtils = new GitterUtils(
73 overallFixtures.userBridge1.username,
74 overallFixtures.group1.uri
78 describe('onDataChange', () => {
79 describe('handleChatMessageCreateEvent', () => {
80 const fixture = fixtureLoader.setupEach({
99 text: 'my gitter message'
104 text: 'my gitter message2'
109 text: '@user1 my gitter status(/me) message',
111 '<span data-link-type="mention" data-screen-name="user1" class="mention">@user1</span> my gitter status(/me) message',
117 text: 'my gitter threaded message1',
123 text: 'my gitter threaded message2',
126 messageFromVirtualUser1: {
130 externalId: 'test-person:matrix.org',
134 text: 'my virtualUser message'
136 messageFromBridgeBot1: {
139 text: `I'm the badger bridge bot`
143 troupe: 'troupePrivate1',
144 text: 'my private gitter message'
148 it('new message gets sent off to Matrix', async () => {
149 const serializedMessage = await restSerializer.serializeObject(fixture.message1, strategy);
151 await gitterBridge.onDataChange({
153 url: `/rooms/${fixture.troupe1.id}/chatMessages`,
155 model: serializedMessage
158 // Room is created for something that hasn't been bridged before
159 assert.strictEqual(matrixBridge.getIntent().createRoom.callCount, 1);
161 // Message is sent to the new room
162 assert.strictEqual(matrixBridge.getIntent().sendMessage.callCount, 1);
163 assert.deepEqual(matrixBridge.getIntent().sendMessage.getCall(0).args[1], {
164 body: fixture.message1.text,
165 format: 'org.matrix.custom.html',
166 formatted_body: fixture.message1.html,
171 it('new status(/me) message gets sent off to Matrix', async () => {
172 const serializedMessage = await restSerializer.serializeObject(
173 fixture.messageStatus1,
177 await gitterBridge.onDataChange({
179 url: `/rooms/${fixture.troupe1.id}/chatMessages`,
181 model: serializedMessage
184 // Room is created for something that hasn't been bridged before
185 assert.strictEqual(matrixBridge.getIntent().createRoom.callCount, 1);
187 // Message is sent to the new room
188 assert.strictEqual(matrixBridge.getIntent().sendMessage.callCount, 1);
189 assert.deepEqual(matrixBridge.getIntent().sendMessage.getCall(0).args[1], {
190 body: 'my gitter status(/me) message',
191 format: 'org.matrix.custom.html',
192 formatted_body: 'my gitter status(/me) message',
197 it('subsequent multiple messages go to the same room', async () => {
198 const serializedMessage1 = await restSerializer.serializeObject(fixture.message1, strategy);
199 const serializedMessage2 = await restSerializer.serializeObject(fixture.message2, strategy);
201 await gitterBridge.onDataChange({
203 url: `/rooms/${fixture.troupe1.id}/chatMessages`,
205 model: serializedMessage1
208 await gitterBridge.onDataChange({
210 url: `/rooms/${fixture.troupe1.id}/chatMessages`,
212 model: serializedMessage2
215 // Room is only created once
216 assert.strictEqual(matrixBridge.getIntent().createRoom.callCount, 1);
218 // Messages are sent to the new room
219 assert.strictEqual(matrixBridge.getIntent().sendMessage.callCount, 2);
221 const sendMessageCall1 = matrixBridge.getIntent().sendMessage.getCall(0);
222 const sendMessageCall2 = matrixBridge.getIntent().sendMessage.getCall(1);
223 // Make sure the messages were sent to the same room
224 assert.strictEqual(sendMessageCall1.args[0], sendMessageCall2.args[0]);
227 it('threaded conversation reply gets sent off to Matrix', async () => {
228 const serializedMessage = await restSerializer.serializeObject(
229 fixture.messageThreaded1,
233 const matrixRoomId = `!${fixtureLoader.generateGithubId()}:localhost`;
234 await store.storeBridgedRoom(fixture.troupe1.id, matrixRoomId);
235 const parentMessageEventId = `$${fixtureLoader.generateGithubId()}:localhost`;
236 await store.storeBridgedMessage(fixture.message1, matrixRoomId, parentMessageEventId);
238 await gitterBridge.onDataChange({
240 url: `/rooms/${fixture.troupe1.id}/chatMessages`,
242 model: serializedMessage
245 // Message is sent to the new room
246 assert.strictEqual(matrixBridge.getIntent().sendMessage.callCount, 1);
247 assert.deepEqual(matrixBridge.getIntent().sendMessage.getCall(0).args[1], {
248 body: fixture.messageThreaded1.text,
249 format: 'org.matrix.custom.html',
250 formatted_body: fixture.messageThreaded1.html,
254 event_id: parentMessageEventId
260 it('threaded conversation replies to last message in thread gets sent off to Matrix', async () => {
261 const serializedMessage = await restSerializer.serializeObject(
262 fixture.messageThreaded2,
266 const matrixRoomId = `!${fixtureLoader.generateGithubId()}:localhost`;
267 await store.storeBridgedRoom(fixture.troupe1.id, matrixRoomId);
268 const parentMessageEventId = `$${fixtureLoader.generateGithubId()}:localhost`;
269 await store.storeBridgedMessage(fixture.message1, matrixRoomId, parentMessageEventId);
270 const threadReplyMessageEventId1 = `$${fixtureLoader.generateGithubId()}:localhost`;
271 await store.storeBridgedMessage(
272 fixture.messageThreaded1,
274 threadReplyMessageEventId1
277 await gitterBridge.onDataChange({
279 url: `/rooms/${fixture.troupe1.id}/chatMessages`,
281 model: serializedMessage
284 // Message is sent to the new room
285 assert.strictEqual(matrixBridge.getIntent().sendMessage.callCount, 1);
286 assert.deepEqual(matrixBridge.getIntent().sendMessage.getCall(0).args[1], {
287 body: fixture.messageThreaded2.text,
288 format: 'org.matrix.custom.html',
289 formatted_body: fixture.messageThreaded2.html,
293 event_id: threadReplyMessageEventId1
299 // This is a edge case in the transition between no Matrix bridge and Matrix.
300 // I don't think we need to worry too much about what happens. Just want a test to know
301 // something happens.
302 it('threaded conversation reply where the parent does not exist on Matrix still gets sent', async () => {
303 const serializedMessage = await restSerializer.serializeObject(
304 fixture.messageThreaded1,
308 const matrixRoomId = `!${fixtureLoader.generateGithubId()}:localhost`;
309 await store.storeBridgedRoom(fixture.troupe1.id, matrixRoomId);
310 // We purposely do not associate the bridged message. We are testing that the
311 // message is ignored if the parent message event is not in the database.
312 //const parentMessageEventId = `$${fixtureLoader.generateGithubId()}:localhost`;
313 //await store.storeBridgedMessage(fixture.message1, matrixRoomId, parentMessageEventId);
315 await gitterBridge.onDataChange({
317 url: `/rooms/${fixture.troupe1.id}/chatMessages`,
319 model: serializedMessage
322 // Message is sent to the new room
323 assert.strictEqual(matrixBridge.getIntent().sendMessage.callCount, 1);
324 assert.deepEqual(matrixBridge.getIntent().sendMessage.getCall(0).args[1], {
325 body: fixture.messageThreaded1.text,
326 format: 'org.matrix.custom.html',
327 formatted_body: fixture.messageThreaded1.html,
332 it('new message from virtualUser is suppressed (no echo back and forth)', async () => {
333 const serializedVirtualUserMessage = await restSerializer.serializeObject(
334 fixture.messageFromVirtualUser1,
338 await gitterBridge.onDataChange({
340 url: `/rooms/${fixture.troupe1.id}/chatMessages`,
342 model: serializedVirtualUserMessage
346 assert.strictEqual(matrixBridge.getIntent().createRoom.callCount, 0);
348 assert.strictEqual(matrixBridge.getIntent().sendMessage.callCount, 0);
351 it('new message in private room is bridged', async () => {
352 const strategy = new restSerializer.ChatStrategy();
353 const serializedMessage = await restSerializer.serializeObject(
354 fixture.messagePrivate1,
358 await gitterBridge.onDataChange({
360 url: `/rooms/${fixture.troupePrivate1.id}/chatMessages`,
362 model: serializedMessage
365 // Room is created for something that hasn't been bridged before
366 assert.strictEqual(matrixBridge.getIntent().createRoom.callCount, 1);
367 assert.deepEqual(matrixBridge.getIntent().createRoom.getCall(0).args[0], {
368 createAsClient: true,
370 name: fixture.troupePrivate1.uri,
371 room_alias_name: getCanonicalAliasLocalpartForGitterRoomUri(fixture.troupePrivate1.uri),
372 visibility: 'private',
373 preset: 'private_chat'
377 // Message is sent to the new room
378 assert.strictEqual(matrixBridge.getIntent().sendMessage.callCount, 1);
381 describe('inviteMatrixUserToDmRoomIfNeeded', async () => {
382 const otherPersonMxid = '@alice:localhost';
385 let serializedMessage;
386 beforeEach(async () => {
387 matrixRoomId = `!${fixtureLoader.generateGithubId()}:localhost`;
389 serializedMessage = await restSerializer.serializeObject(fixture.message1, strategy);
392 it('should invite Matrix user back to Matrix DM room if they have left', async () => {
393 const newDmRoom = await gitterUtils.getOrCreateGitterDmRoomByGitterUserAndOtherPersonMxid(
397 await store.storeBridgedRoom(newDmRoom._id, matrixRoomId);
399 // Stub the other user as left the room
400 matrixBridge.getIntent().getStateEvent = (targetRoomId, type, stateKey) => {
402 targetRoomId === matrixRoomId &&
403 type === 'm.room.member' &&
404 stateKey === otherPersonMxid
412 await gitterBridge.onDataChange({
414 url: `/rooms/${newDmRoom._id}/chatMessages`,
416 model: serializedMessage
419 assert.strictEqual(matrixBridge.getIntent().invite.callCount, 1);
420 assert.deepEqual(matrixBridge.getIntent().invite.getCall(0).args[1], otherPersonMxid);
423 it('should invite Matrix user back to Matrix DM room if they were never in the room', async () => {
424 const newDmRoom = await gitterUtils.getOrCreateGitterDmRoomByGitterUserAndOtherPersonMxid(
428 await store.storeBridgedRoom(newDmRoom._id, matrixRoomId);
430 // Stub the other user as left the room
431 matrixBridge.getIntent().getStateEvent = (targetRoomId, type, stateKey) => {
433 targetRoomId === matrixRoomId &&
434 type === 'm.room.member' &&
435 stateKey === otherPersonMxid
441 await gitterBridge.onDataChange({
443 url: `/rooms/${newDmRoom._id}/chatMessages`,
445 model: serializedMessage
448 assert.strictEqual(matrixBridge.getIntent().invite.callCount, 1);
449 assert.deepEqual(matrixBridge.getIntent().invite.getCall(0).args[1], otherPersonMxid);
452 it('should work if Matrix user already in DM room (not mess anything up)', async () => {
453 const newDmRoom = await gitterUtils.getOrCreateGitterDmRoomByGitterUserAndOtherPersonMxid(
457 await store.storeBridgedRoom(newDmRoom._id, matrixRoomId);
459 // Stub the other user as already in the room
460 matrixBridge.getIntent().getStateEvent = (targetRoomId, type, stateKey) => {
462 targetRoomId === matrixRoomId &&
463 type === 'm.room.member' &&
464 stateKey === otherPersonMxid
472 await gitterBridge.onDataChange({
474 url: `/rooms/${newDmRoom._id}/chatMessages`,
476 model: serializedMessage
479 assert.strictEqual(matrixBridge.getIntent().invite.callCount, 0);
482 it('should still allow message to send if Matrix user failed to invite', async () => {
483 const newDmRoom = await gitterUtils.getOrCreateGitterDmRoomByGitterUserAndOtherPersonMxid(
487 await store.storeBridgedRoom(newDmRoom._id, matrixRoomId);
489 // Stub the other user as left the room
490 matrixBridge.getIntent().getStateEvent = (targetRoomId, type, stateKey) => {
492 targetRoomId === matrixRoomId &&
493 type === 'm.room.member' &&
494 stateKey === otherPersonMxid
502 // Force the invitation to fail!
503 matrixBridge.getIntent().invite = () => Promise.reject('Fake failed to invite');
505 await gitterBridge.onDataChange({
507 url: `/rooms/${newDmRoom._id}/chatMessages`,
509 model: serializedMessage
512 // Make sure the feedback warning message from the bridge user (@gitter-badger)
513 // was sent in the Gitter room to let them know we had trouble inviting the Matrix
514 // side back to the room.
515 const messages = await chatService.findChatMessagesForTroupe(newDmRoom._id);
518 `Unable to invite Matrix user back to DM room. They probably won't know about the message you just sent.`
521 // Message is still sent to the new room
522 assert.strictEqual(matrixBridge.getIntent().sendMessage.callCount, 1);
523 assert.deepEqual(matrixBridge.getIntent().sendMessage.getCall(0).args[1], {
524 body: fixture.message1.text,
525 format: 'org.matrix.custom.html',
526 formatted_body: fixture.message1.html,
531 it('should not invite anyone for non-DM room', async () => {
532 sinon.spy(gitterBridge, 'inviteMatrixUserToDmRoomIfNeeded');
533 await store.storeBridgedRoom(fixture.troupe1.id, matrixRoomId);
535 await gitterBridge.onDataChange({
537 url: `/rooms/${fixture.troupe1.id}/chatMessages`,
539 model: serializedMessage
542 // null means no invite was necessary for this room
543 const inviteResult = await gitterBridge.inviteMatrixUserToDmRoomIfNeeded.firstCall
545 assert.strictEqual(inviteResult, null);
548 it('messages from the bridge bot do not trigger invites to be sent out (avoid feedback loop)', async () => {
549 await store.storeBridgedRoom(fixture.troupe1.id, matrixRoomId);
550 // Pass in userBridge1 as the bridging user
551 gitterBridge = new GitterBridge(matrixBridge, fixture.userBridge1.username);
552 sinon.spy(gitterBridge, 'inviteMatrixUserToDmRoomIfNeeded');
554 // Use a message from userBridge1
555 serializedMessage = await restSerializer.serializeObject(
556 fixture.messageFromBridgeBot1,
560 await gitterBridge.onDataChange({
562 url: `/rooms/${fixture.troupe1.id}/chatMessages`,
564 model: serializedMessage
567 assert.strictEqual(gitterBridge.inviteMatrixUserToDmRoomIfNeeded.callCount, 0);
572 describe('handleChatMessageEditEvent', () => {
573 const fixture = fixtureLoader.setupEach({
583 securityDescriptor: {
592 text: 'my gitter message'
594 messageFromVirtualUser1: {
598 externalId: 'test-person:matrix.org',
602 text: 'my virtualUser message'
606 troupe: 'troupePrivate1',
607 text: 'my private gitter message'
611 it('edit message gets sent off to Matrix', async () => {
612 const matrixRoomId = `!${fixtureLoader.generateGithubId()}:localhost`;
613 const matrixMessageEventId = `$${fixtureLoader.generateGithubId()}`;
614 await store.storeBridgedMessage(fixture.message1, matrixRoomId, matrixMessageEventId);
616 const serializedMessage = await restSerializer.serializeObject(fixture.message1, strategy);
618 await gitterBridge.onDataChange({
620 url: `/rooms/${fixture.troupe1.id}/chatMessages`,
623 ...serializedMessage,
624 editedAt: new Date().toUTCString()
628 // Message edit is sent off to Matrix
629 assert.strictEqual(matrixBridge.getIntent().sendMessage.callCount, 1);
631 assert.deepEqual(matrixBridge.getIntent().sendMessage.getCall(0).args[1], {
632 body: `* ${fixture.message1.text}`,
633 format: 'org.matrix.custom.html',
634 formatted_body: `* ${fixture.message1.html}`,
637 body: fixture.message1.text,
638 format: 'org.matrix.custom.html',
639 formatted_body: fixture.message1.html,
643 event_id: matrixMessageEventId,
644 rel_type: 'm.replace'
649 it('message with same editedAt date is ignored', async () => {
650 const matrixRoomId = `!${fixtureLoader.generateGithubId()}:localhost`;
651 const matrixMessageEventId = `$${fixtureLoader.generateGithubId()}`;
652 await store.storeBridgedMessage(fixture.message1, matrixRoomId, matrixMessageEventId);
654 const serializedMessage = await restSerializer.serializeObject(fixture.message1, strategy);
656 await gitterBridge.onDataChange({
658 url: `/rooms/${fixture.troupe1.id}/chatMessages`,
660 model: serializedMessage
663 // Message edit does not get sent to Matrix since it's already over there
664 assert.strictEqual(matrixBridge.getIntent().sendMessage.callCount, 0);
667 it('non-bridged message that gets an edit is ignored', async () => {
668 const serializedMessage = await restSerializer.serializeObject(fixture.message1, strategy);
670 // We purposely do not associate the bridged message. We are testing that the
671 // edit is ignored if there is no association in the database.
672 //const matrixRoomId = `!${fixtureLoader.generateGithubId()}:localhost`;
673 //await store.storeBridgedMessage(fixture.message1, matrixRoomId, matrixMessageEventId);
675 await gitterBridge.onDataChange({
677 url: `/rooms/${fixture.troupe1.id}/chatMessages`,
679 model: serializedMessage
682 // Message edit is ignored if there isn't an associated bridge message
683 assert.strictEqual(matrixBridge.getIntent().sendMessage.callCount, 0);
686 it('message edit from virtualUser is suppressed (no echo back and forth)', async () => {
687 const serializedVirtualUserMessage = await restSerializer.serializeObject(
688 fixture.messageFromVirtualUser1,
692 const matrixRoomId = `!${fixtureLoader.generateGithubId()}:localhost`;
693 const matrixMessageEventId = `$${fixtureLoader.generateGithubId()}`;
694 await store.storeBridgedMessage(
695 fixture.messageFromVirtualUser1,
700 await gitterBridge.onDataChange({
702 url: `/rooms/${fixture.troupe1.id}/chatMessages`,
704 model: serializedVirtualUserMessage
708 assert.strictEqual(matrixBridge.getIntent().sendMessage.callCount, 0);
711 it('edit in private room are bridged', async () => {
712 const serializedMessage = await restSerializer.serializeObject(
713 fixture.messagePrivate1,
717 const matrixRoomId = `!${fixtureLoader.generateGithubId()}:localhost`;
718 const matrixMessageEventId = `$${fixtureLoader.generateGithubId()}`;
719 await store.storeBridgedMessage(
720 fixture.messagePrivate1,
725 await gitterBridge.onDataChange({
727 url: `/rooms/${fixture.troupePrivate1.id}/chatMessages`,
730 ...serializedMessage,
731 editedAt: new Date().toUTCString()
735 assert.strictEqual(matrixBridge.getIntent().sendMessage.callCount, 1);
739 describe('handleChatMessageRemoveEvent', () => {
740 const fixture = fixtureLoader.setupEach({
750 securityDescriptor: {
759 text: 'my gitter message'
763 troupe: 'troupePrivate1',
764 text: 'my private gitter message'
768 it('remove message gets sent off to Matrix', async () => {
769 const matrixRoomId = `!${fixtureLoader.generateGithubId()}:localhost`;
770 const matrixMessageEventId = `$${fixtureLoader.generateGithubId()}`;
771 await store.storeBridgedMessage(fixture.message1, matrixRoomId, matrixMessageEventId);
773 await gitterBridge.onDataChange({
775 url: `/rooms/${fixture.troupe1.id}/chatMessages`,
777 model: { id: fixture.message1.id }
780 // Message remove is sent off to Matrix
781 assert.strictEqual(matrixBridge.getIntent().matrixClient.redactEvent.callCount, 1);
783 matrixBridge.getIntent().matrixClient.redactEvent.getCall(0).args[1],
788 it('non-bridged message that gets removed is ignored', async () => {
789 // We purposely do not associate bridged message. We are testing that the
790 // remove is ignored if no association in the database.
791 //const matrixRoomId = `!${fixtureLoader.generateGithubId()}:localhost`;
792 //await store.storeBridgedMessage(fixture.message1, matrixRoomId, matrixMessageEventId);
794 await gitterBridge.onDataChange({
796 url: `/rooms/${fixture.troupe1.id}/chatMessages`,
798 model: { id: fixture.message1.id }
801 // Message remove is ignored if there isn't an associated bridge message
802 assert.strictEqual(matrixBridge.getIntent().matrixClient.redactEvent.callCount, 0);
805 it('message remove in private room is bridged', async () => {
806 const matrixRoomId = `!${fixtureLoader.generateGithubId()}:localhost`;
807 const matrixMessageEventId = `$${fixtureLoader.generateGithubId()}`;
808 await store.storeBridgedMessage(
809 fixture.messagePrivate1,
814 await gitterBridge.onDataChange({
816 url: `/rooms/${fixture.troupePrivate1.id}/chatMessages`,
818 model: { id: fixture.messagePrivate1.id }
821 assert.strictEqual(matrixBridge.getIntent().matrixClient.redactEvent.callCount, 1);
824 it('when the Matrix API call to lookup the message author fails(`intent.getEvent()`), still deletes the message (using bridge user)', async () => {
825 // Make the event lookup Matrix API call fail
826 matrixBridge.getIntent().getEvent = () => {
827 throw new Error('Fake error and failed to fetch event');
830 const matrixRoomId = `!${fixtureLoader.generateGithubId()}:localhost`;
831 const matrixMessageEventId = `$${fixtureLoader.generateGithubId()}`;
832 await store.storeBridgedMessage(fixture.message1, matrixRoomId, matrixMessageEventId);
834 await gitterBridge.onDataChange({
836 url: `/rooms/${fixture.troupe1.id}/chatMessages`,
838 model: { id: fixture.message1.id }
841 // Message remove is sent off to Matrix
842 assert.strictEqual(matrixBridge.getIntent().matrixClient.redactEvent.callCount, 1);
844 matrixBridge.getIntent().matrixClient.redactEvent.getCall(0).args[1],
850 describe('handleRoomUpdateEvent', () => {
851 const fixture = fixtureLoader.setupEach({
860 securityDescriptor: {
868 it('room patch gets sent off to Matrix', async () => {
869 const matrixRoomId = `!${fixtureLoader.generateGithubId()}:localhost`;
870 await store.storeBridgedRoom(fixture.troupe1.id, matrixRoomId);
872 await gitterBridge.onDataChange({
874 url: `/rooms/${fixture.troupe1.id}`,
876 model: { id: fixture.troupe1.id, topic: 'bar' }
879 // Find the spy call where the topic was updated
880 const topicCall = matrixBridge
882 .sendStateEvent.getCalls()
884 const [mid, eventType] = call.args;
885 if (mid === matrixRoomId && eventType === 'm.room.topic') {
889 assert.deepEqual(topicCall.args, [
894 // This value should really be 'bar' no worries, this is just a side-effect of mocking the `onDataChange`
895 // instead of actually making an update to the room in the databse
901 it('private room patch is bridged', async () => {
902 await gitterBridge.onDataChange({
904 url: `/rooms/${fixture.troupePrivate1.id}`,
906 model: { id: fixture.troupePrivate1.id, topic: 'bar' }
909 const sendStateEventCalls = matrixBridge.getIntent().sendStateEvent.getCalls();
911 sendStateEventCalls.length > 0,
912 `sendStateEvent was called ${sendStateEventCalls.length} times, expected at least 1 call`
916 it('room update gets sent off to Matrix (same as patch)', async () => {
917 const matrixRoomId = `!${fixtureLoader.generateGithubId()}:localhost`;
918 await store.storeBridgedRoom(fixture.troupe1.id, matrixRoomId);
920 const strategy = new restSerializer.TroupeStrategy();
921 const serializedRoom = await restSerializer.serializeObject(fixture.troupe1, strategy);
923 await gitterBridge.onDataChange({
925 url: `/rooms/${fixture.troupe1.id}`,
927 model: serializedRoom
930 const sendStateEventCalls = matrixBridge.getIntent().sendStateEvent.getCalls();
932 sendStateEventCalls.length > 0,
933 `sendStateEvent was called ${sendStateEventCalls.length} times, expected at least 1 call`
937 it('private room update is bridged', async () => {
938 const strategy = new restSerializer.TroupeStrategy();
939 const serializedRoom = await restSerializer.serializeObject(fixture.troupe1, strategy);
941 await gitterBridge.onDataChange({
943 url: `/rooms/${fixture.troupePrivate1.id}`,
945 model: serializedRoom
948 const sendStateEventCalls = matrixBridge.getIntent().sendStateEvent.getCalls();
950 sendStateEventCalls.length > 0,
951 `sendStateEvent was called ${sendStateEventCalls.length} times, expected at least 1 call`
956 describe('handleRoomRemoveEvent', () => {
957 const fixture = fixtureLoader.setupEach({
966 securityDescriptor: {
974 it('deleted Gitter room shuts down the room on the Matrix side', async () => {
975 const matrixRoomId = `!${fixtureLoader.generateGithubId()}:localhost`;
976 await store.storeBridgedRoom(fixture.troupe1.id, matrixRoomId);
978 await gitterBridge.onDataChange({
980 url: `/rooms/${fixture.troupe1.id}`,
982 model: { id: fixture.troupe1.id }
985 // Find the spy call where the join rules are changed so no one else can join
986 const joinRuleCall = matrixBridge
988 .sendStateEvent.getCalls()
990 const [mid, eventType] = call.args;
991 if (mid === matrixRoomId && eventType === 'm.room.join_rules') {
995 assert.deepEqual(joinRuleCall.args, [
1006 describe('handleUserJoiningRoom', () => {
1007 const fixture = fixtureLoader.setupEach({
1012 it('user join membership syncs to Matrix', async () => {
1013 const matrixRoomId = `!${fixtureLoader.generateGithubId()}:localhost`;
1014 await store.storeBridgedRoom(fixture.troupe1.id, matrixRoomId);
1015 const mxidForGitterUser = getMxidForGitterUser(fixture.user1);
1017 await gitterBridge.onDataChange({
1019 url: `/rooms/${fixture.troupe1.id}/users`,
1020 operation: 'create',
1021 model: { id: fixture.user1.id }
1024 assert.strictEqual(matrixBridge.getIntent.callCount, 3);
1025 assert.strictEqual(matrixBridge.getIntent.getCall(2).args[0], mxidForGitterUser);
1026 assert.strictEqual(matrixBridge.getIntent().join.callCount, 1);
1027 assert.strictEqual(matrixBridge.getIntent().join.getCall(0).args[0], matrixRoomId);
1030 it(`user join is ignored when the Matrix room isn't created yet`, async () => {
1031 // This is commented out on purpose, we are testing that the join is ignored
1032 // when the Matrix room hasn't been created yet.
1033 //await store.storeBridgedRoom(fixture.troupe1.id, matrixRoomId);
1035 await gitterBridge.onDataChange({
1037 url: `/rooms/${fixture.troupe1.id}/users`,
1038 operation: 'create',
1039 model: { id: fixture.user1.id }
1042 assert.strictEqual(matrixBridge.getIntent().join.callCount, 0);
1045 it('no action occurs when user join fails for normal room', async () => {
1046 const matrixRoomId = `!${fixtureLoader.generateGithubId()}:localhost`;
1047 await store.storeBridgedRoom(fixture.troupe1.id, matrixRoomId);
1049 // Make the event lookup Matrix API call fail
1050 matrixBridge.getIntent().join = () => {
1051 throw new Error('Failed to join room');
1054 await gitterBridge.onDataChange({
1056 url: `/rooms/${fixture.troupe1.id}/users`,
1057 operation: 'create',
1058 model: { id: fixture.user1.id }
1062 assert.strictEqual(matrixBridge.getIntent().createRoom.callCount, 0);
1065 it('new Matrix DM created when user join fails for DM room', async () => {
1066 const otherPersonMxid = '@alice:localhost';
1067 const matrixRoomId = `!${fixtureLoader.generateGithubId()}:localhost`;
1068 const newDmRoom = await gitterUtils.getOrCreateGitterDmRoomByGitterUserAndOtherPersonMxid(
1072 await store.storeBridgedRoom(newDmRoom._id, matrixRoomId);
1074 // Make the event lookup Matrix API call fail
1075 matrixBridge.getIntent().join = () => {
1076 throw new Error('Failed to join room');
1079 await gitterBridge.onDataChange({
1081 url: `/rooms/${newDmRoom._id}/users`,
1082 operation: 'create',
1083 model: { id: fixture.user1.id }
1086 // New DM room is created
1087 assert.strictEqual(matrixBridge.getIntent().createRoom.callCount, 1);
1088 assert.deepEqual(matrixBridge.getIntent().createRoom.getCall(0).args[0], {
1089 createAsClient: true,
1091 visibility: 'private',
1092 preset: 'trusted_private_chat',
1094 invite: [otherPersonMxid]
1100 describe('handleUserLeavingRoom', () => {
1101 const fixture = fixtureLoader.setupEach({
1106 it('user leave membership syncs to Matrix', async () => {
1107 const matrixRoomId = `!${fixtureLoader.generateGithubId()}:localhost`;
1108 await store.storeBridgedRoom(fixture.troupe1.id, matrixRoomId);
1109 const mxidForGitterUser = getMxidForGitterUser(fixture.user1);
1111 await gitterBridge.onDataChange({
1113 url: `/rooms/${fixture.troupe1.id}/users`,
1114 operation: 'remove',
1115 model: { id: fixture.user1.id }
1118 assert.strictEqual(matrixBridge.getIntent.callCount, 3);
1119 assert.strictEqual(matrixBridge.getIntent.getCall(2).args[0], mxidForGitterUser);
1120 assert.strictEqual(matrixBridge.getIntent().leave.callCount, 1);
1121 assert.strictEqual(matrixBridge.getIntent().leave.getCall(0).args[0], matrixRoomId);
1124 it(`user leave is ignored when the Matrix room isn't created yet`, async () => {
1125 // This is commented out on purpose, we are testing that the leave is ignored
1126 // when the Matrix room hasn't been created yet.
1127 //await store.storeBridgedRoom(fixture.troupe1.id, matrixRoomId);
1129 await gitterBridge.onDataChange({
1131 url: `/rooms/${fixture.troupe1.id}/users`,
1132 operation: 'remove',
1133 model: { id: fixture.user1.id }
1136 assert.strictEqual(matrixBridge.getIntent().leave.callCount, 0);
1140 describe('handleRoomBanEvent', () => {
1141 const fixture = fixtureLoader.setupEach({
1146 it('bridges ban for Gitter user', async () => {
1147 const matrixRoomId = `!${fixtureLoader.generateGithubId()}:localhost`;
1148 await store.storeBridgedRoom(fixture.troupe1.id, matrixRoomId);
1149 const mxidForGitterUser = getMxidForGitterUser(fixture.user1);
1151 await gitterBridge.onDataChange({
1153 url: `/rooms/${fixture.troupe1.id}/bans`,
1154 operation: 'create',
1155 model: { userId: fixture.user1.id }
1158 assert.strictEqual(matrixBridge.getIntent().ban.callCount, 1);
1159 assert.strictEqual(matrixBridge.getIntent().ban.getCall(0).args[0], matrixRoomId);
1160 assert.strictEqual(matrixBridge.getIntent().ban.getCall(0).args[1], mxidForGitterUser);
1163 it('bridges unban for Gitter user', async () => {
1164 const matrixRoomId = `!${fixtureLoader.generateGithubId()}:localhost`;
1165 await store.storeBridgedRoom(fixture.troupe1.id, matrixRoomId);
1166 const mxidForGitterUser = getMxidForGitterUser(fixture.user1);
1168 await gitterBridge.onDataChange({
1170 url: `/rooms/${fixture.troupe1.id}/bans`,
1171 operation: 'remove',
1172 model: { userId: fixture.user1.id }
1175 assert.strictEqual(matrixBridge.getIntent().unban.callCount, 1);
1176 assert.strictEqual(matrixBridge.getIntent().unban.getCall(0).args[0], matrixRoomId);
1177 assert.strictEqual(matrixBridge.getIntent().unban.getCall(0).args[1], mxidForGitterUser);
1180 it('bridges ban for virtualUser', async () => {
1181 const matrixRoomId = `!${fixtureLoader.generateGithubId()}:localhost`;
1182 await store.storeBridgedRoom(fixture.troupe1.id, matrixRoomId);
1184 await gitterBridge.onDataChange({
1186 url: `/rooms/${fixture.troupe1.id}/bans`,
1187 operation: 'create',
1191 externalId: 'bad-guy:matrix.org'
1196 assert.strictEqual(matrixBridge.getIntent().ban.callCount, 1);
1197 assert.strictEqual(matrixBridge.getIntent().ban.getCall(0).args[0], matrixRoomId);
1198 assert.strictEqual(matrixBridge.getIntent().ban.getCall(0).args[1], '@bad-guy:matrix.org');
1201 it('bridges unban for virtualUser', async () => {
1202 const matrixRoomId = `!${fixtureLoader.generateGithubId()}:localhost`;
1203 await store.storeBridgedRoom(fixture.troupe1.id, matrixRoomId);
1205 await gitterBridge.onDataChange({
1207 url: `/rooms/${fixture.troupe1.id}/bans`,
1208 operation: 'remove',
1212 externalId: 'bad-guy:matrix.org'
1217 assert.strictEqual(matrixBridge.getIntent().unban.callCount, 1);
1218 assert.strictEqual(matrixBridge.getIntent().unban.getCall(0).args[0], matrixRoomId);
1220 matrixBridge.getIntent().unban.getCall(0).args[1],
1221 '@bad-guy:matrix.org'