mattermost-mobile/app/actions/websocket.test.js
Elias Nahum d3a6e166ad
MM-15758 Update dependencies including Fastlane (#4272)
* Update dependencies including Fastlane and disable Flipper on iOS

* Remove EventEmitter for previous doc-viewer

* Fix android crash when setting more channels buttons

* Downgrade fuse.js

* Upgrade deps to latest

* Update Podfile.lock

* Downgrade RNN to 6.4.0

* QA Review #2

* Upgrade fuse.js to 6.0.0
2020-05-18 11:24:47 -07:00

1094 lines
46 KiB
JavaScript

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
/* eslint-disable no-import-assign */
import assert from 'assert';
import nock from 'nock';
import {Server, WebSocket as MockWebSocket} from 'mock-socket';
import {batchActions} from 'redux-batched-actions';
import thunk from 'redux-thunk';
import configureMockStore from 'redux-mock-store';
import {ChannelTypes, GeneralTypes, RoleTypes, TeamTypes, UserTypes} from '@mm-redux/action_types';
import * as ChannelActions from '@mm-redux/actions/channels';
import * as PostActions from '@mm-redux/actions/posts';
import * as TeamActions from '@mm-redux/actions/teams';
import {Client4} from '@mm-redux/client';
import {General, Posts, RequestStatus} from '@mm-redux/constants';
import * as PostSelectors from '@mm-redux/selectors/entities/posts';
import EventEmitter from '@mm-redux/utils/event_emitter';
import * as Actions from '@actions/websocket';
import {WebsocketEvents} from '@constants';
import initial_state from '@store/initial_state';
import TestHelper from 'test/test_helper';
import configureStore from 'test/test_store';
global.WebSocket = MockWebSocket;
const mockConfigRequest = (config = {}) => {
nock(Client4.getBaseRoute()).
get('/config/client?format=old').
reply(200, config);
};
const mockChanelsRequest = (teamId, channels = []) => {
nock(Client4.getUserRoute('me')).
get(`/teams/${teamId}/channels?include_deleted=true`).
reply(200, channels);
};
const mockGetKnownUsersRequest = (userIds = []) => {
nock(Client4.getBaseRoute()).
get('/users/known').
reply(200, userIds);
};
const mockRolesRequest = (rolesToLoad = []) => {
nock(Client4.getRolesRoute()).
post('/names', JSON.stringify(rolesToLoad)).
reply(200, rolesToLoad);
};
const mockTeamMemberRequest = (tm = []) => {
nock(Client4.getUserRoute('me')).
log(console.log).
get('/teams/members').
reply(200, tm);
};
describe('Actions.Websocket', () => {
let store;
let mockServer;
beforeAll(async () => {
store = await configureStore();
await TestHelper.initBasic(Client4);
const connUrl = (Client4.getUrl() + '/api/v4/websocket').replace(/^http:/, 'ws:');
mockServer = new Server(connUrl);
return store.dispatch(Actions.init({websocketUrl: Client4.getUrl().replace(/^http:/, 'ws:')}));
});
afterAll(async () => {
Actions.close()();
mockServer.stop();
await TestHelper.tearDown();
});
it('WebSocket Connect', () => {
const ws = store.getState().requests.general.websocket;
assert.ok(ws.status === RequestStatus.SUCCESS);
});
it('Websocket Handle New Post if post does not exist', async () => {
PostSelectors.getPost = jest.fn();
const channelId = TestHelper.basicChannel.id;
const message = JSON.stringify({event: WebsocketEvents.POSTED, data: {channel_display_name: TestHelper.basicChannel.display_name, channel_name: TestHelper.basicChannel.name, channel_type: 'O', post: `{"id": "71k8gz5ompbpfkrzaxzodffj8w", "create_at": 1508245311774, "update_at": 1508245311774, "edit_at": 0, "delete_at": 0, "is_pinned": false, "user_id": "${TestHelper.basicUser.id}", "channel_id": "${channelId}", "root_id": "", "parent_id": "", "original_id": "", "message": "Unit Test", "type": "", "props": {}, "hashtags": "", "pending_post_id": "t36kso9nwtdhbm8dbkd6g4eeby: 1508245311749"}`, sender_name: TestHelper.basicUser.username, team_id: TestHelper.basicTeam.id}, broadcast: {omit_users: null, user_id: '', channel_id: channelId, team_id: ''}, seq: 2});
nock(Client4.getBaseRoute()).
post('/users/ids').
reply(200, [TestHelper.basicUser.id]);
nock(Client4.getBaseRoute()).
post('/users/status/ids').
reply(200, [{user_id: TestHelper.basicUser.id, status: 'online', manual: false, last_activity_at: 1507662212199}]);
// Mock that post already exists and check it is not added
PostSelectors.getPost.mockReturnValueOnce(true);
mockServer.emit('message', message);
let entities = store.getState().entities;
let posts = entities.posts.posts;
assert.deepEqual(posts, {});
// Mock that post does not exist and check it is added
PostSelectors.getPost.mockReturnValueOnce(false);
mockServer.emit('message', message);
await TestHelper.wait(100);
entities = store.getState().entities;
posts = entities.posts.posts;
const postId = Object.keys(posts)[0];
assert.ok(posts[postId].message.indexOf('Unit Test') > -1);
entities = store.getState().entities;
});
it('Websocket Handle New Post emits INCREASE_POST_VISIBILITY_BY_ONE for current channel when post does not exist', async () => {
PostSelectors.getPost = jest.fn();
const emit = jest.spyOn(EventEmitter, 'emit');
const currentChannelId = TestHelper.generateId();
const otherChannelId = TestHelper.generateId();
const messageFor = (channelId) => ({event: WebsocketEvents.POSTED, data: {channel_display_name: TestHelper.basicChannel.display_name, channel_name: TestHelper.basicChannel.name, channel_type: 'O', post: `{"id": "71k8gz5ompbpfkrzaxzodffj8w", "create_at": 1508245311774, "update_at": 1508245311774, "edit_at": 0, "delete_at": 0, "is_pinned": false, "user_id": "${TestHelper.basicUser.id}", "channel_id": "${channelId}", "root_id": "", "parent_id": "", "original_id": "", "message": "Unit Test", "type": "", "props": {}, "hashtags": "", "pending_post_id": "t36kso9nwtdhbm8dbkd6g4eeby: 1508245311749"}`, sender_name: TestHelper.basicUser.username, team_id: TestHelper.basicTeam.id}, broadcast: {omit_users: null, user_id: '', channel_id: channelId, team_id: ''}, seq: 2});
await store.dispatch(ChannelActions.selectChannel(currentChannelId));
await TestHelper.wait(100);
// Post does not exist and is not for current channel
PostSelectors.getPost.mockReturnValueOnce(false);
mockServer.emit('message', JSON.stringify(messageFor(otherChannelId)));
expect(emit).not.toHaveBeenCalled();
// Post exists and is not for current channel
PostSelectors.getPost.mockReturnValueOnce(true);
mockServer.emit('message', JSON.stringify(messageFor(otherChannelId)));
expect(emit).not.toHaveBeenCalled();
// Post exists and is for current channel
PostSelectors.getPost.mockReturnValueOnce(true);
mockServer.emit('message', JSON.stringify(messageFor(currentChannelId)));
expect(emit).not.toHaveBeenCalled();
// Post does not exist and is for current channel
PostSelectors.getPost.mockReturnValueOnce(false);
mockServer.emit('message', JSON.stringify(messageFor(currentChannelId)));
expect(emit).toHaveBeenCalledWith(WebsocketEvents.INCREASE_POST_VISIBILITY_BY_ONE);
});
it('Websocket Handle New Post if status is manually set do not set to online', async () => {
const userId = TestHelper.generateId();
store = await configureStore({
entities: {
users: {
statuses: {
[userId]: General.DND,
},
isManualStatus: {
[userId]: true,
},
},
},
});
await store.dispatch(Actions.init({websocketUrl: Client4.getUrl().replace(/^http:/, 'ws:')}));
const channelId = TestHelper.basicChannel.id;
const message = JSON.stringify({
event: WebsocketEvents.POSTED,
data: {
channel_display_name: TestHelper.basicChannel.display_name,
channel_name: TestHelper.basicChannel.name,
channel_type: 'O',
post: `{"id": "71k8gz5ompbpfkrzaxzodffj8w", "create_at": 1508245311774, "update_at": 1508245311774, "edit_at": 0, "delete_at": 0, "is_pinned": false, "user_id": "${userId}", "channel_id": "${channelId}", "root_id": "", "parent_id": "", "original_id": "", "message": "Unit Test", "type": "", "props": {}, "hashtags": "", "pending_post_id": "t36kso9nwtdhbm8dbkd6g4eeby: 1508245311749"}`,
sender_name: TestHelper.basicUser.username,
team_id: TestHelper.basicTeam.id,
},
broadcast: {
omit_users: null,
user_id: userId,
channel_id: channelId,
team_id: '',
},
seq: 2,
});
mockServer.emit('message', message);
const entities = store.getState().entities;
const statuses = entities.users.statuses;
assert.equal(statuses[userId], General.DND);
});
it('Websocket Handle Post Edited', async () => {
const post = {id: '71k8gz5ompbpfkrzaxzodffj8w'};
mockServer.emit('message', JSON.stringify({event: WebsocketEvents.POST_EDITED, data: {post: `{"id": "71k8gz5ompbpfkrzaxzodffj8w","create_at": 1508245311774,"update_at": 1585236976007,"edit_at": 1585236976007,"delete_at": 0,"is_pinned": false,"user_id": "${TestHelper.basicUser.id}","channel_id": "${TestHelper.basicChannel.id}","root_id": "","parent_id": "","original_id": "","message": "Unit Test (edited)","type": "","props": {},"hashtags": "","pending_post_id": ""}`}, broadcast: {omit_users: null, user_id: '', channel_id: '18k9ffsuci8xxm7ak68zfdyrce', team_id: ''}, seq: 2}));
await TestHelper.wait(300);
const {posts} = store.getState().entities.posts;
assert.ok(posts);
assert.ok(posts[post.id]);
assert.ok(posts[post.id].message.indexOf('(edited)') > -1);
});
it('Websocket Handle Post Deleted', async () => {
const post = TestHelper.fakePost();
post.channel_id = TestHelper.basicChannel.id;
post.id = '71k8gz5ompbpfkrzaxzodffj8w';
store.dispatch(PostActions.receivedPost(post));
mockServer.emit('message', JSON.stringify({event: WebsocketEvents.POST_DELETED, data: {post: `{"id": "71k8gz5ompbpfkrzaxzodffj8w","create_at": 1508245311774,"update_at": 1508247709215,"edit_at": 1508247709215,"delete_at": 0,"is_pinned": false,"user_id": "${TestHelper.basicUser.id}","channel_id": "${post.channel_id}","root_id": "","parent_id": "","original_id": "","message": "Unit Test","type": "","props": {},"hashtags": "","pending_post_id": ""}`}, broadcast: {omit_users: null, user_id: '', channel_id: '18k9ffsuci8xxm7ak68zfdyrce', team_id: ''}, seq: 7}));
const entities = store.getState().entities;
const {posts} = entities.posts;
assert.strictEqual(posts[post.id].state, Posts.POST_DELETED);
});
it('Websocket handle Post Unread', async () => {
const teamId = TestHelper.generateId();
const channelId = TestHelper.generateId();
const userId = TestHelper.generateId();
store = await configureStore({
entities: {
channels: {
channels: {
[channelId]: {id: channelId},
},
myMembers: {
[channelId]: {msg_count: 10, mention_count: 0, last_viewed_at: 0},
},
},
teams: {
myMembers: {
[teamId]: {msg_count: 10, mention_count: 0},
},
},
},
});
await store.dispatch(Actions.init({websocketUrl: Client4.getUrl().replace(/^http:/, 'ws:')}));
mockServer.emit('message', JSON.stringify({
event: WebsocketEvents.POST_UNREAD,
data: {
last_viewed_at: 25,
msg_count: 3,
mention_count: 2,
delta_msg: 7,
},
broadcast: {omit_users: null, user_id: userId, channel_id: channelId, team_id: teamId},
seq: 7,
}));
const state = store.getState();
assert.equal(state.entities.channels.manuallyUnread[channelId], true);
assert.equal(state.entities.channels.myMembers[channelId].msg_count, 3);
assert.equal(state.entities.channels.myMembers[channelId].mention_count, 2);
assert.equal(state.entities.channels.myMembers[channelId].last_viewed_at, 25);
assert.equal(state.entities.teams.myMembers[teamId].msg_count, 3);
assert.equal(state.entities.teams.myMembers[teamId].mention_count, 2);
});
it('Websocket handle Post Unread When marked on the same client', async () => {
const teamId = TestHelper.generateId();
const channelId = TestHelper.generateId();
const userId = TestHelper.generateId();
store = await configureStore({
entities: {
channels: {
channels: {
[channelId]: {id: channelId},
},
myMembers: {
[channelId]: {msg_count: 5, mention_count: 4, last_viewed_at: 14},
},
manuallyUnread: {
[channelId]: true,
},
},
teams: {
myMembers: {
[teamId]: {msg_count: 5, mention_count: 4},
},
},
},
});
await store.dispatch(Actions.init({websocketUrl: Client4.getUrl().replace(/^http:/, 'ws:')}));
mockServer.emit('message', JSON.stringify({
event: WebsocketEvents.POST_UNREAD,
data: {
last_viewed_at: 25,
msg_count: 5,
mention_count: 4,
delta_msg: 1,
},
broadcast: {omit_users: null, user_id: userId, channel_id: channelId, team_id: teamId},
seq: 17,
}));
const state = store.getState();
assert.equal(state.entities.channels.manuallyUnread[channelId], true);
assert.equal(state.entities.channels.myMembers[channelId].msg_count, 5);
assert.equal(state.entities.channels.myMembers[channelId].mention_count, 4);
assert.equal(state.entities.channels.myMembers[channelId].last_viewed_at, 14);
assert.equal(state.entities.teams.myMembers[teamId].msg_count, 5);
assert.equal(state.entities.teams.myMembers[teamId].mention_count, 4);
});
it('Websocket Handle Reaction Added to Post', async () => {
const emoji = '+1';
const post = {id: 'w7yo9377zbfi9mgiq5gbfpn3ha'};
mockServer.emit('message', JSON.stringify({event: WebsocketEvents.REACTION_ADDED, data: {reaction: `{"user_id":"${TestHelper.basicUser.id}","post_id":"w7yo9377zbfi9mgiq5gbfpn3ha","emoji_name":"${emoji}","create_at":1508249125852}`}, broadcast: {omit_users: null, user_id: '', channel_id: TestHelper.basicChannel.id, team_id: ''}, seq: 12}));
await TestHelper.wait(300);
const nextEntities = store.getState().entities;
const {reactions} = nextEntities.posts;
const reactionsForPost = reactions[post.id];
assert.ok(reactionsForPost.hasOwnProperty(`${TestHelper.basicUser.id}-${emoji}`));
});
// If we move this test lower it will fail cause of a permissions issue
it('Websocket handle team updated', async () => {
const team = {id: '55pfercbm7bsmd11p5cjpgsbwr'};
mockServer.emit('message', JSON.stringify({event: WebsocketEvents.UPDATE_TEAM, data: {team: `{"id":"55pfercbm7bsmd11p5cjpgsbwr","create_at":1495553950859,"update_at":1508250370054,"delete_at":0,"display_name":"${TestHelper.basicTeam.display_name}","name":"${TestHelper.basicTeam.name}","description":"description","email":"","type":"O","company_name":"","allowed_domains":"","invite_id":"m93f54fu5bfntewp8ctwonw19w","allow_open_invite":true}`}, broadcast: {omit_users: null, user_id: '', channel_id: '', team_id: ''}, seq: 26}));
await TestHelper.wait(300);
const entities = store.getState().entities;
const {teams} = entities.teams;
const updated = teams[team.id];
assert.ok(updated);
assert.strictEqual(updated.allow_open_invite, true);
});
it('Websocket handle team patched', async () => {
const team = {id: '55pfercbm7bsmd11p5cjpgsbwr'};
mockServer.emit('message', JSON.stringify({event: WebsocketEvents.UPDATE_TEAM, data: {team: `{"id":"55pfercbm7bsmd11p5cjpgsbwr","create_at":1495553950859,"update_at":1508250370054,"delete_at":0,"display_name":"${TestHelper.basicTeam.display_name}","name":"${TestHelper.basicTeam.name}","description":"description","email":"","type":"O","company_name":"","allowed_domains":"","invite_id":"m93f54fu5bfntewp8ctwonw19w","allow_open_invite":true}`}, broadcast: {omit_users: null, user_id: '', channel_id: '', team_id: ''}, seq: 26}));
await TestHelper.wait(300);
const entities = store.getState().entities;
const {teams} = entities.teams;
const updated = teams[team.id];
assert.ok(updated);
assert.strictEqual(updated.allow_open_invite, true);
});
it('WebSocket Leave Team', async () => {
const team = TestHelper.basicTeam;
store.dispatch(batchActions([
{type: UserTypes.RECEIVED_ME, data: TestHelper.basicUser},
{type: TeamTypes.RECEIVED_TEAM, data: TestHelper.basicTeam},
{type: TeamTypes.RECEIVED_MY_TEAM_MEMBER, data: TestHelper.basicTeamMember},
]));
mockServer.emit('message', JSON.stringify({event: WebsocketEvents.LEAVE_TEAM, data: {team_id: team.id, user_id: TestHelper.basicUser.id}, broadcast: {omit_users: null, user_id: '', channel_id: '', team_id: team.id}, seq: 35}));
const {myMembers} = store.getState().entities.teams;
assert.ifError(myMembers[team.id]);
});
it('Websocket Handle User Added', async () => {
const user = {...TestHelper.fakeUser(), id: TestHelper.generateId()};
store.dispatch({type: UserTypes.RECEIVED_PROFILE_IN_CHANNEL, data: {id: TestHelper.basicChannel.id, user_id: user.id}});
mockServer.emit('message', JSON.stringify({event: WebsocketEvents.USER_ADDED, data: {team_id: TestHelper.basicTeam.id, user_id: user.id}, broadcast: {omit_users: null, user_id: '', channel_id: TestHelper.basicChannel.id, team_id: ''}, seq: 42}));
const entities = store.getState().entities;
const profilesInChannel = entities.users.profilesInChannel;
assert.ok(profilesInChannel[TestHelper.basicChannel.id].has(user.id));
});
it('Websocket Handle User Removed', async () => {
const user = {...TestHelper.fakeUser(), id: TestHelper.generateId()};
store.dispatch({type: UserTypes.RECEIVED_PROFILE_NOT_IN_CHANNEL, data: {id: TestHelper.basicChannel.id, user_id: user.id}});
mockServer.emit('message', JSON.stringify({event: WebsocketEvents.USER_REMOVED, data: {remover_id: TestHelper.basicUser.id, user_id: user.id}, broadcast: {omit_users: null, user_id: '', channel_id: TestHelper.basicChannel.id, team_id: ''}, seq: 42}));
const state = store.getState();
const entities = state.entities;
const profilesNotInChannel = entities.users.profilesNotInChannel;
assert.ok(profilesNotInChannel[TestHelper.basicChannel.id].has(user.id));
});
it('Websocket Handle User Removed when Current is Guest', async () => {
const basicGuestUser = TestHelper.fakeUserWithId();
basicGuestUser.roles = 'system_guest';
const user = {...TestHelper.fakeUser(), id: TestHelper.generateId()};
// add user first
store.dispatch({type: UserTypes.RECEIVED_PROFILE_IN_CHANNEL, data: {id: TestHelper.basicChannel.id, user_id: user.id}});
mockServer.emit('message', JSON.stringify({event: WebsocketEvents.USER_ADDED, data: {team_id: TestHelper.basicTeam.id, user_id: user.id}, broadcast: {omit_users: null, user_id: '', channel_id: TestHelper.basicChannel.id, team_id: ''}, seq: 42}));
assert.ok(store.getState().entities.users.profilesInChannel[TestHelper.basicChannel.id].has(user.id));
// remove user
store.dispatch({type: UserTypes.RECEIVED_PROFILE_NOT_IN_CHANNEL, data: {id: TestHelper.basicChannel.id, user_id: user.id}});
mockServer.emit('message', JSON.stringify({event: WebsocketEvents.USER_REMOVED, data: {remover_id: basicGuestUser.id, user_id: user.id}, broadcast: {omit_users: null, user_id: '', channel_id: TestHelper.basicChannel.id, team_id: ''}, seq: 42}));
assert.ok(!store.getState().entities.users.profilesInChannel[TestHelper.basicChannel.id].has(user.id));
});
it('Websocket Handle User Updated', async () => {
const user = {...TestHelper.fakeUser(), id: TestHelper.generateId()};
mockServer.emit('message', JSON.stringify({event: WebsocketEvents.USER_UPDATED, data: {user: {id: user.id, create_at: 1495570297229, update_at: 1508253268652, delete_at: 0, username: 'tim', auth_data: '', auth_service: '', email: 'tim@bladekick.com', nickname: '', first_name: 'tester4', last_name: '', position: '', roles: 'system_user', locale: 'en'}}, broadcast: {omit_users: null, user_id: '', channel_id: '', team_id: ''}, seq: 53}));
store.subscribe(() => {
const state = store.getState();
const entities = state.entities;
const profiles = entities.users.profiles;
assert.strictEqual(profiles[user.id].first_name, 'tester4');
});
});
it('Websocket Handle Channel Member Updated', async () => {
const channelMember = TestHelper.basicChannelMember;
const mockStore = configureMockStore([thunk]);
const st = mockStore(initial_state);
await st.dispatch(Actions.init({websocketUrl: Client4.getUrl().replace(/^http:/, 'ws:')}));
channelMember.roles = 'channel_user channel_admin';
const rolesToLoad = channelMember.roles.split(' ');
nock(Client4.getRolesRoute()).
post('/names', JSON.stringify(rolesToLoad)).
reply(200, rolesToLoad);
mockServer.emit('message', JSON.stringify({
event: WebsocketEvents.CHANNEL_MEMBER_UPDATED,
data: {
channelMember: JSON.stringify(channelMember),
},
}));
await TestHelper.wait(300);
const storeActions = st.getActions();
const batch = storeActions.find((a) => a.type === 'BATCH_WS_CHANNEL_MEMBER_UPDATE');
expect(batch).not.toBeNull();
const memberAction = batch.payload.find((a) => a.type === ChannelTypes.RECEIVED_MY_CHANNEL_MEMBER);
expect(memberAction).not.toBeNull();
const rolesActions = batch.payload.find((a) => a.type === RoleTypes.RECEIVED_ROLES);
expect(rolesActions).not.toBeNull();
expect(rolesActions.data).toEqual(rolesToLoad);
});
it('Websocket Handle Channel Created', async () => {
const channelId = TestHelper.basicChannel.id;
const channel = {id: channelId, display_name: 'test', name: TestHelper.basicChannel.name};
await store.dispatch(Actions.init({websocketUrl: Client4.getUrl().replace(/^http:/, 'ws:')}));
store.dispatch({type: ChannelTypes.RECEIVED_CHANNEL, data: channel});
mockServer.emit('message', JSON.stringify({event: WebsocketEvents.CHANNEL_CREATED, data: {channel_id: channelId, team_id: TestHelper.basicTeam.id}, broadcast: {omit_users: null, user_id: 't36kso9nwtdhbm8dbkd6g4eeby', channel_id: '', team_id: ''}, seq: 57}));
await TestHelper.wait(300);
const state = store.getState();
const entities = state.entities;
const {channels} = entities.channels;
assert.ok(channels[channel.id]);
});
it('Websocket Handle Channel Updated', async () => {
const channelName = 'Test name';
const channelId = TestHelper.basicChannel.id;
mockServer.emit('message', JSON.stringify({event: WebsocketEvents.CHANNEL_UPDATED, data: {channel: `{"id":"${channelId}","create_at":1508253647983,"update_at":1508254198797,"delete_at":0,"team_id":"55pfercbm7bsmd11p5cjpgsbwr","type":"O","display_name":"${channelName}","name":"${TestHelper.basicChannel.name}","header":"header","purpose":"","last_post_at":1508253648004,"total_msg_count":0,"extra_update_at":1508253648001,"creator_id":"${TestHelper.basicUser.id}"}`}, broadcast: {omit_users: null, user_id: '', channel_id: channelId, team_id: ''}, seq: 62}));
await TestHelper.wait(300);
const state = store.getState();
const entities = state.entities;
const {channels} = entities.channels;
assert.strictEqual(channels[channelId].display_name, channelName);
});
it('Websocket Handle Channel Deleted', async () => {
const time = Date.now();
await store.dispatch(TeamActions.selectTeam(TestHelper.basicTeam));
await store.dispatch(ChannelActions.selectChannel(TestHelper.basicChannel.id));
store.dispatch({type: ChannelTypes.RECEIVED_CHANNEL, data: {id: TestHelper.generateId(), name: General.DEFAULT_CHANNEL, team_id: TestHelper.basicTeam.id, display_name: General.DEFAULT_CHANNEL}});
store.dispatch({type: ChannelTypes.RECEIVED_CHANNEL, data: TestHelper.basicChannel});
nock(Client4.getUserRoute('me')).
get(`/teams/${TestHelper.basicTeam.id}/channels/members`).
reply(201, [{user_id: TestHelper.basicUser.id, channel_id: TestHelper.basicChannel.id}]);
mockServer.emit('message', JSON.stringify({
event: WebsocketEvents.CHANNEL_DELETED,
data: {
channel_id: TestHelper.basicChannel.id,
delete_at: time,
},
broadcast: {
omit_users: null,
user_id: '',
channel_id: '',
team_id: TestHelper.basicTeam.id,
},
seq: 68,
}));
await TestHelper.wait(300);
const state = store.getState();
const entities = state.entities;
const {channels, currentChannelId} = entities.channels;
assert.ok(channels[currentChannelId].name === General.DEFAULT_CHANNEL);
});
it('Websocket Handle Channel Unarchive', async () => {
await store.dispatch(TeamActions.selectTeam(TestHelper.basicTeam));
await store.dispatch(ChannelActions.selectChannel(TestHelper.basicChannel.id));
store.dispatch({type: ChannelTypes.RECEIVED_CHANNEL, data: {id: TestHelper.generateId(), name: General.DEFAULT_CHANNEL, team_id: TestHelper.basicTeam.id, display_name: General.DEFAULT_CHANNEL}});
store.dispatch({type: ChannelTypes.RECEIVED_CHANNEL, data: TestHelper.basicChannel});
nock(Client4.getUserRoute('me')).
get(`/teams/${TestHelper.basicTeam.id}/channels/members`).
reply(201, [{user_id: TestHelper.basicUser.id, channel_id: TestHelper.basicChannel.id}]);
mockServer.emit('message', JSON.stringify({event: WebsocketEvents.CHANNEL_UNARCHIVE, data: {channel_id: TestHelper.basicChannel.id}, broadcast: {omit_users: null, user_id: '', channel_id: '', team_id: TestHelper.basicTeam.id}, seq: 68}));
await TestHelper.wait(300);
const state = store.getState();
const entities = state.entities;
const {channels, currentChannelId} = entities.channels;
assert.ok(channels[currentChannelId].delete_at === 0);
});
it('Websocket Handle Direct Channel', async () => {
const channel = {id: TestHelper.generateId(), name: TestHelper.basicUser.id + '__' + TestHelper.generateId(), type: 'D'};
nock(Client4.getChannelsRoute()).
get(`/${channel.id}/members/me`).
reply(201, {user_id: TestHelper.basicUser.id, channel_id: channel.id});
mockServer.emit('message', JSON.stringify({event: WebsocketEvents.DIRECT_ADDED, data: {teammate_id: 'btaxe5msnpnqurayosn5p8twuw'}, broadcast: {omit_users: null, user_id: '', channel_id: channel.id, team_id: ''}, seq: 2}));
store.dispatch({type: ChannelTypes.RECEIVED_CHANNEL, data: channel});
await TestHelper.wait(300);
const {channels} = store.getState().entities.channels;
assert.ok(Object.keys(channels).length);
});
it('Websocket handle user added to team', async () => {
const team = {id: TestHelper.generateId()};
nock(Client4.getBaseRoute()).
get(`/teams/${team.id}`).
reply(200, team);
nock(Client4.getBaseRoute()).
get('/users/me/teams/unread').
reply(200, [{team_id: team.id, msg_count: 0, mention_count: 0}]);
mockServer.emit('message', JSON.stringify({event: WebsocketEvents.ADDED_TO_TEAM, data: {team_id: team.id, user_id: TestHelper.basicUser.id}, broadcast: {omit_users: null, user_id: TestHelper.basicUser.id, channel_id: '', team_id: ''}, seq: 2}));
await TestHelper.wait(300);
const {teams, myMembers} = store.getState().entities.teams;
assert.ok(teams[team.id]);
assert.ok(myMembers[team.id]);
const member = myMembers[team.id];
assert.ok(member.hasOwnProperty('mention_count'));
});
it('Websocket handle emoji added', async () => {
const created = {id: '1mmgakhhupfgfm8oug6pooc5no'};
mockServer.emit('message', JSON.stringify({event: WebsocketEvents.EMOJI_ADDED, data: {emoji: `{"id":"1mmgakhhupfgfm8oug6pooc5no","create_at":1508263941321,"update_at":1508263941321,"delete_at":0,"creator_id":"t36kso9nwtdhbm8dbkd6g4eeby","name":"${TestHelper.generateId()}"}`}, broadcast: {omit_users: null, user_id: '', channel_id: '', team_id: ''}, seq: 2}));
await TestHelper.wait(200);
const state = store.getState();
const emojis = state.entities.emojis.customEmoji;
assert.ok(emojis);
assert.ok(emojis[created.id]);
});
it('handle license changed', async () => {
mockServer.emit('message', JSON.stringify({event: WebsocketEvents.LICENSE_CHANGED, data: {license: {IsLicensed: 'true'}}}));
await TestHelper.wait(200);
const state = store.getState();
const license = state.entities.general.license;
assert.ok(license);
assert.ok(license.IsLicensed);
});
it('handle config changed', async () => {
mockServer.emit('message', JSON.stringify({event: WebsocketEvents.CONFIG_CHANGED, data: {config: {EnableCustomEmoji: 'true', EnableLinkPreviews: 'false'}}}));
await TestHelper.wait(200);
const state = store.getState();
const config = state.entities.general.config;
assert.ok(config);
assert.ok(config.EnableCustomEmoji === 'true');
assert.ok(config.EnableLinkPreviews === 'false');
});
it('handle open dialog', async () => {
mockServer.emit('message', JSON.stringify({event: WebsocketEvents.OPEN_DIALOG, data: {dialog: JSON.stringify({url: 'someurl', trigger_id: 'sometriggerid', dialog: {}})}}));
await TestHelper.wait(200);
const state = store.getState();
const dialog = state.entities.integrations.dialog;
assert.ok(dialog);
assert.ok(dialog.url === 'someurl');
assert.ok(dialog.trigger_id === 'sometriggerid');
assert.ok(dialog.dialog);
});
});
describe('Actions.Websocket doReconnect', () => {
const mockStore = configureMockStore([thunk]);
const me = TestHelper.fakeUserWithId();
const team = TestHelper.fakeTeamWithId();
const teamMember = TestHelper.fakeTeamMember(me.id, team.id);
const channel1 = TestHelper.fakeChannelWithId(team.id);
const channel2 = TestHelper.fakeChannelWithId(team.id);
const cMember1 = TestHelper.fakeChannelMember(me.id, channel1.id);
const cMember2 = TestHelper.fakeChannelMember(me.id, channel2.id);
const currentTeamId = team.id;
const currentUserId = me.id;
const currentChannelId = channel1.id;
const initialState = {
entities: {
general: {
config: {},
},
teams: {
currentTeamId,
myMembers: {
[currentTeamId]: teamMember,
},
teams: {
[currentTeamId]: team,
},
},
channels: {
currentChannelId,
channels: {
currentChannelId: channel1,
},
},
users: {
currentUserId,
profiles: {
[me.id]: me,
},
},
preferences: {
myPreferences: {},
},
posts: {
posts: {},
postsInChannel: {},
},
},
websocket: {
connected: false,
lastConnectAt: 0,
lastDisconnectAt: 0,
},
};
beforeAll(async () => {
return TestHelper.initBasic(Client4);
});
beforeEach(() => {
nock(Client4.getBaseRoute()).
get('/users/me').
reply(200, me);
nock(Client4.getUserRoute('me')).
get('/teams').
reply(200, [team]);
nock(Client4.getUserRoute('me')).
get('/teams/unread').
reply(200, [{id: team.id, msg_count: 0, mention_count: 0}]);
nock(Client4.getBaseRoute()).
get('/users/me/preferences').
reply(200, []);
nock(Client4.getUserRoute('me')).
get(`/teams/${team.id}/channels/members`).
reply(200, [cMember1, cMember2]);
nock(Client4.getChannelRoute(channel1.id)).
get(`/posts?page=0&per_page=${Posts.POST_CHUNK_SIZE}`).
reply(200, {
posts: {
post1: {id: 'post1', create_at: 0, message: 'hey'},
},
order: ['post1'],
});
});
afterAll(async () => {
Actions.close()();
await TestHelper.tearDown();
});
it('handle doReconnect', async () => {
const state = {...initialState};
const testStore = await mockStore(state);
const timestamp = 1000;
const expectedActions = [
GeneralTypes.WEBSOCKET_SUCCESS,
'BATCH_WS_RECONNECT',
'BATCH_GET_POSTS',
];
mockConfigRequest();
mockTeamMemberRequest([teamMember]);
mockChanelsRequest(team.id, [channel1, channel2]);
let rolesToLoad = Array.from(new Set(me.roles.split(' ').
concat(teamMember.roles.split(' '))));
mockRolesRequest(rolesToLoad);
rolesToLoad = Array.from(new Set(cMember1.roles.split(' ').
concat(cMember2.roles.split(' '))));
mockRolesRequest(rolesToLoad);
await testStore.dispatch(Actions.doReconnect(timestamp));
await TestHelper.wait(300);
const actionTypes = testStore.getActions().map((a) => a.type);
expect(actionTypes).toEqual(expectedActions);
});
it('handle doReconnect after the current channel was archived or the user left it', async () => {
const state = {
...initialState,
entities: {
...initialState.entities,
channels: {
...initialState.entities.channels,
currentChannelId: 'channel-3',
},
},
};
const testStore = await mockStore(state);
const timestamp = 1000;
const expectedActions = [
GeneralTypes.WEBSOCKET_SUCCESS,
'BATCH_WS_RECONNECT',
];
const expectedMissingActions = [
'BATCH_GET_POSTS',
];
mockConfigRequest();
mockTeamMemberRequest([teamMember]);
mockChanelsRequest(team.id, [channel1, channel2]);
let rolesToLoad = Array.from(new Set(me.roles.split(' ').
concat(teamMember.roles.split(' '))));
mockRolesRequest(rolesToLoad);
rolesToLoad = Array.from(new Set(cMember1.roles.split(' ').
concat(cMember2.roles.split(' '))));
mockRolesRequest(rolesToLoad);
await testStore.dispatch(Actions.doReconnect(timestamp));
await TestHelper.wait(300);
const actions = testStore.getActions().map((a) => a.type);
expect(actions).toEqual(expect.arrayContaining(expectedActions));
expect(actions).not.toEqual(expect.arrayContaining(expectedMissingActions));
});
it('handle doReconnect after the current channel was archived and setting is on', async () => {
const archived = {
...channel1,
delete_at: 123,
};
const state = {
...initialState,
channels: {
currentChannelId,
channels: {
currentChannelId: archived,
},
},
};
const testStore = await mockStore(state);
const timestamp = 1000;
const expectedActions = [
GeneralTypes.WEBSOCKET_SUCCESS,
'BATCH_WS_RECONNECT',
];
mockConfigRequest({ExperimentalViewArchivedChannels: 'true'});
mockTeamMemberRequest([teamMember]);
mockChanelsRequest(team.id, [archived, channel2]);
let rolesToLoad = Array.from(new Set(me.roles.split(' ').
concat(teamMember.roles.split(' '))));
mockRolesRequest(rolesToLoad);
rolesToLoad = Array.from(new Set(cMember1.roles.split(' ').
concat(cMember2.roles.split(' '))));
mockRolesRequest(rolesToLoad);
await testStore.dispatch(Actions.doReconnect(timestamp));
await TestHelper.wait(300);
const actions = testStore.getActions().map((a) => a.type);
expect(actions).toEqual(expect.arrayContaining(expectedActions));
});
it('handle doReconnect after the current channel was archived and setting is off', async () => {
const archived = {
...channel1,
delete_at: 123,
};
const state = {
...initialState,
channels: {
currentChannelId,
channels: {
currentChannelId: archived,
},
},
};
const testStore = await mockStore(state);
const timestamp = 1000;
const expectedActions = [
GeneralTypes.WEBSOCKET_SUCCESS,
'BATCH_WS_RECONNECT',
];
const expectedMissingActions = [
'BATCH_GET_POSTS',
];
mockConfigRequest({ExperimentalViewArchivedChannels: 'false'});
mockTeamMemberRequest([teamMember]);
mockChanelsRequest(team.id, [archived, channel2]);
let rolesToLoad = Array.from(new Set(me.roles.split(' ').
concat(teamMember.roles.split(' '))));
mockRolesRequest(rolesToLoad);
rolesToLoad = Array.from(new Set(cMember1.roles.split(' ').
concat(cMember2.roles.split(' '))));
mockRolesRequest(rolesToLoad);
await testStore.dispatch(Actions.doReconnect(timestamp));
await TestHelper.wait(300);
const actions = testStore.getActions().map((a) => a.type);
expect(actions).toEqual(expectedActions);
expect(actions).not.toEqual(expect.arrayContaining(expectedMissingActions));
});
it('handle doReconnect after user left current team', async () => {
const state = {...initialState};
state.entities.teams.myMembers = {};
const testStore = await mockStore(state);
const timestamp = 1000;
const expectedActions = [
GeneralTypes.WEBSOCKET_SUCCESS,
'BATCH_WS_LEAVE_TEAM',
'BATCH_WS_RECONNECT',
];
const expectedMissingActions = [
'BATCH_GET_POSTS',
];
mockConfigRequest();
mockTeamMemberRequest([]);
mockChanelsRequest(team.id, [channel1, channel2]);
let rolesToLoad = me.roles.split(' ');
mockRolesRequest(rolesToLoad);
rolesToLoad = Array.from(new Set(cMember1.roles.split(' ').
concat(cMember2.roles.split(' '))));
mockRolesRequest(rolesToLoad);
await testStore.dispatch(Actions.doReconnect(timestamp));
await TestHelper.wait(300);
const actions = testStore.getActions().map((a) => a.type);
expect(actions).toEqual(expectedActions);
expect(actions).not.toEqual(expect.arrayContaining(expectedMissingActions));
});
});
describe('Actions.Websocket notVisibleUsersActions', () => {
configureMockStore([thunk]);
const me = TestHelper.fakeUserWithId();
const user = TestHelper.fakeUserWithId();
const user2 = TestHelper.fakeUserWithId();
const user3 = TestHelper.fakeUserWithId();
const user4 = TestHelper.fakeUserWithId();
const user5 = TestHelper.fakeUserWithId();
it('should do nothing if the known users and the profiles list are the same', async () => {
const profiles = {
[me.id]: me,
[user.id]: user,
[user2.id]: user2,
[user3.id]: user3,
};
Client4.serverVersion = '5.23.0';
const state = {
entities: {
users: {
currentUserId: me.id,
profiles,
},
},
};
mockGetKnownUsersRequest([user.id, user2.id, user3.id]);
const actions = await Actions.notVisibleUsersActions(state);
expect(actions.length).toEqual(0);
});
it('should do nothing if there are known users in my memberships but not in the profiles list', async () => {
const profiles = {
[me.id]: me,
[user3.id]: user3,
};
Client4.serverVersion = '5.23.0';
const state = {
entities: {
users: {
currentUserId: me.id,
profiles,
},
},
};
mockGetKnownUsersRequest([user.id, user2.id, user3.id]);
const actions = await Actions.notVisibleUsersActions(state);
expect(actions.length).toEqual(0);
});
it('should remove the users if there are unknown users in the profiles list', async () => {
const profiles = {
[me.id]: me,
[user.id]: user,
[user2.id]: user2,
[user3.id]: user3,
[user4.id]: user4,
[user5.id]: user5,
};
Client4.serverVersion = '5.23.0';
const state = {
entities: {
users: {
currentUserId: me.id,
profiles,
},
},
};
mockGetKnownUsersRequest([user.id, user3.id]);
const expectedAction = [
{type: UserTypes.PROFILE_NO_LONGER_VISIBLE, data: {user_id: user2.id}},
{type: UserTypes.PROFILE_NO_LONGER_VISIBLE, data: {user_id: user4.id}},
{type: UserTypes.PROFILE_NO_LONGER_VISIBLE, data: {user_id: user5.id}},
];
const actions = await Actions.notVisibleUsersActions(state);
expect(actions.length).toEqual(3);
expect(actions).toEqual(expectedAction);
});
it('should do nothing if the server version is less than 5.23', async () => {
const profiles = {
[me.id]: me,
[user.id]: user,
[user2.id]: user2,
[user3.id]: user3,
[user4.id]: user4,
[user5.id]: user5,
};
Client4.serverVersion = '5.22.0';
const state = {
entities: {
users: {
currentUserId: me.id,
profiles,
},
},
};
mockGetKnownUsersRequest([user.id, user3.id]);
const actions = await Actions.notVisibleUsersActions(state);
expect(actions.length).toEqual(0);
});
});
describe('Actions.Websocket handleUserTypingEvent', () => {
const mockStore = configureMockStore([thunk]);
const currentUserId = 'user-id';
const otherUserId = 'other-user-id';
const currentChannelId = 'channel-id';
const otherChannelId = 'other-channel-id';
const initialState = {
entities: {
general: {
config: {},
},
channels: {
currentChannelId,
channels: {
currentChannelId: {
id: currentChannelId,
name: 'channel',
},
},
},
users: {
currentUserId,
profiles: {
[currentUserId]: {},
[otherUserId]: {},
},
statuses: {
[currentUserId]: General.ONLINE,
[otherUserId]: General.OFFLINE,
},
},
preferences: {
myPreferences: {},
},
},
};
it('dispatches actions for current channel if other user is typing', async () => {
const state = {...initialState};
const testStore = await mockStore(state);
const msg = {broadcast: {channel_id: currentChannelId}, data: {parent_id: 'parent-id', user_id: otherUserId}};
nock(Client4.getUsersRoute()).
post('/status/ids', JSON.stringify([otherUserId])).
reply(200, ['away']);
const expectedActionsTypes = [
WebsocketEvents.TYPING,
UserTypes.RECEIVED_STATUSES,
];
await testStore.dispatch(Actions.handleUserTypingEvent(msg));
await TestHelper.wait(300);
const actionTypes = testStore.getActions().map((action) => action.type);
expect(actionTypes).toEqual(expectedActionsTypes);
});
it('does not dispatch actions for non current channel', async () => {
const state = {...initialState};
const testStore = await mockStore(state);
const msg = {broadcast: {channel_id: otherChannelId}, data: {parent_id: 'parent-id', user_id: otherUserId}};
const expectedActionsTypes = [];
await testStore.dispatch(Actions.handleUserTypingEvent(msg));
const actionTypes = testStore.getActions().map((action) => action.type);
expect(actionTypes).toEqual(expectedActionsTypes);
});
});