// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. /* eslint-disable max-lines */ import {Database, Q} from '@nozbe/watermelondb'; import {of as of$} from 'rxjs'; import {General, Permissions} from '@constants'; import {MM_TABLES} from '@constants/database'; import DatabaseManager from '@database/manager'; import ServerDataOperator from '@database/operator/server_data_operator'; import EphemeralStore from '@store/ephemeral_store'; import TestHelper from '@test/test_helper'; import {hasPermission} from '@utils/role'; import {prepareChannels, prepareMissingChannelsForAllTeams, prepareMyChannelsForTeam, prepareDeleteChannel, prepareDeleteBookmarks, queryAllChannels, queryAllChannelsForTeam, queryAllChannelsInfo, queryAllChannelsInfoForTeam, queryAllMyChannel, queryAllMyChannelsForTeam, queryAllUnreadDMsAndGMsIds, queryChannelsById, queryChannelsByTypes, queryUserChannelsByTypes, queryTeamDefaultChannel, queryMyChannelsByTeam, queryUsersOnChannel, getMyChannel, observeMyChannel, observeMyChannelRoles, getChannelById, observeChannel, getChannelByName, getDefaultChannelForTeam, getCurrentChannel, getCurrentChannelInfo, observeCurrentChannel, getChannelInfo, deleteChannelMembership, addChannelMembership, getMembersCountByChannelsId, queryAllMyChannelSettings, queryMyChannelSettingsByIds, queryMyChannelUnreads, queryMyRecentChannels, observeChannelInfo, observeAllMyChannelNotifyProps, observeNotifyPropsByChannels, observeMyChannelMentionCount, observeArchiveChannelsByTerm, observeChannelsByLastPostAt, observeChannelSettings, observeDirectChannelsByTerm, observeIsMutedSetting, observeJoinedChannelsByTerm, observeNotDirectChannelsByTerm, queryChannelMembers, queryChannelsForAutocomplete, observeChannelMembers, queryMyChannelsByChannelIds, queryMyChannelsWithAutotranslation, observeChannelAutotranslation, observeIsChannelAutotranslated, } from './channel'; import {queryRoles} from './role'; import {getCurrentChannelId, observeCurrentChannelId, observeCurrentUserId} from './system'; import {observeTeammateNameDisplay} from './user'; import type ChannelModel from '@typings/database/models/servers/channel'; import type ChannelBookmarkModel from '@typings/database/models/servers/channel_bookmark'; import type ChannelInfoModel from '@typings/database/models/servers/channel_info'; import type ChannelMembershipModel from '@typings/database/models/servers/channel_membership'; jest.mock('./role'); jest.mock('./system', () => { const actual = jest.requireActual('./system'); return { ...actual, getCurrentChannelId: jest.fn(), observeCurrentChannelId: jest.fn(), observeCurrentUserId: jest.fn(), }; }); jest.mock('./user'); jest.mock('@utils/role'); describe('prepareChannels', () => { let operator: ServerDataOperator; beforeEach(() => { operator = { handleChannel: jest.fn(), handleChannelInfo: jest.fn(), handleChannelMembership: jest.fn(), handleMyChannel: jest.fn(), handleMyChannelSettings: jest.fn(), } as unknown as ServerDataOperator; }); it('should prepare channels, channelInfos, channelMemberships, memberships, and myChannelSettings', () => { const channels = [ TestHelper.fakeChannel({id: 'channel1'}), TestHelper.fakeChannel({id: 'channel2'}), ]; const channelInfos = [ TestHelper.fakeChannelInfo({id: 'channel1'}), TestHelper.fakeChannelInfo({id: 'channel2'}), ]; const channelMemberships = [ TestHelper.fakeChannelMember({channel_id: 'channel1', user_id: 'user1'}), TestHelper.fakeChannelMember({channel_id: 'channel2', user_id: 'user2'}), ]; const memberships = [ TestHelper.fakeChannelMember({channel_id: 'channel1', user_id: 'user1'}), TestHelper.fakeChannelMember({channel_id: 'channel2', user_id: 'user2'}), ]; const channelRecords = Promise.resolve([]); const channelInfoRecords = Promise.resolve([]); const membershipRecords = Promise.resolve([]); const myChannelRecords = Promise.resolve([]); const myChannelSettingsRecords = Promise.resolve([]); jest.mocked(operator.handleChannel).mockReturnValue(channelRecords); jest.mocked(operator.handleChannelInfo).mockReturnValue(channelInfoRecords); jest.mocked(operator.handleChannelMembership).mockReturnValue(membershipRecords); jest.mocked(operator.handleMyChannel).mockReturnValue(myChannelRecords); jest.mocked(operator.handleMyChannelSettings).mockReturnValue(myChannelSettingsRecords); const result = prepareChannels(operator, channels, channelInfos, channelMemberships, memberships, true); expect(result).toEqual([channelRecords, channelInfoRecords, membershipRecords, myChannelRecords, myChannelSettingsRecords]); expect(operator.handleChannel).toHaveBeenCalledWith({channels, prepareRecordsOnly: true}); expect(operator.handleChannelInfo).toHaveBeenCalledWith({channelInfos, prepareRecordsOnly: true}); expect(operator.handleChannelMembership).toHaveBeenCalledWith({channelMemberships, prepareRecordsOnly: true}); expect(operator.handleMyChannel).toHaveBeenCalledWith({channels, myChannels: memberships, prepareRecordsOnly: true, isCRTEnabled: true}); expect(operator.handleMyChannelSettings).toHaveBeenCalledWith({settings: memberships, prepareRecordsOnly: true}); }); it('should return an empty array if an error occurs', () => { jest.mocked(operator.handleChannel).mockImplementation(() => { throw new Error('Test error'); }); const result = prepareChannels(operator); expect(result).toEqual([]); }); }); describe('prepareMissingChannelsForAllTeams', () => { let operator: ServerDataOperator; beforeEach(() => { operator = { handleChannel: jest.fn(), handleChannelInfo: jest.fn(), handleChannelMembership: jest.fn(), handleMyChannel: jest.fn(), handleMyChannelSettings: jest.fn(), } as unknown as ServerDataOperator; }); it('should prepare missing channels for all teams', () => { const channels = [ TestHelper.fakeChannel({id: 'channel1', header: 'header1', purpose: 'purpose1', last_post_at: 123, last_root_post_at: 456}), TestHelper.fakeChannel({id: 'channel2', header: 'header2', purpose: 'purpose2', last_post_at: 789, last_root_post_at: 101112}), ]; const channelMembers = [ TestHelper.fakeChannelMember({channel_id: 'channel1', user_id: 'user1'}), TestHelper.fakeChannelMember({channel_id: 'channel2', user_id: 'user2'}), ]; const channelRecords = Promise.resolve([]); const channelInfoRecords = Promise.resolve([]); const membershipRecords = Promise.resolve([]); const myChannelRecords = Promise.resolve([]); const myChannelSettingsRecords = Promise.resolve([]); jest.mocked(operator.handleChannel).mockReturnValue(channelRecords); jest.mocked(operator.handleChannelInfo).mockReturnValue(channelInfoRecords); jest.mocked(operator.handleChannelMembership).mockReturnValue(membershipRecords); jest.mocked(operator.handleMyChannel).mockReturnValue(myChannelRecords); jest.mocked(operator.handleMyChannelSettings).mockReturnValue(myChannelSettingsRecords); const result = prepareMissingChannelsForAllTeams(operator, channels, channelMembers, true); expect(result).toEqual([channelRecords, channelInfoRecords, membershipRecords, myChannelRecords, myChannelSettingsRecords]); expect(operator.handleChannel).toHaveBeenCalledWith({channels, prepareRecordsOnly: true}); expect(operator.handleChannelInfo).toHaveBeenCalledWith({ channelInfos: [ {id: 'channel1', header: 'header1', purpose: 'purpose1', guest_count: 0, member_count: 0, pinned_post_count: 0, files_count: 0}, {id: 'channel2', header: 'header2', purpose: 'purpose2', guest_count: 0, member_count: 0, pinned_post_count: 0, files_count: 0}, ], prepareRecordsOnly: true, }); expect(operator.handleChannelMembership).toHaveBeenCalledWith({ channelMemberships: [ expect.objectContaining({channel_id: 'channel1', user_id: 'user1', id: 'channel1', last_post_at: 123, last_root_post_at: 456}), expect.objectContaining({channel_id: 'channel2', user_id: 'user2', id: 'channel2', last_post_at: 789, last_root_post_at: 101112}), ], prepareRecordsOnly: true, }); expect(operator.handleMyChannel).toHaveBeenCalledWith({ channels, myChannels: [ expect.objectContaining({channel_id: 'channel1', user_id: 'user1', id: 'channel1', last_post_at: 123, last_root_post_at: 456}), expect.objectContaining({channel_id: 'channel2', user_id: 'user2', id: 'channel2', last_post_at: 789, last_root_post_at: 101112}), ], prepareRecordsOnly: true, isCRTEnabled: true, }); expect(operator.handleMyChannelSettings).toHaveBeenCalledWith({ settings: [ expect.objectContaining({channel_id: 'channel1', user_id: 'user1', id: 'channel1', last_post_at: 123, last_root_post_at: 456}), expect.objectContaining({channel_id: 'channel2', user_id: 'user2', id: 'channel2', last_post_at: 789, last_root_post_at: 101112}), ], prepareRecordsOnly: true, }); }); it('should return an empty array if an error occurs', () => { jest.mocked(operator.handleChannel).mockImplementation(() => { throw new Error('Test error'); }); const result = prepareMissingChannelsForAllTeams(operator, [], []); expect(result).toEqual([]); }); }); describe('prepareMyChannelsForTeam', () => { let operator: ServerDataOperator; let database: Database; let query: jest.Mock; beforeEach(() => { query = jest.fn(); database = { get: () => ({ query, }), } as unknown as Database; operator = { handleChannel: jest.fn(), handleChannelInfo: jest.fn(), handleChannelMembership: jest.fn(), handleMyChannel: jest.fn(), handleMyChannelSettings: jest.fn(), database, } as unknown as ServerDataOperator; }); it('should prepare my channels for a team', async () => { const teamId = 'team_id'; const channels = [ TestHelper.fakeChannel({id: 'channel1', header: 'header1', purpose: 'purpose1', last_post_at: 123, last_root_post_at: 456}), TestHelper.fakeChannel({id: 'channel2', header: 'header2', purpose: 'purpose2', last_post_at: 789, last_root_post_at: 101112}), ]; const channelMembers = [ TestHelper.fakeChannelMember({channel_id: 'channel1', user_id: 'user1'}), TestHelper.fakeChannelMember({channel_id: 'channel2', user_id: 'user2'}), ]; const allChannelsForTeam = { channel1: {id: 'channel1'}, channel2: {id: 'channel2'}, }; const allChannelsInfoForTeam = { channel1: {id: 'channel1', memberCount: 10, guestCount: 2, pinnedPostCount: 1, filesCount: 5}, channel2: {id: 'channel2', memberCount: 20, guestCount: 3, pinnedPostCount: 2, filesCount: 10}, }; const channelsQuery = {fetch: jest.fn().mockResolvedValue(Object.values(allChannelsForTeam))}; const channelInfosQuery = {fetch: jest.fn().mockResolvedValue(Object.values(allChannelsInfoForTeam))}; query.mockReturnValueOnce(channelsQuery); query.mockReturnValueOnce(channelInfosQuery); const channelRecords = Promise.resolve([]); const channelInfoRecords = Promise.resolve([]); const membershipRecords = Promise.resolve([]); const myChannelRecords = Promise.resolve([]); const myChannelSettingsRecords = Promise.resolve([]); jest.mocked(operator.handleChannel).mockReturnValue(channelRecords); jest.mocked(operator.handleChannelInfo).mockReturnValue(channelInfoRecords); jest.mocked(operator.handleChannelMembership).mockReturnValue(membershipRecords); jest.mocked(operator.handleMyChannel).mockReturnValue(myChannelRecords); jest.mocked(operator.handleMyChannelSettings).mockReturnValue(myChannelSettingsRecords); query = jest.fn().mockResolvedValueOnce(Object.entries(allChannelsForTeam).map((c) => c[1])).mockResolvedValueOnce(Object.entries(allChannelsInfoForTeam).map((i) => i[1])); const result = await prepareMyChannelsForTeam(operator, teamId, channels, channelMembers, true); query = jest.fn(); expect(result).toEqual([channelRecords, channelInfoRecords, membershipRecords, myChannelRecords, myChannelSettingsRecords]); expect(operator.handleChannel).toHaveBeenCalledWith({channels, prepareRecordsOnly: true}); expect(operator.handleChannelInfo).toHaveBeenCalledWith({ channelInfos: [ expect.objectContaining({id: 'channel1', header: 'header1', purpose: 'purpose1', guest_count: 2, member_count: 10, pinned_post_count: 1, files_count: 5}), expect.objectContaining({id: 'channel2', header: 'header2', purpose: 'purpose2', guest_count: 3, member_count: 20, pinned_post_count: 2, files_count: 10}), ], prepareRecordsOnly: true, }); expect(operator.handleChannelMembership).toHaveBeenCalledWith({ channelMemberships: [ expect.objectContaining({channel_id: 'channel1', user_id: 'user1'}), expect.objectContaining({channel_id: 'channel2', user_id: 'user2'}), ], prepareRecordsOnly: true, }); expect(operator.handleMyChannel).toHaveBeenCalledWith({ channels, myChannels: [ expect.objectContaining({channel_id: 'channel1', user_id: 'user1', id: 'channel1'}), expect.objectContaining({channel_id: 'channel2', user_id: 'user2', id: 'channel2'}), ], prepareRecordsOnly: true, isCRTEnabled: true, }); expect(operator.handleMyChannelSettings).toHaveBeenCalledWith({ settings: [ expect.objectContaining({channel_id: 'channel1', user_id: 'user1', id: 'channel1'}), expect.objectContaining({channel_id: 'channel2', user_id: 'user2', id: 'channel2'}), ], prepareRecordsOnly: true, }); }); }); describe('prepareDeleteChannel', () => { let channel: ChannelModel; const serverUrl = 'prepareDeleteChannel.test.com'; beforeEach(() => { channel = TestHelper.fakeChannelModel({ prepareDestroyPermanently: jest.fn().mockReturnValue({}), }); }); it('should prepare models for deletion', async () => { const unsetSpy = jest.spyOn(EphemeralStore, 'unsetChannelPlaybooksSynced'); const membershipModel = TestHelper.fakeMyChannelModel({prepareDestroyPermanently: jest.fn().mockReturnValue({id: 'membership'})}); const infoModel = TestHelper.fakeChannelInfoModel({prepareDestroyPermanently: jest.fn().mockReturnValue({id: 'info'})}); const categoryChannelModel = TestHelper.fakeCategoryChannelModel({prepareDestroyPermanently: jest.fn().mockReturnValue({id: 'category'})}); const memberModels = [TestHelper.fakeChannelMembershipModel({prepareDestroyPermanently: jest.fn().mockReturnValue({id: 'member'})})]; const draftModels = [TestHelper.fakeDraftModel({prepareDestroyPermanently: jest.fn().mockReturnValue({id: 'draft'})})]; const postsInChannelModels = [ TestHelper.fakePostsInChannelModel({prepareDestroyPermanently: jest.fn().mockReturnValue({id: 'postsInChannel'})}), ]; const postModels = [ TestHelper.fakePostModel({id: 'post1', prepareDestroyPermanently: jest.fn().mockReturnValue({id: 'post'})}), ]; const bookmarkModels = [ TestHelper.fakeChannelBookmarkModel({id: 'bookmark1', prepareDestroyPermanently: jest.fn().mockReturnValue({id: 'bookmark'})}), ]; const playbookRunModels = [ TestHelper.fakePlaybookRunModel({ id: 'playbookRun', prepareDestroyWithRelations: jest.fn().mockResolvedValue([ TestHelper.fakePlaybookRunModel({ id: 'playbookRun', }), ]), }), ]; jest.mocked(channel.membership.fetch).mockResolvedValue(membershipModel); jest.mocked(channel.info.fetch).mockResolvedValue(infoModel); jest.mocked(channel.categoryChannel.fetch).mockResolvedValue(categoryChannelModel); jest.mocked(channel.members.fetch).mockResolvedValue(memberModels); jest.mocked(channel.drafts.fetch).mockResolvedValue(draftModels); jest.mocked(channel.postsInChannel.fetch).mockResolvedValue(postsInChannelModels); jest.mocked(channel.posts.fetch).mockResolvedValue(postModels); jest.mocked(channel.bookmarks.fetch).mockResolvedValue(bookmarkModels); jest.mocked(channel.playbookRuns.fetch).mockResolvedValue(playbookRunModels); const result = await prepareDeleteChannel(serverUrl, channel); expect(result).toEqual([ {}, {id: 'membership'}, {id: 'info'}, {id: 'category'}, {id: 'member'}, {id: 'draft'}, {id: 'postsInChannel'}, {id: 'post'}, {id: 'bookmark'}, expect.objectContaining({id: 'playbookRun'}), ]); expect(channel.prepareDestroyPermanently).toHaveBeenCalled(); expect(channel.membership.fetch).toHaveBeenCalled(); expect(channel.info.fetch).toHaveBeenCalled(); expect(channel.categoryChannel.fetch).toHaveBeenCalled(); expect(channel.members.fetch).toHaveBeenCalled(); expect(channel.drafts.fetch).toHaveBeenCalled(); expect(channel.postsInChannel.fetch).toHaveBeenCalled(); expect(channel.posts.fetch).toHaveBeenCalled(); expect(channel.bookmarks.fetch).toHaveBeenCalled(); expect(channel.playbookRuns.fetch).toHaveBeenCalled(); expect(playbookRunModels[0].prepareDestroyWithRelations).toHaveBeenCalled(); // Should have cleared the playbooks synced expect(unsetSpy).toHaveBeenCalledWith(serverUrl, channel.id); }); it('should handle errors gracefully', async () => { jest.mocked(channel.membership.fetch).mockRejectedValue(new Error('Test error')); jest.mocked(channel.info.fetch).mockRejectedValue(new Error('Test error')); jest.mocked(channel.categoryChannel.fetch).mockRejectedValue(new Error('Test error')); const result = await prepareDeleteChannel(serverUrl, channel); expect(result).toEqual([{}]); expect(channel.prepareDestroyPermanently).toHaveBeenCalled(); }); }); describe('prepareDeleteBookmarks', () => { let bookmark: ChannelBookmarkModel; beforeEach(() => { bookmark = TestHelper.fakeChannelBookmarkModel({ prepareDestroyPermanently: jest.fn().mockReturnValue({}), fileId: 'file_id', }); }); it('should prepare bookmark and associated file for deletion', async () => { const fileModel = TestHelper.fakeFileModel({prepareDestroyPermanently: jest.fn().mockReturnValue({})}); jest.mocked(bookmark.file.fetch).mockResolvedValue(fileModel); const result = await prepareDeleteBookmarks(bookmark); expect(result).toEqual([{}, {}]); expect(bookmark.prepareDestroyPermanently).toHaveBeenCalled(); expect(bookmark.file.fetch).toHaveBeenCalled(); expect(fileModel.prepareDestroyPermanently).toHaveBeenCalled(); }); it('should handle errors gracefully when fetching associated file', async () => { jest.mocked(bookmark.file.fetch).mockRejectedValue(new Error('Test error')); const result = await prepareDeleteBookmarks(bookmark); expect(result).toEqual([{}]); expect(bookmark.prepareDestroyPermanently).toHaveBeenCalled(); expect(bookmark.file.fetch).toHaveBeenCalled(); }); it('should prepare only the bookmark for deletion if no associated file', async () => { bookmark.fileId = undefined; const result = await prepareDeleteBookmarks(bookmark); expect(result).toEqual([{}]); expect(bookmark.prepareDestroyPermanently).toHaveBeenCalled(); expect(bookmark.file.fetch).not.toHaveBeenCalled(); }); }); describe('Channel Queries', () => { let database: Database; let mockQuery: jest.Mock; let mockCollection: { query: jest.Mock}; beforeEach(() => { mockQuery = jest.fn(() => ({extend: jest.fn()})); mockCollection = {query: mockQuery}; database = { get: jest.fn().mockReturnValue(mockCollection), } as unknown as Database; }); it('should query all channels', () => { queryAllChannels(database); expect(database.get).toHaveBeenCalledWith(MM_TABLES.SERVER.CHANNEL); expect(mockQuery).toHaveBeenCalled(); }); it('should query all channels for a team', () => { const teamId = 'team_id'; queryAllChannelsForTeam(database, teamId); expect(database.get).toHaveBeenCalledWith(MM_TABLES.SERVER.CHANNEL); expect(mockQuery).toHaveBeenCalledWith(Q.where('team_id', teamId)); }); it('should query all channel info', () => { queryAllChannelsInfo(database); expect(database.get).toHaveBeenCalledWith(MM_TABLES.SERVER.CHANNEL_INFO); expect(mockQuery).toHaveBeenCalled(); }); it('should query all channel info for a team', () => { const teamId = 'team_id'; queryAllChannelsInfoForTeam(database, teamId); expect(database.get).toHaveBeenCalledWith(MM_TABLES.SERVER.CHANNEL_INFO); expect(mockQuery).toHaveBeenCalledWith(Q.on(MM_TABLES.SERVER.CHANNEL, Q.where('team_id', teamId))); }); it('should query all my channels', () => { queryAllMyChannel(database); expect(database.get).toHaveBeenCalledWith(MM_TABLES.SERVER.MY_CHANNEL); expect(mockQuery).toHaveBeenCalled(); }); it('should query all my channels for a team', () => { const teamId = 'team_id'; queryAllMyChannelsForTeam(database, teamId); expect(database.get).toHaveBeenCalledWith(MM_TABLES.SERVER.MY_CHANNEL); expect(mockQuery).toHaveBeenCalledWith(Q.on(MM_TABLES.SERVER.CHANNEL, Q.where('team_id', Q.oneOf([teamId, ''])))); }); it('should query all unread DMs and GMs ids', () => { queryAllUnreadDMsAndGMsIds(database); expect(database.get).toHaveBeenCalledWith(MM_TABLES.SERVER.CHANNEL); expect(mockQuery).toHaveBeenCalledWith( Q.on(MM_TABLES.SERVER.MY_CHANNEL, Q.or( Q.where('mentions_count', Q.gt(0)), Q.where('message_count', Q.gt(0)), )), Q.where('type', Q.oneOf([General.GM_CHANNEL, General.DM_CHANNEL])), ); }); it('should query channels by their IDs', () => { const channelIds = ['channel1', 'channel2']; queryChannelsById(database, channelIds); expect(database.get).toHaveBeenCalledWith(MM_TABLES.SERVER.CHANNEL); expect(mockQuery).toHaveBeenCalledWith(Q.where('id', Q.oneOf(channelIds))); }); it('should query channels by their types', () => { const channelTypes: ChannelType[] = ['O', 'P']; queryChannelsByTypes(database, channelTypes); expect(database.get).toHaveBeenCalledWith(MM_TABLES.SERVER.CHANNEL); expect(mockQuery).toHaveBeenCalledWith(Q.where('type', Q.oneOf(channelTypes))); }); it('should query user channels by their types', () => { const userId = 'user_id'; const channelTypes: ChannelType[] = ['O', 'P']; queryUserChannelsByTypes(database, userId, channelTypes); expect(database.get).toHaveBeenCalledWith(MM_TABLES.SERVER.CHANNEL); expect(mockQuery).toHaveBeenCalledWith( Q.where('type', Q.oneOf(channelTypes)), Q.on(MM_TABLES.SERVER.CHANNEL_MEMBERSHIP, Q.where('user_id', userId)), ); }); it('should query the default channel for a team', () => { const teamId = 'team_id'; queryTeamDefaultChannel(database, teamId); expect(database.get).toHaveBeenCalledWith(MM_TABLES.SERVER.CHANNEL); expect(mockQuery).toHaveBeenCalledWith( Q.where('team_id', teamId), Q.where('name', General.DEFAULT_CHANNEL), ); }); it('should query my channels by team', () => { const teamId = 'team_id'; queryMyChannelsByTeam(database, teamId); expect(database.get).toHaveBeenCalledWith(MM_TABLES.SERVER.MY_CHANNEL); expect(mockQuery).toHaveBeenCalledWith( Q.on(MM_TABLES.SERVER.CHANNEL, Q.and( Q.where('team_id', Q.eq(teamId)), Q.where('delete_at', Q.eq(0)), )), ); }); it('should include deleted channels if includeDeleted is true', () => { const teamId = 'team_id'; queryMyChannelsByTeam(database, teamId, true); expect(database.get).toHaveBeenCalledWith(MM_TABLES.SERVER.MY_CHANNEL); expect(mockQuery).toHaveBeenCalledWith( Q.on(MM_TABLES.SERVER.CHANNEL, Q.and( Q.where('team_id', Q.eq(teamId)), )), ); }); it('should query users on a channel', () => { const channelId = 'channel_id'; queryUsersOnChannel(database, channelId); expect(database.get).toHaveBeenCalledWith(MM_TABLES.SERVER.USER); expect(mockQuery).toHaveBeenCalledWith(Q.on(MM_TABLES.SERVER.CHANNEL_MEMBERSHIP, Q.where('channel_id', channelId))); }); it('should query all my channel settings', () => { queryAllMyChannelSettings(database); expect(database.get).toHaveBeenCalledWith(MM_TABLES.SERVER.MY_CHANNEL_SETTINGS); expect(mockQuery).toHaveBeenCalled(); }); it('should query my channel settings by IDs', () => { const ids = ['id1', 'id2']; queryMyChannelSettingsByIds(database, ids); expect(database.get).toHaveBeenCalledWith(MM_TABLES.SERVER.MY_CHANNEL_SETTINGS); expect(mockQuery).toHaveBeenCalledWith(Q.where('id', Q.oneOf(ids))); }); it('should query my channel unreads', () => { const currentTeamId = 'team_id'; queryMyChannelUnreads(database, currentTeamId); expect(database.get).toHaveBeenCalledWith(MM_TABLES.SERVER.MY_CHANNEL); expect(mockQuery).toHaveBeenCalledWith( Q.on( MM_TABLES.SERVER.CHANNEL, Q.and( Q.or( Q.where('team_id', Q.eq(currentTeamId)), Q.where('team_id', Q.eq('')), ), Q.where('delete_at', Q.eq(0)), ), ), Q.or( Q.where('is_unread', Q.eq(true)), Q.where('mentions_count', Q.gte(0)), ), Q.sortBy('last_post_at', Q.desc), ); }); it('should query my recent channels', () => { const take = 10; queryMyRecentChannels(database, take); expect(database.get).toHaveBeenCalledWith(MM_TABLES.SERVER.MY_CHANNEL); expect(mockQuery).toHaveBeenCalled(); }); it('should query my recent channels without take', () => { const take = 0; queryMyRecentChannels(database, take); expect(database.get).toHaveBeenCalledWith(MM_TABLES.SERVER.MY_CHANNEL); expect(mockQuery).toHaveBeenCalled(); }); it('should query channels for autocomplete', () => { const matchTerm = 'test'; const teamId = 'team_id'; queryChannelsForAutocomplete(database, matchTerm, true, teamId); expect(database.get).toHaveBeenCalledWith(MM_TABLES.SERVER.CHANNEL); expect(mockQuery).toHaveBeenCalled(); }); it('should query channels for autocomplete with no search', () => { const matchTerm = 'test'; const teamId = 'team_id'; queryChannelsForAutocomplete(database, matchTerm, false, teamId); expect(database.get).toHaveBeenCalledWith(MM_TABLES.SERVER.CHANNEL); expect(mockQuery).toHaveBeenCalled(); }); it('should query channel members', () => { const channelId = 'channel_id'; queryChannelMembers(database, channelId); expect(database.get).toHaveBeenCalledWith(MM_TABLES.SERVER.CHANNEL_MEMBERSHIP); expect(mockQuery).toHaveBeenCalledWith( Q.where('channel_id', channelId), ); }); }); describe('Channel Functions', () => { let database: Database; beforeEach(() => { database = { get: jest.fn(), } as unknown as Database; }); describe('getMyChannel', () => { it('should return the channel member if found', async () => { const channelId = 'channel_id'; const mockMember = TestHelper.fakeMyChannelModel({id: channelId}); jest.mocked(database.get).mockReturnValue({ find: jest.fn().mockResolvedValue(mockMember), } as any); const result = await getMyChannel(database, channelId); expect(database.get).toHaveBeenCalledWith(MM_TABLES.SERVER.MY_CHANNEL); expect(result).toEqual(mockMember); }); it('should return undefined if the channel member is not found', async () => { const channelId = 'channel_id'; jest.mocked(database.get).mockReturnValue({ find: jest.fn().mockRejectedValue(new Error('Not found')), } as any); const result = await getMyChannel(database, channelId); expect(database.get).toHaveBeenCalledWith(MM_TABLES.SERVER.MY_CHANNEL); expect(result).toBeUndefined(); }); }); describe('observeMyChannel', () => { it('should observe the channel member', () => { const channelId = 'channel_id'; const mockQuery = jest.fn().mockReturnValue({ observe: jest.fn().mockReturnValue(of$([{id: channelId, observe: jest.fn().mockReturnValue(of$({id: channelId}))}])), }); jest.mocked(database.get).mockReturnValue({ query: mockQuery, } as any); const result = observeMyChannel(database, channelId); expect(database.get).toHaveBeenCalledWith(MM_TABLES.SERVER.MY_CHANNEL); expect(mockQuery).toHaveBeenCalledWith(Q.where('id', channelId), Q.take(1)); result.subscribe((value) => { expect(value).toEqual({id: channelId}); }); }); it('should return undefined if the channel member is not found', () => { const channelId = 'channel_id'; const mockQuery = jest.fn().mockReturnValue({ observe: jest.fn().mockReturnValue(of$([])), }); jest.mocked(database.get).mockReturnValue({ query: mockQuery, } as any); const result = observeMyChannel(database, channelId); expect(database.get).toHaveBeenCalledWith(MM_TABLES.SERVER.MY_CHANNEL); expect(mockQuery).toHaveBeenCalledWith(Q.where('id', channelId), Q.take(1)); result.subscribe((value) => { expect(value).toBeUndefined(); }); }); }); describe('observeMyChannelRoles', () => { it('should observe the channel member roles', () => { const channelId = 'channel_id'; const roles = 'role1 role2'; const mockQuery = jest.fn().mockReturnValue({ observe: jest.fn().mockReturnValue(of$([{id: channelId, roles, observe: jest.fn().mockReturnValue(of$({roles}))}])), }); jest.mocked(database.get).mockReturnValue({ query: mockQuery, } as any); const result = observeMyChannelRoles(database, channelId); result.subscribe((value) => { expect(value).toEqual(roles); }); }); it('should return undefined if the channel member is not found', () => { const channelId = 'channel_id'; const mockQuery = jest.fn().mockReturnValue({ observe: jest.fn().mockReturnValue(of$([])), }); jest.mocked(database.get).mockReturnValue({ query: mockQuery, } as any); const result = observeMyChannelRoles(database, channelId); result.subscribe((value) => { expect(value).toBeUndefined(); }); }); }); describe('getChannelById', () => { it('should return the channel if found', async () => { const channelId = 'channel_id'; const mockedChannel = TestHelper.fakeChannelModel({id: channelId}); jest.mocked(database.get).mockReturnValue({ find: jest.fn().mockResolvedValue(mockedChannel), } as any); const result = await getChannelById(database, channelId); expect(database.get).toHaveBeenCalledWith(MM_TABLES.SERVER.CHANNEL); expect(result).toEqual(mockedChannel); }); it('should return undefined if the channel is not found', async () => { const channelId = 'channel_id'; jest.mocked(database.get).mockReturnValue({ find: jest.fn().mockRejectedValue(new Error('Not found')), } as any); const result = await getChannelById(database, channelId); expect(database.get).toHaveBeenCalledWith(MM_TABLES.SERVER.CHANNEL); expect(result).toBeUndefined(); }); }); describe('observeChannel', () => { it('should observe the channel', () => { const channelId = 'channel_id'; const mockQuery = jest.fn().mockReturnValue({ observe: jest.fn().mockReturnValue(of$([{id: channelId, observe: jest.fn().mockReturnValue(of$({id: channelId}))}])), }); jest.mocked(database.get).mockReturnValue({ query: mockQuery, } as any); const result = observeChannel(database, channelId); expect(database.get).toHaveBeenCalledWith(MM_TABLES.SERVER.CHANNEL); expect(mockQuery).toHaveBeenCalledWith(Q.where('id', channelId), Q.take(1)); result.subscribe((value) => { expect(value).toEqual({id: channelId}); }); }); it('should return undefined if the channel is not found', () => { const channelId = 'channel_id'; const mockQuery = jest.fn().mockReturnValue({ observe: jest.fn().mockReturnValue(of$([])), }); jest.mocked(database.get).mockReturnValue({ query: mockQuery, } as any); const result = observeChannel(database, channelId); expect(database.get).toHaveBeenCalledWith(MM_TABLES.SERVER.CHANNEL); expect(mockQuery).toHaveBeenCalledWith(Q.where('id', channelId), Q.take(1)); result.subscribe((value) => { expect(value).toBeUndefined(); }); }); }); describe('getChannelByName', () => { it('should return the channel if found', async () => { const teamId = 'team_id'; const channelName = 'channel_name'; const mockedChannel = TestHelper.fakeChannelModel({id: 'channel_id', name: channelName}); jest.mocked(database.get).mockReturnValue({ query: jest.fn().mockReturnValue({ fetch: jest.fn().mockResolvedValue([mockedChannel]), }), } as any); const result = await getChannelByName(database, teamId, channelName); expect(database.get).toHaveBeenCalledWith(MM_TABLES.SERVER.CHANNEL); expect(result).toEqual(mockedChannel); }); it('should return undefined if the channel is not found', async () => { const teamId = 'team_id'; const channelName = 'channel_name'; jest.mocked(database.get).mockReturnValue({ query: jest.fn().mockReturnValue({ fetch: jest.fn().mockResolvedValue([]), }), } as any); const result = await getChannelByName(database, teamId, channelName); expect(database.get).toHaveBeenCalledWith(MM_TABLES.SERVER.CHANNEL); expect(result).toBeUndefined(); }); }); describe('getChannelInfo', () => { it('should return the channel info if found', async () => { const channelId = 'channel_id'; const mockChannelInfo = TestHelper.fakeChannelInfoModel({id: channelId}); jest.mocked(database.get).mockReturnValue({ find: jest.fn().mockResolvedValue(mockChannelInfo), } as any); const result = await getChannelInfo(database, channelId); expect(database.get).toHaveBeenCalledWith(MM_TABLES.SERVER.CHANNEL_INFO); expect(result).toEqual(mockChannelInfo); }); it('should return undefined if the channel info is not found', async () => { const channelId = 'channel_id'; jest.mocked(database.get).mockReturnValue({ find: jest.fn().mockRejectedValue(new Error('Not found')), } as any); const result = await getChannelInfo(database, channelId); expect(database.get).toHaveBeenCalledWith(MM_TABLES.SERVER.CHANNEL_INFO); expect(result).toBeUndefined(); }); }); describe('getDefaultChannelForTeam', () => { let mockQuery: jest.Mock; let mockCollection: { query: jest.Mock }; beforeEach(() => { mockQuery = jest.fn(); mockCollection = {query: mockQuery}; database = { get: jest.fn().mockReturnValue(mockCollection), } as unknown as Database; }); it('should return the default channel for the team', async () => { const teamId = 'team_id'; const defaultChannel = TestHelper.fakeChannelModel({id: 'default_channel', name: General.DEFAULT_CHANNEL}); const myFirstTeamChannel = TestHelper.fakeChannelModel({id: 'first_team_channel'}); const roles = [TestHelper.fakeRoleModel({permissions: [Permissions.JOIN_PUBLIC_CHANNELS]})]; jest.mocked(queryRoles).mockReturnValue(TestHelper.fakeQuery(roles)); jest.mocked(hasPermission).mockReturnValue(true); jest.mocked(mockQuery).mockReturnValue({ fetch: jest.fn().mockResolvedValue([defaultChannel, myFirstTeamChannel]), }); const result = await getDefaultChannelForTeam(database, teamId); expect(queryRoles).toHaveBeenCalledWith(database); expect(hasPermission).toHaveBeenCalledWith(roles, Permissions.JOIN_PUBLIC_CHANNELS); expect(database.get).toHaveBeenCalledWith(MM_TABLES.SERVER.CHANNEL); expect(mockQuery).toHaveBeenCalledWith( Q.on(MM_TABLES.SERVER.MY_CHANNEL, 'id', Q.notEq('')), Q.and( Q.where('team_id', teamId), Q.where('delete_at', Q.eq(0)), Q.where('type', General.OPEN_CHANNEL), ), Q.sortBy('display_name', Q.asc), ); expect(result).toEqual(defaultChannel); }); it('should return the default channel for the team with ignore id', async () => { const teamId = 'team_id'; const defaultChannel = TestHelper.fakeChannelModel({id: 'default_channel', name: General.DEFAULT_CHANNEL}); const myFirstTeamChannel = TestHelper.fakeChannelModel({id: 'first_team_channel'}); jest.mocked(queryRoles).mockReturnValue(TestHelper.fakeQuery([])); jest.mocked(hasPermission).mockReturnValue(true); jest.mocked(mockQuery).mockReturnValue({ fetch: jest.fn().mockResolvedValue([defaultChannel, myFirstTeamChannel]), }); const result = await getDefaultChannelForTeam(database, teamId, 'ignoreid'); expect(queryRoles).toHaveBeenCalledWith(database); expect(database.get).toHaveBeenCalledWith(MM_TABLES.SERVER.CHANNEL); expect(mockQuery).toHaveBeenCalledWith( Q.on(MM_TABLES.SERVER.MY_CHANNEL, 'id', Q.notEq('')), Q.and( Q.where('team_id', teamId), Q.where('delete_at', Q.eq(0)), Q.where('type', General.OPEN_CHANNEL), Q.where('id', Q.notEq('ignoreid')), ), Q.sortBy('display_name', Q.asc), ); expect(result).toEqual(defaultChannel); }); it('should return the first team channel if no default channel is found', async () => { const teamId = 'team_id'; const myFirstTeamChannel = TestHelper.fakeChannelModel({id: 'first_team_channel'}); const roles = [TestHelper.fakeRoleModel({permissions: []})]; jest.mocked(queryRoles).mockReturnValue(TestHelper.fakeQuery(roles)); jest.mocked(hasPermission).mockReturnValue(false); jest.mocked(mockQuery).mockReturnValue({ fetch: jest.fn().mockResolvedValue([myFirstTeamChannel]), }); const result = await getDefaultChannelForTeam(database, teamId); expect(queryRoles).toHaveBeenCalledWith(database); expect(hasPermission).toHaveBeenCalledWith(roles, Permissions.JOIN_PUBLIC_CHANNELS); expect(database.get).toHaveBeenCalledWith(MM_TABLES.SERVER.CHANNEL); expect(mockQuery).toHaveBeenCalledWith( Q.on(MM_TABLES.SERVER.MY_CHANNEL, 'id', Q.notEq('')), Q.and( Q.where('team_id', teamId), Q.where('delete_at', Q.eq(0)), Q.where('type', General.OPEN_CHANNEL), ), Q.sortBy('display_name', Q.asc), ); expect(result).toEqual(myFirstTeamChannel); }); it('should return undefined if no channels are found', async () => { const teamId = 'team_id'; const roles = [TestHelper.fakeRoleModel({permissions: [Permissions.JOIN_PUBLIC_CHANNELS]})]; jest.mocked(queryRoles).mockReturnValue(TestHelper.fakeQuery(roles)); jest.mocked(hasPermission).mockReturnValue(true); jest.mocked(mockQuery).mockReturnValue({ fetch: jest.fn().mockResolvedValue([]), }); const result = await getDefaultChannelForTeam(database, teamId); expect(queryRoles).toHaveBeenCalledWith(database); expect(hasPermission).toHaveBeenCalledWith(roles, Permissions.JOIN_PUBLIC_CHANNELS); expect(database.get).toHaveBeenCalledWith(MM_TABLES.SERVER.CHANNEL); expect(mockQuery).toHaveBeenCalledWith( Q.on(MM_TABLES.SERVER.MY_CHANNEL, 'id', Q.notEq('')), Q.and( Q.where('team_id', teamId), Q.where('delete_at', Q.eq(0)), Q.where('type', General.OPEN_CHANNEL), ), Q.sortBy('display_name', Q.asc), ); expect(result).toBeUndefined(); }); }); describe('getCurrentChannel', () => { it('should return the current channel if found', async () => { const currentChannelId = 'current_channel_id'; const mockedChannel = TestHelper.fakeChannelModel({id: currentChannelId}); jest.mocked(getCurrentChannelId).mockResolvedValue(currentChannelId); jest.mocked(database.get).mockReturnValue({ find: jest.fn().mockResolvedValue(mockedChannel), } as any); const result = await getCurrentChannel(database); expect(getCurrentChannelId).toHaveBeenCalledWith(database); expect(database.get).toHaveBeenCalledWith(MM_TABLES.SERVER.CHANNEL); expect(result).toEqual(mockedChannel); }); it('should return undefined if no current channel is found', async () => { jest.mocked(getCurrentChannelId).mockResolvedValue(''); const result = await getCurrentChannel(database); expect(getCurrentChannelId).toHaveBeenCalledWith(database); expect(result).toBeUndefined(); }); }); describe('getCurrentChannelInfo', () => { it('should return the current channel info if found', async () => { const currentChannelId = 'current_channel_id'; const mockChannelInfo = TestHelper.fakeChannelInfoModel({id: currentChannelId}); jest.mocked(getCurrentChannelId).mockResolvedValue(currentChannelId); jest.mocked(database.get).mockReturnValue({ find: jest.fn().mockResolvedValue(mockChannelInfo), } as any); const result = await getCurrentChannelInfo(database); expect(getCurrentChannelId).toHaveBeenCalledWith(database); expect(database.get).toHaveBeenCalledWith(MM_TABLES.SERVER.CHANNEL_INFO); expect(result).toEqual(mockChannelInfo); }); it('should return undefined if no current channel info is found', async () => { jest.mocked(getCurrentChannelId).mockResolvedValue(''); const result = await getCurrentChannelInfo(database); expect(getCurrentChannelId).toHaveBeenCalledWith(database); expect(result).toBeUndefined(); }); }); describe('observeCurrentChannel', () => { it('should observe the current channel', () => { const currentChannelId = 'current_channel_id'; const mockedChannel = TestHelper.fakeChannelModel({id: currentChannelId, observe: jest.fn().mockReturnValue(of$({id: currentChannelId}))}); const mockQuery = jest.fn().mockReturnValue({ observe: jest.fn().mockReturnValue(of$([mockedChannel])), }); jest.mocked(database.get).mockReturnValue({ query: mockQuery, } as any); jest.mocked(observeCurrentChannelId).mockReturnValue(of$(currentChannelId)); const result = observeCurrentChannel(database); expect(observeCurrentChannelId).toHaveBeenCalledWith(database); result.subscribe((value) => { expect(value).toEqual({id: currentChannelId}); }); }); it('should return undefined if no current channel is found', () => { const currentChannelId = 'current_channel_id'; const mockQuery = jest.fn().mockReturnValue({ observe: jest.fn().mockReturnValue(of$([])), }); jest.mocked(database.get).mockReturnValue({ query: mockQuery, } as any); jest.mocked(observeCurrentChannelId).mockReturnValue(of$(currentChannelId)); const result = observeCurrentChannel(database); expect(observeCurrentChannelId).toHaveBeenCalledWith(database); result.subscribe((value) => { expect(value).toBeUndefined(); }); }); }); describe('getChannelInfo', () => { it('should return the channel info if found', async () => { const channelId = 'channel_id'; const mockChannelInfo = TestHelper.fakeChannelInfoModel({id: channelId}); jest.mocked(database.get).mockReturnValue({ find: jest.fn().mockResolvedValue(mockChannelInfo), } as any); const result = await getChannelInfo(database, channelId); expect(database.get).toHaveBeenCalledWith(MM_TABLES.SERVER.CHANNEL_INFO); expect(result).toEqual(mockChannelInfo); }); it('should return undefined if the channel info is not found', async () => { const channelId = 'channel_id'; jest.mocked(database.get).mockReturnValue({ find: jest.fn().mockRejectedValue(new Error('Not found')), } as any); const result = await getChannelInfo(database, channelId); expect(database.get).toHaveBeenCalledWith(MM_TABLES.SERVER.CHANNEL_INFO); expect(result).toBeUndefined(); }); }); }); describe('Channel Membership Functions', () => { let operator: ServerDataOperator; let database: Database; beforeEach(() => { database = { get: jest.fn(), } as unknown as Database; operator = { database, handleChannelMembership: jest.fn(), batchRecords: jest.fn(), } as unknown as ServerDataOperator; }); describe('deleteChannelMembership', () => { it('should delete channel membership and return models', async () => { const userId = 'user_id'; const channelId = 'channel_id'; const mockMembership = TestHelper.fakeChannelMembershipModel({prepareDestroyPermanently: jest.fn().mockReturnValue({})}); jest.mocked(database.get).mockReturnValue({ query: jest.fn().mockReturnValue({ fetch: jest.fn().mockResolvedValue([mockMembership]), }), } as any); const result = await deleteChannelMembership(operator, userId, channelId); expect(database.get).toHaveBeenCalledWith(MM_TABLES.SERVER.CHANNEL_MEMBERSHIP); expect(result.models).toEqual([{}]); expect(operator.batchRecords).toHaveBeenCalledWith([{}], 'deleteChannelMembership'); }); it('should return models without batching if prepareRecordsOnly is true', async () => { const userId = 'user_id'; const channelId = 'channel_id'; const mockMembership = TestHelper.fakeChannelMembershipModel({prepareDestroyPermanently: jest.fn().mockReturnValue({})}); jest.mocked(database.get).mockReturnValue({ query: jest.fn().mockReturnValue({ fetch: jest.fn().mockResolvedValue([mockMembership]), }), } as any); const result = await deleteChannelMembership(operator, userId, channelId, true); expect(database.get).toHaveBeenCalledWith(MM_TABLES.SERVER.CHANNEL_MEMBERSHIP); expect(result.models).toEqual([{}]); expect(operator.batchRecords).not.toHaveBeenCalled(); }); it('should return an error if an exception occurs', async () => { const userId = 'user_id'; const channelId = 'channel_id'; const error = new Error('Test error'); jest.mocked(database.get).mockReturnValue({ query: jest.fn().mockReturnValue({ fetch: jest.fn().mockRejectedValue(error), }), } as any); const result = await deleteChannelMembership(operator, userId, channelId); expect(result.error).toEqual(error); }); }); describe('addChannelMembership', () => { it('should add channel membership and return an empty object', async () => { const userId = 'user_id'; const channelId = 'channel_id'; const result = await addChannelMembership(operator, userId, channelId); expect(operator.handleChannelMembership).toHaveBeenCalledWith({ channelMemberships: [{channel_id: channelId, user_id: userId}], prepareRecordsOnly: false, }); expect(result).toEqual({}); }); it('should return an error if an exception occurs', async () => { const userId = 'user_id'; const channelId = 'channel_id'; const error = new Error('Test error'); jest.mocked(operator.handleChannelMembership).mockRejectedValue(error); const result = await addChannelMembership(operator, userId, channelId); expect(result.error).toEqual(error); }); }); describe('getMembersCountByChannelsId', () => { let mockQuery: jest.Mock; let mockCollection: { query: jest.Mock }; beforeEach(() => { mockQuery = jest.fn(); mockCollection = {query: mockQuery}; database = { get: jest.fn().mockReturnValue(mockCollection), } as unknown as Database; }); it('should return the members count by channel IDs', async () => { const channelsId = ['channel1', 'channel2']; const mockMemberships = [ TestHelper.fakeChannelMembershipModel({channelId: 'channel1'}), TestHelper.fakeChannelMembershipModel({channelId: 'channel1'}), TestHelper.fakeChannelMembershipModel({channelId: 'channel2'}), ]; jest.mocked(mockQuery).mockReturnValue({ fetch: jest.fn().mockResolvedValue(mockMemberships), }); const result = await getMembersCountByChannelsId(database, channelsId); expect(database.get).toHaveBeenCalledWith(MM_TABLES.SERVER.CHANNEL_MEMBERSHIP); expect(mockQuery).toHaveBeenCalledWith(Q.where('channel_id', Q.oneOf(channelsId))); expect(result).toEqual({channel1: 2, channel2: 1}); }); it('should return zero counts for channels with no members', async () => { const channelsId = ['channel1', 'channel2']; const mockMemberships: ChannelMembershipModel[] = []; jest.mocked(mockQuery).mockReturnValue({ fetch: jest.fn().mockResolvedValue(mockMemberships), }); const result = await getMembersCountByChannelsId(database, channelsId); expect(database.get).toHaveBeenCalledWith(MM_TABLES.SERVER.CHANNEL_MEMBERSHIP); expect(mockQuery).toHaveBeenCalledWith(Q.where('channel_id', Q.oneOf(channelsId))); expect(result).toEqual({channel1: 0, channel2: 0}); }); it('should handle errors gracefully', async () => { const channelsId = ['channel1', 'channel2']; const error = new Error('Test error'); jest.mocked(mockQuery).mockReturnValue({ fetch: jest.fn().mockRejectedValue(error), }); await expect(getMembersCountByChannelsId(database, channelsId)).rejects.toThrow(error); }); }); }); describe('Channel Observations', () => { let database: Database; let mockQuery: jest.Mock; let mockCollection: { query: jest.Mock }; beforeEach(() => { mockQuery = jest.fn(); mockCollection = {query: mockQuery}; database = { get: jest.fn().mockReturnValue(mockCollection), } as unknown as Database; }); it('should observe channel info', () => { const channelId = 'channel_id'; const mockChannelInfo = {id: channelId, observe: jest.fn().mockReturnValue(of$({id: channelId}))} as unknown as ChannelInfoModel; mockQuery.mockReturnValue({ observe: jest.fn().mockReturnValue(of$([mockChannelInfo])), }); const result = observeChannelInfo(database, channelId); expect(database.get).toHaveBeenCalledWith(MM_TABLES.SERVER.CHANNEL_INFO); expect(mockQuery).toHaveBeenCalledWith(Q.where('id', channelId), Q.take(1)); result.subscribe((value) => { expect(value).toEqual({id: channelId}); }); }); it('should observe all my channel notify props', () => { const mockSettings = [ TestHelper.fakeMyChannelSettingsModel({id: 'id1', notifyProps: {mark_unread: 'all'}}), TestHelper.fakeMyChannelSettingsModel({id: 'id2', notifyProps: {mark_unread: 'mention'}}), ]; mockQuery.mockReturnValue({ observeWithColumns: jest.fn().mockReturnValue(of$(mockSettings)), }); const result = observeAllMyChannelNotifyProps(database); expect(database.get).toHaveBeenCalledWith(MM_TABLES.SERVER.MY_CHANNEL_SETTINGS); result.subscribe((value) => { expect(value).toEqual({ id1: {mark_unread: 'all'}, id2: {mark_unread: 'mention'}, }); }); }); it('should observe notify props by channels', () => { const channels = [ TestHelper.fakeChannelModel({id: 'channel1'}), TestHelper.fakeChannelModel({id: 'channel2'}), ]; const mockSettings = [ TestHelper.fakeMyChannelSettingsModel({id: 'channel1', notifyProps: {mark_unread: 'all'}}), TestHelper.fakeMyChannelSettingsModel({id: 'channel2', notifyProps: {mark_unread: 'mention'}}), ]; mockQuery.mockReturnValue({ observeWithColumns: jest.fn().mockReturnValue(of$(mockSettings)), }); const result = observeNotifyPropsByChannels(database, channels); expect(database.get).toHaveBeenCalledWith(MM_TABLES.SERVER.MY_CHANNEL_SETTINGS); result.subscribe((value) => { expect(value).toEqual({ channel1: {mark_unread: 'all'}, channel2: {mark_unread: 'mention'}, }); }); }); it('should observe my channel mention count', () => { const teamId = 'team_id'; const mockChannels = [ TestHelper.fakeMyChannelModel({mentionsCount: 2, isUnread: true}), TestHelper.fakeMyChannelModel({mentionsCount: 3, isUnread: false}), ]; mockQuery.mockReturnValue({ observeWithColumns: jest.fn().mockReturnValue(of$(mockChannels)), }); const result = observeMyChannelMentionCount(database, teamId); expect(database.get).toHaveBeenCalledWith(MM_TABLES.SERVER.MY_CHANNEL); result.subscribe((value) => { expect(value).toEqual(5); }); }); it('should observe my channel mention count with no team id', () => { const mockChannels = [ TestHelper.fakeMyChannelModel({mentionsCount: 2, isUnread: true}), TestHelper.fakeMyChannelModel({mentionsCount: 3, isUnread: false}), ]; mockQuery.mockReturnValue({ observeWithColumns: jest.fn().mockReturnValue(of$(mockChannels)), }); const result = observeMyChannelMentionCount(database); expect(database.get).toHaveBeenCalledWith(MM_TABLES.SERVER.MY_CHANNEL); result.subscribe((value) => { expect(value).toEqual(5); }); }); it('should observe direct channels by term', () => { const term = '@test'; const mockCurrentUserId = 'user_id'; const mockChannels = [TestHelper.fakeMyChannelModel({id: 'channel1'})]; jest.mocked(observeCurrentUserId).mockReturnValue(of$(mockCurrentUserId)); mockQuery.mockReturnValue({ observe: jest.fn().mockReturnValue(of$(mockChannels)), }); const result = observeDirectChannelsByTerm(database, term); result.subscribe((value) => { expect(value).toEqual(mockChannels); }); }); it('should observe not direct channels by term', () => { const term = 'test'; const mockTeammateNameSetting = General.TEAMMATE_NAME_DISPLAY.SHOW_USERNAME; const mockUsers = [TestHelper.fakeUserModel({id: 'user1'})]; jest.mocked(observeTeammateNameDisplay).mockReturnValue(of$(mockTeammateNameSetting)); mockQuery.mockReturnValue({ observe: jest.fn().mockReturnValue(of$(mockUsers)), }); const result = observeNotDirectChannelsByTerm(database, term); result.subscribe((value) => { expect(value).toEqual(mockUsers); }); }); it('should observe joined channels by term', () => { const term = 'test'; const mockChannels = [TestHelper.fakeMyChannelModel({id: 'channel1'})]; mockQuery.mockReturnValue({ observe: jest.fn().mockReturnValue(of$(mockChannels)), }); const result = observeJoinedChannelsByTerm(database, term); expect(database.get).toHaveBeenCalledWith(MM_TABLES.SERVER.MY_CHANNEL); result.subscribe((value) => { expect(value).toEqual(mockChannels); }); }); it('should observe joined channels by term with @ mention', () => { const term = '@test'; const result = observeJoinedChannelsByTerm(database, term); result.subscribe((value) => { expect(value).toEqual([]); }); }); it('should observe archive channels by term', () => { const term = 'test'; const mockChannels = [TestHelper.fakeMyChannelModel({id: 'channel1'})]; mockQuery.mockReturnValue({ observe: jest.fn().mockReturnValue(of$(mockChannels)), }); const result = observeArchiveChannelsByTerm(database, term); expect(database.get).toHaveBeenCalledWith(MM_TABLES.SERVER.MY_CHANNEL); result.subscribe((value) => { expect(value).toEqual(mockChannels); }); }); it('should observe joined channels by term with @ mention', () => { const term = '@test'; const result = observeArchiveChannelsByTerm(database, term); result.subscribe((value) => { expect(value).toEqual([]); }); }); it('should observe channel settings', () => { const channelId = 'channel_id'; const mockSettings = TestHelper.fakeMyChannelSettingsModel({id: channelId}); jest.mocked(mockSettings.observe).mockReturnValue(of$(mockSettings)); mockQuery.mockReturnValue({ observe: jest.fn().mockReturnValue(of$([mockSettings])), }); const result = observeChannelSettings(database, channelId); expect(database.get).toHaveBeenCalledWith(MM_TABLES.SERVER.MY_CHANNEL_SETTINGS); result.subscribe((value) => { expect(value).toMatchObject({id: channelId}); }); }); it('should observe is muted setting', () => { const channelId = 'channel_id'; const mockSettings = TestHelper.fakeMyChannelSettingsModel({id: channelId, notifyProps: {mark_unread: General.MENTION}}); jest.mocked(mockSettings.observe).mockReturnValue(of$(mockSettings)); mockQuery.mockReturnValue({ observe: jest.fn().mockReturnValue(of$([mockSettings])), }); const result = observeIsMutedSetting(database, channelId); expect(database.get).toHaveBeenCalledWith(MM_TABLES.SERVER.MY_CHANNEL_SETTINGS); result.subscribe((value) => { expect(value).toEqual(true); }); }); it('should observe channels by last post at', () => { const myChannels = [TestHelper.fakeMyChannelModel({id: 'channel1'})]; const mockChannels = [TestHelper.fakeChannelModel({id: 'channel1'})]; mockQuery.mockReturnValue({ observe: jest.fn().mockReturnValue(of$(mockChannels)), }); const result = observeChannelsByLastPostAt(database, myChannels); expect(database.get).toHaveBeenCalledWith(MM_TABLES.SERVER.CHANNEL); result.subscribe((value) => { expect(value).toEqual(mockChannels); }); }); it('should observe channel members', () => { const channelId = 'channel_id'; const mockMembers = [ TestHelper.fakeChannelMembershipModel({id: 'member1'}), TestHelper.fakeChannelMembershipModel({id: 'member2'}), ]; mockQuery.mockReturnValue({ observe: jest.fn().mockReturnValue(of$(mockMembers)), }); const result = observeChannelMembers(database, channelId); expect(database.get).toHaveBeenCalledWith(MM_TABLES.SERVER.CHANNEL_MEMBERSHIP); expect(mockQuery).toHaveBeenCalledWith(Q.where('channel_id', channelId)); result.subscribe((value) => { expect(value).toEqual(mockMembers); }); }); }); describe('queryMyChannelsByChannelIds and queryMyChannelsWithAutotranslation', () => { const serverUrl = 'channelQueries.test.com'; let database: Database; let operator: ServerDataOperator; beforeEach(async () => { await DatabaseManager.init([serverUrl]); const serverDatabaseAndOperator = DatabaseManager.getServerDatabaseAndOperator(serverUrl); database = serverDatabaseAndOperator.database; operator = serverDatabaseAndOperator.operator; }); afterEach(async () => { await DatabaseManager.destroyServerDatabase(serverUrl); }); describe('queryMyChannelsByChannelIds', () => { it('should return myChannels for given ids', async () => { const channel1 = TestHelper.fakeChannel({id: 'ch1', team_id: 'team1'}); const channel2 = TestHelper.fakeChannel({id: 'ch2', team_id: 'team1'}); const myCh1 = TestHelper.fakeChannelMember({id: 'ch1', channel_id: 'ch1'}); const myCh2 = TestHelper.fakeChannelMember({id: 'ch2', channel_id: 'ch2'}); await operator.handleChannel({channels: [channel1, channel2], prepareRecordsOnly: false}); await operator.handleMyChannel({channels: [channel1, channel2], myChannels: [myCh1, myCh2], prepareRecordsOnly: false}); const result = queryMyChannelsByChannelIds(database, ['ch1', 'ch2']); const fetched = await result.fetch(); expect(fetched.length).toBe(2); expect(fetched.map((c) => c.id).sort()).toEqual(['ch1', 'ch2']); }); it('should return empty when no matching ids', async () => { const result = queryMyChannelsByChannelIds(database, ['nonexistent']); const fetched = await result.fetch(); expect(fetched.length).toBe(0); }); }); describe('queryMyChannelsWithAutotranslation', () => { it('should return only myChannels with channel autotranslation enabled and user autotranslation not disabled', async () => { const channel1 = TestHelper.fakeChannel({id: 'ch1', team_id: 'team1', autotranslation: true}); const channel2 = TestHelper.fakeChannel({id: 'ch2', team_id: 'team1', autotranslation: true}); const myCh1 = TestHelper.fakeChannelMember({id: 'ch1', channel_id: 'ch1', autotranslation_disabled: false}); const myCh2 = TestHelper.fakeChannelMember({id: 'ch2', channel_id: 'ch2', autotranslation_disabled: true}); await operator.handleChannel({channels: [channel1, channel2], prepareRecordsOnly: false}); await operator.handleMyChannel({channels: [channel1, channel2], myChannels: [myCh1, myCh2], prepareRecordsOnly: false}); const result = queryMyChannelsWithAutotranslation(database); const fetched = await result.fetch(); expect(fetched.length).toBe(1); expect(fetched[0].id).toBe('ch1'); expect(fetched[0].autotranslationDisabled).toBe(false); }); it('should exclude channels that have autotranslation disabled at channel level', async () => { const channel1 = TestHelper.fakeChannel({id: 'ch1', team_id: 'team1', autotranslation: false}); const myCh1 = TestHelper.fakeChannelMember({id: 'ch1', channel_id: 'ch1', autotranslation_disabled: false}); await operator.handleChannel({channels: [channel1], prepareRecordsOnly: false}); await operator.handleMyChannel({channels: [channel1], myChannels: [myCh1], prepareRecordsOnly: false}); const result = queryMyChannelsWithAutotranslation(database); const fetched = await result.fetch(); expect(fetched.length).toBe(0); }); it('should return empty when no myChannels have autotranslation disabled false', async () => { const channel1 = TestHelper.fakeChannel({id: 'ch1', team_id: 'team1', autotranslation: true}); const myCh1 = TestHelper.fakeChannelMember({id: 'ch1', channel_id: 'ch1', autotranslation_disabled: true}); await operator.handleChannel({channels: [channel1], prepareRecordsOnly: false}); await operator.handleMyChannel({channels: [channel1], myChannels: [myCh1], prepareRecordsOnly: false}); const result = queryMyChannelsWithAutotranslation(database); const fetched = await result.fetch(); expect(fetched.length).toBe(0); }); }); describe('observeChannelAutotranslation', () => { const channelId = 'ch_observe'; it('should emit true when EnableAutoTranslation is true and channel has autotranslation', async () => { await operator.handleConfigs({ configs: [ {id: 'EnableAutoTranslation', value: 'true'}, {id: 'RestrictDMAndGMAutotranslation', value: 'false'}, ], configsToDelete: [], prepareRecordsOnly: false, }); const channel = TestHelper.fakeChannel({id: channelId, team_id: 'team1', autotranslation: true}); await operator.handleChannel({channels: [channel], prepareRecordsOnly: false}); const subscriptionNext = jest.fn(); const result = observeChannelAutotranslation(database, channelId); result.subscribe({next: subscriptionNext}); expect(subscriptionNext).toHaveBeenCalledWith(true); }); it('should emit false when EnableAutoTranslation is false', async () => { await operator.handleConfigs({ configs: [ {id: 'EnableAutoTranslation', value: 'false'}, {id: 'RestrictDMAndGMAutotranslation', value: 'false'}, ], configsToDelete: [], prepareRecordsOnly: false, }); const channel = TestHelper.fakeChannel({id: channelId, team_id: 'team1', autotranslation: true}); await operator.handleChannel({channels: [channel], prepareRecordsOnly: false}); const subscriptionNext = jest.fn(); const result = observeChannelAutotranslation(database, channelId); result.subscribe({next: subscriptionNext}); expect(subscriptionNext).toHaveBeenCalledWith(false); }); it('should emit false when channel has autotranslation false', async () => { await operator.handleConfigs({ configs: [ {id: 'EnableAutoTranslation', value: 'true'}, {id: 'RestrictDMAndGMAutotranslation', value: 'false'}, ], configsToDelete: [], prepareRecordsOnly: false, }); const channel = TestHelper.fakeChannel({id: channelId, team_id: 'team1', autotranslation: false}); await operator.handleChannel({channels: [channel], prepareRecordsOnly: false}); const subscriptionNext = jest.fn(); const result = observeChannelAutotranslation(database, channelId); result.subscribe({next: subscriptionNext}); expect(subscriptionNext).toHaveBeenCalledWith(false); }); it('should emit false when channel is not found', async () => { await operator.handleConfigs({ configs: [ {id: 'EnableAutoTranslation', value: 'true'}, {id: 'RestrictDMAndGMAutotranslation', value: 'false'}, ], configsToDelete: [], prepareRecordsOnly: false, }); const subscriptionNext = jest.fn(); const result = observeChannelAutotranslation(database, 'nonexistent'); result.subscribe({next: subscriptionNext}); expect(subscriptionNext).toHaveBeenCalledWith(false); }); it('should emit false when channel is DM and RestrictDMAndGMAutotranslation is true', async () => { await operator.handleConfigs({ configs: [ {id: 'EnableAutoTranslation', value: 'true'}, {id: 'RestrictDMAndGMAutotranslation', value: 'true'}, ], configsToDelete: [], prepareRecordsOnly: false, }); const channel = TestHelper.fakeChannel({ id: channelId, type: General.DM_CHANNEL, autotranslation: true, }); await operator.handleChannel({channels: [channel], prepareRecordsOnly: false}); const subscriptionNext = jest.fn(); const result = observeChannelAutotranslation(database, channelId); result.subscribe({next: subscriptionNext}); expect(subscriptionNext).toHaveBeenCalledWith(false); }); it('should emit true when channel is DM and RestrictDMAndGMAutotranslation is false', async () => { await operator.handleConfigs({ configs: [ {id: 'EnableAutoTranslation', value: 'true'}, {id: 'RestrictDMAndGMAutotranslation', value: 'false'}, ], configsToDelete: [], prepareRecordsOnly: false, }); const channel = TestHelper.fakeChannel({ id: channelId, type: General.DM_CHANNEL, autotranslation: true, }); await operator.handleChannel({channels: [channel], prepareRecordsOnly: false}); const subscriptionNext = jest.fn(); const result = observeChannelAutotranslation(database, channelId); result.subscribe({next: subscriptionNext}); expect(subscriptionNext).toHaveBeenCalledWith(true); }); }); describe('observeIsChannelAutotranslated', () => { const channelId = 'ch_is_autotranslated'; it('should emit true when config and channel has autotranslation true and myChannel has autotranslation disabled false', async () => { await operator.handleConfigs({ configs: [ {id: 'EnableAutoTranslation', value: 'true'}, {id: 'RestrictDMAndGMAutotranslation', value: 'false'}, ], configsToDelete: [], prepareRecordsOnly: false, }); const channel = TestHelper.fakeChannel({id: channelId, team_id: 'team1', autotranslation: true}); const myChannel = TestHelper.fakeChannelMember({id: channelId, channel_id: channelId, autotranslation_disabled: false}); await operator.handleChannel({channels: [channel], prepareRecordsOnly: false}); await operator.handleMyChannel({channels: [channel], myChannels: [myChannel], prepareRecordsOnly: false}); const subscriptionNext = jest.fn(); const result = observeIsChannelAutotranslated(database, channelId); result.subscribe({next: subscriptionNext}); expect(subscriptionNext).toHaveBeenCalledWith(true); }); it('should emit false when channel autotranslation is false', async () => { await operator.handleConfigs({ configs: [ {id: 'EnableAutoTranslation', value: 'true'}, {id: 'RestrictDMAndGMAutotranslation', value: 'false'}, ], configsToDelete: [], prepareRecordsOnly: false, }); const channel = TestHelper.fakeChannel({id: channelId, team_id: 'team1', autotranslation: false}); const myChannel = TestHelper.fakeChannelMember({id: channelId, channel_id: channelId, autotranslation_disabled: false}); await operator.handleChannel({channels: [channel], prepareRecordsOnly: false}); await operator.handleMyChannel({channels: [channel], myChannels: [myChannel], prepareRecordsOnly: false}); const subscriptionNext = jest.fn(); const result = observeIsChannelAutotranslated(database, channelId); result.subscribe({next: subscriptionNext}); expect(subscriptionNext).toHaveBeenCalledWith(false); }); it('should emit false when myChannel autotranslation disabled is true', async () => { await operator.handleConfigs({ configs: [ {id: 'EnableAutoTranslation', value: 'true'}, {id: 'RestrictDMAndGMAutotranslation', value: 'false'}, ], configsToDelete: [], prepareRecordsOnly: false, }); const channel = TestHelper.fakeChannel({id: channelId, team_id: 'team1', autotranslation: true}); const myChannel = TestHelper.fakeChannelMember({id: channelId, channel_id: channelId, autotranslation_disabled: true}); await operator.handleChannel({channels: [channel], prepareRecordsOnly: false}); await operator.handleMyChannel({channels: [channel], myChannels: [myChannel], prepareRecordsOnly: false}); const subscriptionNext = jest.fn(); const result = observeIsChannelAutotranslated(database, channelId); result.subscribe({next: subscriptionNext}); expect(subscriptionNext).toHaveBeenCalledWith(false); }); it('should emit false when channel is DM and RestrictDMAndGMAutotranslation is true', async () => { await operator.handleConfigs({ configs: [ {id: 'EnableAutoTranslation', value: 'true'}, {id: 'RestrictDMAndGMAutotranslation', value: 'true'}, ], configsToDelete: [], prepareRecordsOnly: false, }); const channel = TestHelper.fakeChannel({ id: channelId, type: General.DM_CHANNEL, autotranslation: true, }); const myChannel = TestHelper.fakeChannelMember({id: channelId, channel_id: channelId, autotranslation_disabled: false}); await operator.handleChannel({channels: [channel], prepareRecordsOnly: false}); await operator.handleMyChannel({channels: [channel], myChannels: [myChannel], prepareRecordsOnly: false}); const subscriptionNext = jest.fn(); const result = observeIsChannelAutotranslated(database, channelId); result.subscribe({next: subscriptionNext}); expect(subscriptionNext).toHaveBeenCalledWith(false); }); it('should emit true when channel is DM and RestrictDMAndGMAutotranslation is false', async () => { await operator.handleConfigs({ configs: [ {id: 'EnableAutoTranslation', value: 'true'}, {id: 'RestrictDMAndGMAutotranslation', value: 'false'}, ], configsToDelete: [], prepareRecordsOnly: false, }); const channel = TestHelper.fakeChannel({ id: channelId, type: General.DM_CHANNEL, autotranslation: true, }); const myChannel = TestHelper.fakeChannelMember({id: channelId, channel_id: channelId, autotranslation_disabled: false}); await operator.handleChannel({channels: [channel], prepareRecordsOnly: false}); await operator.handleMyChannel({channels: [channel], myChannels: [myChannel], prepareRecordsOnly: false}); const subscriptionNext = jest.fn(); const result = observeIsChannelAutotranslated(database, channelId); result.subscribe({next: subscriptionNext}); expect(subscriptionNext).toHaveBeenCalledWith(true); }); }); });