Add tests to remaining actions/websocket files (#8495)
* test: Add comprehensive tests for websocket roles actions * test: Add comprehensive tests for websocket category actions * test: Add comprehensive tests for websocket preferences actions * test: Add comprehensive tests for websocket threads actions * test: Add comprehensive tests for websocket reactions actions * test: Add comprehensive tests for websocket system actions * test: Add comprehensive tests for websocket integrations actions
This commit is contained in:
parent
e8ed761d30
commit
575a689407
8 changed files with 1462 additions and 1 deletions
296
app/actions/websocket/category.test.ts
Normal file
296
app/actions/websocket/category.test.ts
Normal file
|
|
@ -0,0 +1,296 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {deleteCategory, storeCategories} from '@actions/local/category';
|
||||
import {fetchCategories} from '@actions/remote/category';
|
||||
import DatabaseManager from '@database/manager';
|
||||
import {queryCategoriesById} from '@queries/servers/categories';
|
||||
|
||||
import {handleCategoryCreatedEvent, handleCategoryUpdatedEvent, handleCategoryDeletedEvent, handleCategoryOrderUpdatedEvent, type WebsocketCategoriesMessage} from './category';
|
||||
|
||||
import type ServerDataOperator from '@database/operator/server_data_operator';
|
||||
|
||||
jest.mock('@actions/local/category');
|
||||
jest.mock('@actions/remote/category');
|
||||
jest.mock('@database/manager');
|
||||
jest.mock('@queries/servers/categories');
|
||||
|
||||
describe('WebSocket Category Actions', () => {
|
||||
const serverUrl = 'baseHandler.test.com';
|
||||
const teamId = 'team-id';
|
||||
const categoryId = 'category-id';
|
||||
|
||||
let operator: ServerDataOperator;
|
||||
let batchRecords: jest.SpyInstance;
|
||||
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
await DatabaseManager.init([serverUrl]);
|
||||
operator = DatabaseManager.serverDatabases[serverUrl]!.operator;
|
||||
batchRecords = jest.spyOn(operator, 'batchRecords').mockResolvedValue();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await DatabaseManager.destroyServerDatabase(serverUrl);
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe('handleCategoryCreatedEvent', () => {
|
||||
it('should handle valid category creation', async () => {
|
||||
const mockCategory = {
|
||||
id: categoryId,
|
||||
team_id: teamId,
|
||||
display_name: 'Test Category',
|
||||
};
|
||||
|
||||
const msg = {
|
||||
data: {
|
||||
category: JSON.stringify(mockCategory),
|
||||
},
|
||||
broadcast: {
|
||||
team_id: teamId,
|
||||
},
|
||||
} as WebsocketCategoriesMessage;
|
||||
|
||||
await handleCategoryCreatedEvent(serverUrl, msg);
|
||||
|
||||
expect(storeCategories).toHaveBeenCalledWith(serverUrl, [mockCategory]);
|
||||
});
|
||||
|
||||
it('should handle invalid JSON in category data', async () => {
|
||||
const msg = {
|
||||
data: {
|
||||
category: 'invalid-json',
|
||||
},
|
||||
broadcast: {
|
||||
team_id: teamId,
|
||||
},
|
||||
} as WebsocketCategoriesMessage;
|
||||
|
||||
await handleCategoryCreatedEvent(serverUrl, msg);
|
||||
|
||||
expect(fetchCategories).toHaveBeenCalledWith(serverUrl, teamId);
|
||||
});
|
||||
|
||||
it('should handle invalid JSON in category data - no team id', async () => {
|
||||
const msg = {
|
||||
data: {
|
||||
category: 'invalid-json',
|
||||
},
|
||||
broadcast: {},
|
||||
} as WebsocketCategoriesMessage;
|
||||
|
||||
await handleCategoryCreatedEvent(serverUrl, msg);
|
||||
|
||||
expect(fetchCategories).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle missing category data', async () => {
|
||||
const msg = {
|
||||
data: {},
|
||||
broadcast: {
|
||||
team_id: teamId,
|
||||
},
|
||||
} as WebsocketCategoriesMessage;
|
||||
|
||||
await handleCategoryCreatedEvent(serverUrl, msg);
|
||||
|
||||
expect(storeCategories).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('handleCategoryUpdatedEvent', () => {
|
||||
it('should handle valid category updates', async () => {
|
||||
const mockCategories = [{
|
||||
id: categoryId,
|
||||
team_id: teamId,
|
||||
display_name: 'Updated Category',
|
||||
}];
|
||||
|
||||
const msg = {
|
||||
data: {
|
||||
updatedCategories: JSON.stringify(mockCategories),
|
||||
},
|
||||
broadcast: {
|
||||
team_id: teamId,
|
||||
},
|
||||
} as WebsocketCategoriesMessage;
|
||||
|
||||
await handleCategoryUpdatedEvent(serverUrl, msg);
|
||||
|
||||
expect(storeCategories).toHaveBeenCalledWith(serverUrl, mockCategories);
|
||||
});
|
||||
|
||||
it('should handle invalid JSON in updated categories', async () => {
|
||||
const msg = {
|
||||
data: {
|
||||
updatedCategories: 'invalid-json',
|
||||
},
|
||||
broadcast: {
|
||||
team_id: teamId,
|
||||
},
|
||||
} as WebsocketCategoriesMessage;
|
||||
|
||||
await handleCategoryUpdatedEvent(serverUrl, msg);
|
||||
|
||||
expect(fetchCategories).toHaveBeenCalledWith(serverUrl, teamId, true);
|
||||
});
|
||||
|
||||
it('should handle invalid JSON in updated categories - no team id', async () => {
|
||||
const msg = {
|
||||
data: {
|
||||
updatedCategories: 'invalid-json',
|
||||
},
|
||||
broadcast: {},
|
||||
} as WebsocketCategoriesMessage;
|
||||
|
||||
await handleCategoryUpdatedEvent(serverUrl, msg);
|
||||
|
||||
expect(fetchCategories).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle missing updated categories data', async () => {
|
||||
const msg = {
|
||||
data: {},
|
||||
broadcast: {
|
||||
team_id: teamId,
|
||||
},
|
||||
} as WebsocketCategoriesMessage;
|
||||
|
||||
await handleCategoryUpdatedEvent(serverUrl, msg);
|
||||
|
||||
expect(storeCategories).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('handleCategoryDeletedEvent', () => {
|
||||
it('should handle category deletion', async () => {
|
||||
const msg = {
|
||||
data: {
|
||||
category_id: categoryId,
|
||||
},
|
||||
broadcast: {
|
||||
team_id: teamId,
|
||||
},
|
||||
} as WebsocketCategoriesMessage;
|
||||
|
||||
await handleCategoryDeletedEvent(serverUrl, msg);
|
||||
|
||||
expect(deleteCategory).toHaveBeenCalledWith(serverUrl, categoryId);
|
||||
expect(fetchCategories).toHaveBeenCalledWith(serverUrl, teamId);
|
||||
});
|
||||
|
||||
it('should handle missing category_id', async () => {
|
||||
const msg = {
|
||||
data: {},
|
||||
broadcast: {
|
||||
team_id: teamId,
|
||||
},
|
||||
} as WebsocketCategoriesMessage;
|
||||
|
||||
await handleCategoryDeletedEvent(serverUrl, msg);
|
||||
|
||||
expect(deleteCategory).not.toHaveBeenCalled();
|
||||
expect(fetchCategories).toHaveBeenCalledWith(serverUrl, teamId);
|
||||
});
|
||||
});
|
||||
|
||||
describe('handleCategoryOrderUpdatedEvent', () => {
|
||||
it('should handle empty order array', async () => {
|
||||
const msg = {
|
||||
data: {
|
||||
order: [],
|
||||
team_id: teamId,
|
||||
},
|
||||
broadcast: {
|
||||
team_id: teamId,
|
||||
},
|
||||
} as unknown as WebsocketCategoriesMessage;
|
||||
|
||||
await handleCategoryOrderUpdatedEvent(serverUrl, msg);
|
||||
expect(batchRecords).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should update category order', async () => {
|
||||
const mockCategories = [
|
||||
{id: 'cat1', prepareUpdate: jest.fn()},
|
||||
{id: 'cat2', prepareUpdate: jest.fn()},
|
||||
];
|
||||
|
||||
jest.mocked(queryCategoriesById).mockReturnValue({
|
||||
fetch: jest.fn().mockResolvedValue(mockCategories),
|
||||
} as any);
|
||||
|
||||
const msg = {
|
||||
data: {
|
||||
order: ['cat1', 'cat2'],
|
||||
team_id: teamId,
|
||||
},
|
||||
broadcast: {
|
||||
team_id: teamId,
|
||||
},
|
||||
} as WebsocketCategoriesMessage;
|
||||
|
||||
await handleCategoryOrderUpdatedEvent(serverUrl, msg);
|
||||
|
||||
expect(batchRecords).toHaveBeenCalled();
|
||||
mockCategories.forEach((cat) => {
|
||||
expect(cat.prepareUpdate).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle missing order data', async () => {
|
||||
const msg = {
|
||||
data: {
|
||||
team_id: teamId,
|
||||
},
|
||||
broadcast: {
|
||||
team_id: teamId,
|
||||
},
|
||||
} as WebsocketCategoriesMessage;
|
||||
|
||||
await handleCategoryOrderUpdatedEvent(serverUrl, msg);
|
||||
|
||||
expect(batchRecords).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle database error', async () => {
|
||||
jest.mocked(queryCategoriesById).mockImplementation(() => {
|
||||
throw new Error('Database error');
|
||||
});
|
||||
|
||||
const msg = {
|
||||
data: {
|
||||
order: ['cat1', 'cat2'],
|
||||
team_id: teamId,
|
||||
},
|
||||
broadcast: {
|
||||
team_id: teamId,
|
||||
},
|
||||
} as WebsocketCategoriesMessage;
|
||||
|
||||
await handleCategoryOrderUpdatedEvent(serverUrl, msg);
|
||||
|
||||
expect(fetchCategories).toHaveBeenCalledWith(serverUrl, teamId);
|
||||
});
|
||||
|
||||
it('should handle database error - no team id', async () => {
|
||||
jest.mocked(queryCategoriesById).mockImplementation(() => {
|
||||
throw new Error('Database error');
|
||||
});
|
||||
|
||||
const msg = {
|
||||
data: {
|
||||
order: ['cat1', 'cat2'],
|
||||
team_id: teamId,
|
||||
},
|
||||
broadcast: {},
|
||||
} as WebsocketCategoriesMessage;
|
||||
|
||||
await handleCategoryOrderUpdatedEvent(serverUrl, msg);
|
||||
|
||||
expect(fetchCategories).not.toHaveBeenCalledWith();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -7,7 +7,7 @@ import DatabaseManager from '@database/manager';
|
|||
import {queryCategoriesById} from '@queries/servers/categories';
|
||||
import {logError} from '@utils/log';
|
||||
|
||||
type WebsocketCategoriesMessage = {
|
||||
export type WebsocketCategoriesMessage = {
|
||||
broadcast: {
|
||||
team_id: string;
|
||||
};
|
||||
|
|
|
|||
90
app/actions/websocket/integrations.test.ts
Normal file
90
app/actions/websocket/integrations.test.ts
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import IntegrationsManager from '@managers/integrations_manager';
|
||||
import {getActiveServerUrl} from '@queries/app/servers';
|
||||
|
||||
import {handleOpenDialogEvent} from './integrations';
|
||||
|
||||
jest.mock('@managers/integrations_manager');
|
||||
jest.mock('@queries/app/servers');
|
||||
|
||||
describe('WebSocket Integrations Actions', () => {
|
||||
const serverUrl = 'baseHandler.test.com';
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
const mockManager = {
|
||||
setDialog: jest.fn(),
|
||||
};
|
||||
jest.spyOn(IntegrationsManager, 'getManager').mockReturnValue(mockManager as any);
|
||||
});
|
||||
|
||||
describe('handleOpenDialogEvent', () => {
|
||||
const mockDialog = {
|
||||
app_id: 'app1',
|
||||
trigger_id: 'trigger1',
|
||||
url: 'http://test.com',
|
||||
dialog: {
|
||||
callback_id: 'callback1',
|
||||
title: 'Test Dialog',
|
||||
introduction_text: 'Test Intro',
|
||||
elements: [],
|
||||
submit_label: 'Submit',
|
||||
notify_on_cancel: false,
|
||||
state: 'some-state',
|
||||
},
|
||||
} as InteractiveDialogConfig;
|
||||
|
||||
it('should handle missing dialog data', async () => {
|
||||
const msg = {
|
||||
data: {},
|
||||
} as WebSocketMessage;
|
||||
|
||||
await handleOpenDialogEvent(serverUrl, msg);
|
||||
|
||||
expect(IntegrationsManager.getManager).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle invalid JSON dialog data', async () => {
|
||||
const msg = {
|
||||
data: {
|
||||
dialog: '{invalid json',
|
||||
},
|
||||
} as WebSocketMessage;
|
||||
|
||||
await handleOpenDialogEvent(serverUrl, msg);
|
||||
|
||||
expect(IntegrationsManager.getManager).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not set dialog when server url does not match active server', async () => {
|
||||
jest.mocked(getActiveServerUrl).mockResolvedValue('different-server');
|
||||
|
||||
const msg = {
|
||||
data: {
|
||||
dialog: JSON.stringify(mockDialog),
|
||||
},
|
||||
} as WebSocketMessage;
|
||||
|
||||
await handleOpenDialogEvent(serverUrl, msg);
|
||||
|
||||
expect(IntegrationsManager.getManager).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should set dialog when server url matches active server', async () => {
|
||||
jest.mocked(getActiveServerUrl).mockResolvedValue(serverUrl);
|
||||
|
||||
const msg = {
|
||||
data: {
|
||||
dialog: JSON.stringify(mockDialog),
|
||||
},
|
||||
} as WebSocketMessage;
|
||||
|
||||
await handleOpenDialogEvent(serverUrl, msg);
|
||||
|
||||
expect(IntegrationsManager.getManager).toHaveBeenCalledWith(serverUrl);
|
||||
expect(IntegrationsManager.getManager(serverUrl).setDialog).toHaveBeenCalledWith(mockDialog);
|
||||
});
|
||||
});
|
||||
});
|
||||
206
app/actions/websocket/preferences.test.ts
Normal file
206
app/actions/websocket/preferences.test.ts
Normal file
|
|
@ -0,0 +1,206 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {updateDmGmDisplayName} from '@actions/local/channel';
|
||||
import {fetchPostById} from '@actions/remote/post';
|
||||
import {handleCRTToggled} from '@actions/remote/preference';
|
||||
import {Preferences} from '@constants';
|
||||
import DatabaseManager from '@database/manager';
|
||||
import {getPostById} from '@queries/servers/post';
|
||||
import {deletePreferences, differsFromLocalNameFormat, getHasCRTChanged} from '@queries/servers/preference';
|
||||
import EphemeralStore from '@store/ephemeral_store';
|
||||
|
||||
import {handlePreferenceChangedEvent, handlePreferencesChangedEvent, handlePreferencesDeletedEvent} from './preferences';
|
||||
|
||||
import type ServerDataOperator from '@database/operator/server_data_operator';
|
||||
|
||||
jest.mock('@actions/local/channel');
|
||||
jest.mock('@actions/remote/post');
|
||||
jest.mock('@actions/remote/preference');
|
||||
jest.mock('@database/manager');
|
||||
jest.mock('@queries/servers/post');
|
||||
jest.mock('@queries/servers/preference');
|
||||
jest.mock('@store/ephemeral_store');
|
||||
|
||||
describe('WebSocket Preferences Actions', () => {
|
||||
const serverUrl = 'baseHandler.test.com';
|
||||
let operator: ServerDataOperator;
|
||||
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
await DatabaseManager.init([serverUrl]);
|
||||
operator = DatabaseManager.serverDatabases[serverUrl]!.operator;
|
||||
jest.spyOn(operator, 'handlePreferences').mockResolvedValue([]);
|
||||
|
||||
jest.mocked(EphemeralStore.isEnablingCRT).mockReturnValue(false);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await DatabaseManager.destroyServerDatabase(serverUrl);
|
||||
});
|
||||
|
||||
describe('handlePreferenceChangedEvent', () => {
|
||||
const mockPreference = {
|
||||
category: Preferences.CATEGORIES.SAVED_POST,
|
||||
name: 'post1',
|
||||
user_id: 'user1',
|
||||
value: 'true',
|
||||
};
|
||||
|
||||
it('should handle enabling CRT', async () => {
|
||||
jest.mocked(EphemeralStore.isEnablingCRT).mockReturnValue(true);
|
||||
|
||||
const msg = {
|
||||
data: {
|
||||
preference: JSON.stringify(mockPreference),
|
||||
},
|
||||
} as WebSocketMessage;
|
||||
|
||||
await handlePreferenceChangedEvent(serverUrl, msg);
|
||||
|
||||
expect(operator.handlePreferences).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle preference change with saved post', async () => {
|
||||
const msg = {
|
||||
data: {
|
||||
preference: JSON.stringify(mockPreference),
|
||||
},
|
||||
} as WebSocketMessage;
|
||||
|
||||
jest.mocked(differsFromLocalNameFormat).mockResolvedValue(false);
|
||||
jest.mocked(getHasCRTChanged).mockResolvedValue(false);
|
||||
jest.mocked(getPostById).mockResolvedValue(undefined);
|
||||
|
||||
await handlePreferenceChangedEvent(serverUrl, msg);
|
||||
|
||||
expect(operator.handlePreferences).toHaveBeenCalledWith({
|
||||
prepareRecordsOnly: false,
|
||||
preferences: [mockPreference],
|
||||
});
|
||||
expect(fetchPostById).toHaveBeenCalledWith(serverUrl, 'post1', false);
|
||||
});
|
||||
|
||||
it('should handle name format changes', async () => {
|
||||
const msg = {
|
||||
data: {
|
||||
preference: JSON.stringify(mockPreference),
|
||||
},
|
||||
} as WebSocketMessage;
|
||||
|
||||
jest.mocked(differsFromLocalNameFormat).mockResolvedValue(true);
|
||||
jest.mocked(getHasCRTChanged).mockResolvedValue(false);
|
||||
|
||||
await handlePreferenceChangedEvent(serverUrl, msg);
|
||||
|
||||
expect(updateDmGmDisplayName).toHaveBeenCalledWith(serverUrl);
|
||||
});
|
||||
|
||||
it('should handle CRT changes', async () => {
|
||||
const msg = {
|
||||
data: {
|
||||
preference: JSON.stringify(mockPreference),
|
||||
},
|
||||
} as WebSocketMessage;
|
||||
|
||||
jest.mocked(differsFromLocalNameFormat).mockResolvedValue(false);
|
||||
jest.mocked(getHasCRTChanged).mockResolvedValue(true);
|
||||
|
||||
await handlePreferenceChangedEvent(serverUrl, msg);
|
||||
|
||||
expect(handleCRTToggled).toHaveBeenCalledWith(serverUrl);
|
||||
});
|
||||
});
|
||||
|
||||
describe('handlePreferencesChangedEvent', () => {
|
||||
const mockPreferences = [{
|
||||
category: Preferences.CATEGORIES.SAVED_POST,
|
||||
name: 'post1',
|
||||
user_id: 'user1',
|
||||
value: 'true',
|
||||
}];
|
||||
|
||||
it('should handle enabling CRT', async () => {
|
||||
jest.mocked(EphemeralStore.isEnablingCRT).mockReturnValue(true);
|
||||
|
||||
const msg = {
|
||||
data: {
|
||||
preferences: JSON.stringify(mockPreferences),
|
||||
},
|
||||
} as WebSocketMessage;
|
||||
|
||||
await handlePreferencesChangedEvent(serverUrl, msg);
|
||||
|
||||
expect(operator.handlePreferences).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle multiple preferences change', async () => {
|
||||
const msg = {
|
||||
data: {
|
||||
preferences: JSON.stringify(mockPreferences),
|
||||
},
|
||||
} as WebSocketMessage;
|
||||
|
||||
jest.mocked(differsFromLocalNameFormat).mockResolvedValue(false);
|
||||
jest.mocked(getHasCRTChanged).mockResolvedValue(false);
|
||||
jest.mocked(getPostById).mockResolvedValue(undefined);
|
||||
|
||||
await handlePreferencesChangedEvent(serverUrl, msg);
|
||||
|
||||
expect(operator.handlePreferences).toHaveBeenCalledWith({
|
||||
prepareRecordsOnly: false,
|
||||
preferences: mockPreferences,
|
||||
});
|
||||
expect(fetchPostById).toHaveBeenCalledWith(serverUrl, 'post1', false);
|
||||
});
|
||||
|
||||
it('should handle name format changes in bulk', async () => {
|
||||
const msg = {
|
||||
data: {
|
||||
preferences: JSON.stringify(mockPreferences),
|
||||
},
|
||||
} as WebSocketMessage;
|
||||
|
||||
jest.mocked(differsFromLocalNameFormat).mockResolvedValue(true);
|
||||
jest.mocked(getHasCRTChanged).mockResolvedValue(false);
|
||||
|
||||
await handlePreferencesChangedEvent(serverUrl, msg);
|
||||
|
||||
expect(updateDmGmDisplayName).toHaveBeenCalledWith(serverUrl);
|
||||
});
|
||||
});
|
||||
|
||||
describe('handlePreferencesDeletedEvent', () => {
|
||||
const mockPreferences = [{
|
||||
category: Preferences.CATEGORIES.SAVED_POST,
|
||||
name: 'post1',
|
||||
user_id: 'user1',
|
||||
value: 'true',
|
||||
}];
|
||||
|
||||
it('should delete preferences', async () => {
|
||||
const msg = {
|
||||
data: {
|
||||
preferences: JSON.stringify(mockPreferences),
|
||||
},
|
||||
} as WebSocketMessage;
|
||||
|
||||
await handlePreferencesDeletedEvent(serverUrl, msg);
|
||||
|
||||
expect(deletePreferences).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle invalid preferences data', async () => {
|
||||
const msg = {
|
||||
data: {
|
||||
preferences: 'invalid-json',
|
||||
},
|
||||
} as WebSocketMessage;
|
||||
|
||||
await handlePreferencesDeletedEvent(serverUrl, msg);
|
||||
|
||||
expect(deletePreferences).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
182
app/actions/websocket/reactions.test.ts
Normal file
182
app/actions/websocket/reactions.test.ts
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import DatabaseManager from '@database/manager';
|
||||
import {queryReaction} from '@queries/servers/reaction';
|
||||
|
||||
import {handleAddCustomEmoji, handleReactionAddedToPostEvent, handleReactionRemovedFromPostEvent} from './reactions';
|
||||
|
||||
jest.mock('@database/manager');
|
||||
jest.mock('@queries/servers/reaction');
|
||||
|
||||
describe('WebSocket Reactions Actions', () => {
|
||||
const serverUrl = 'baseHandler.test.com';
|
||||
const postId = 'post-id';
|
||||
const userId = 'user-id';
|
||||
const emojiName = 'smile';
|
||||
|
||||
let operator: any;
|
||||
let database: any;
|
||||
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
operator = {
|
||||
handleCustomEmojis: jest.fn(),
|
||||
handleReactions: jest.fn(),
|
||||
};
|
||||
|
||||
database = {
|
||||
write: jest.fn((callback) => callback()),
|
||||
};
|
||||
|
||||
DatabaseManager.getServerDatabaseAndOperator = jest.fn().mockReturnValue({
|
||||
database,
|
||||
operator,
|
||||
});
|
||||
|
||||
await DatabaseManager.init([serverUrl]);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await DatabaseManager.destroyServerDatabase(serverUrl);
|
||||
});
|
||||
|
||||
describe('handleAddCustomEmoji', () => {
|
||||
it('should handle custom emoji addition', async () => {
|
||||
const emoji = {
|
||||
id: 'emoji-id',
|
||||
name: 'custom-emoji',
|
||||
creator_id: userId,
|
||||
};
|
||||
|
||||
const msg = {
|
||||
data: {
|
||||
emoji: JSON.stringify(emoji),
|
||||
},
|
||||
} as WebSocketMessage;
|
||||
|
||||
await handleAddCustomEmoji(serverUrl, msg);
|
||||
|
||||
expect(operator.handleCustomEmojis).toHaveBeenCalledWith({
|
||||
prepareRecordsOnly: false,
|
||||
emojis: [emoji],
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle invalid emoji data gracefully', async () => {
|
||||
const msg = {
|
||||
data: {
|
||||
emoji: 'invalid-json',
|
||||
},
|
||||
} as WebSocketMessage;
|
||||
|
||||
await handleAddCustomEmoji(serverUrl, msg);
|
||||
|
||||
expect(operator.handleCustomEmojis).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('handleReactionAddedToPostEvent', () => {
|
||||
it('should handle reaction addition', async () => {
|
||||
const reaction = {
|
||||
user_id: userId,
|
||||
post_id: postId,
|
||||
emoji_name: emojiName,
|
||||
create_at: 123,
|
||||
};
|
||||
|
||||
const msg = {
|
||||
data: {
|
||||
reaction: JSON.stringify(reaction),
|
||||
},
|
||||
} as WebSocketMessage;
|
||||
|
||||
await handleReactionAddedToPostEvent(serverUrl, msg);
|
||||
|
||||
expect(operator.handleReactions).toHaveBeenCalledWith({
|
||||
prepareRecordsOnly: false,
|
||||
skipSync: true,
|
||||
postsReactions: [{
|
||||
post_id: postId,
|
||||
reactions: [reaction],
|
||||
}],
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle invalid reaction data gracefully', async () => {
|
||||
const msg = {
|
||||
data: {
|
||||
reaction: 'invalid-json',
|
||||
},
|
||||
} as WebSocketMessage;
|
||||
|
||||
await handleReactionAddedToPostEvent(serverUrl, msg);
|
||||
|
||||
expect(operator.handleReactions).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('handleReactionRemovedFromPostEvent', () => {
|
||||
it('should handle reaction removal', async () => {
|
||||
const reaction = {
|
||||
user_id: userId,
|
||||
post_id: postId,
|
||||
emoji_name: emojiName,
|
||||
};
|
||||
|
||||
const mockReactionModel = {
|
||||
destroyPermanently: jest.fn(),
|
||||
};
|
||||
|
||||
jest.mocked(queryReaction).mockReturnValue({
|
||||
fetch: jest.fn().mockResolvedValue([mockReactionModel]),
|
||||
} as any);
|
||||
|
||||
const msg = {
|
||||
data: {
|
||||
reaction: JSON.stringify(reaction),
|
||||
},
|
||||
} as WebSocketMessage;
|
||||
|
||||
await handleReactionRemovedFromPostEvent(serverUrl, msg);
|
||||
|
||||
expect(queryReaction).toHaveBeenCalledWith(database, emojiName, postId, userId);
|
||||
expect(mockReactionModel.destroyPermanently).toHaveBeenCalled();
|
||||
expect(database.write).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle non-existent reaction gracefully', async () => {
|
||||
jest.mocked(queryReaction).mockReturnValue({
|
||||
fetch: jest.fn().mockResolvedValue([]),
|
||||
} as any);
|
||||
|
||||
const msg = {
|
||||
data: {
|
||||
reaction: JSON.stringify({
|
||||
user_id: userId,
|
||||
post_id: postId,
|
||||
emoji_name: emojiName,
|
||||
}),
|
||||
},
|
||||
} as WebSocketMessage;
|
||||
|
||||
await handleReactionRemovedFromPostEvent(serverUrl, msg);
|
||||
|
||||
expect(database.write).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle invalid reaction data gracefully', async () => {
|
||||
const msg = {
|
||||
data: {
|
||||
reaction: 'invalid-json',
|
||||
},
|
||||
} as WebSocketMessage;
|
||||
|
||||
await handleReactionRemovedFromPostEvent(serverUrl, msg);
|
||||
|
||||
expect(queryReaction).not.toHaveBeenCalled();
|
||||
expect(database.write).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
309
app/actions/websocket/roles.test.ts
Normal file
309
app/actions/websocket/roles.test.ts
Normal file
|
|
@ -0,0 +1,309 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {fetchRolesIfNeeded} from '@actions/remote/role';
|
||||
import DatabaseManager from '@database/manager';
|
||||
import {getRoleById} from '@queries/servers/role';
|
||||
import {getCurrentUserId} from '@queries/servers/system';
|
||||
import {getCurrentUser} from '@queries/servers/user';
|
||||
|
||||
import {handleRoleUpdatedEvent, handleUserRoleUpdatedEvent, handleTeamMemberRoleUpdatedEvent} from './roles';
|
||||
|
||||
import type ServerDataOperator from '@database/operator/server_data_operator';
|
||||
import type RoleModel from '@typings/database/models/servers/role';
|
||||
import type UserModel from '@typings/database/models/servers/user';
|
||||
|
||||
jest.mock('@actions/remote/role');
|
||||
jest.mock('@database/manager');
|
||||
jest.mock('@queries/servers/role');
|
||||
jest.mock('@queries/servers/system');
|
||||
jest.mock('@queries/servers/user');
|
||||
|
||||
describe('WebSocket Roles Actions', () => {
|
||||
const serverUrl = 'baseHandler.test.com';
|
||||
const currentUserId = 'current-user-id';
|
||||
const roleId = 'role-id';
|
||||
const teamId = 'team-id';
|
||||
|
||||
let operator: ServerDataOperator;
|
||||
let batchRecords: jest.SpyInstance;
|
||||
let handleRole: jest.SpyInstance;
|
||||
let handleMyTeam: jest.SpyInstance;
|
||||
let handleTeamMemberships: jest.SpyInstance;
|
||||
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
await DatabaseManager.init([serverUrl]);
|
||||
operator = DatabaseManager.serverDatabases[serverUrl]!.operator;
|
||||
|
||||
batchRecords = jest.spyOn(operator, 'batchRecords').mockResolvedValue();
|
||||
handleRole = jest.spyOn(operator, 'handleRole').mockResolvedValue([{id: 'role1'} as RoleModel]);
|
||||
handleMyTeam = jest.spyOn(operator, 'handleMyTeam').mockResolvedValue([]);
|
||||
handleTeamMemberships = jest.spyOn(operator, 'handleTeamMemberships').mockResolvedValue([]);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await DatabaseManager.destroyServerDatabase(serverUrl);
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe('handleRoleUpdatedEvent', () => {
|
||||
it('should handle missing operator', async () => {
|
||||
DatabaseManager.serverDatabases = {};
|
||||
const msg = {
|
||||
data: {
|
||||
role: JSON.stringify({id: roleId}),
|
||||
},
|
||||
} as WebSocketMessage;
|
||||
await handleRoleUpdatedEvent(serverUrl, msg);
|
||||
expect(handleRole).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle missing role in database', async () => {
|
||||
jest.mocked(getRoleById).mockResolvedValue(undefined);
|
||||
const msg = {
|
||||
data: {
|
||||
role: JSON.stringify({id: roleId}),
|
||||
},
|
||||
} as WebSocketMessage;
|
||||
await handleRoleUpdatedEvent(serverUrl, msg);
|
||||
expect(handleRole).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should update existing role', async () => {
|
||||
const mockRole = {
|
||||
id: roleId,
|
||||
name: 'test_role',
|
||||
permissions: ['permission1'],
|
||||
};
|
||||
jest.mocked(getRoleById).mockResolvedValue({id: roleId} as RoleModel);
|
||||
const msg = {
|
||||
data: {
|
||||
role: JSON.stringify(mockRole),
|
||||
},
|
||||
} as WebSocketMessage;
|
||||
|
||||
await handleRoleUpdatedEvent(serverUrl, msg);
|
||||
|
||||
expect(handleRole).toHaveBeenCalledWith({
|
||||
roles: [mockRole],
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle invalid JSON in role data', async () => {
|
||||
jest.mocked(getRoleById).mockResolvedValue({id: roleId} as RoleModel);
|
||||
const msg = {
|
||||
data: {
|
||||
role: 'invalid json',
|
||||
},
|
||||
} as WebSocketMessage;
|
||||
|
||||
await handleRoleUpdatedEvent(serverUrl, msg);
|
||||
expect(handleRole).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('handleUserRoleUpdatedEvent', () => {
|
||||
it('should handle missing operator', async () => {
|
||||
DatabaseManager.serverDatabases = {};
|
||||
const msg = {
|
||||
data: {
|
||||
user_id: currentUserId,
|
||||
roles: 'role1 role2',
|
||||
},
|
||||
} as WebSocketMessage;
|
||||
await handleUserRoleUpdatedEvent(serverUrl, msg);
|
||||
expect(handleRole).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle different user', async () => {
|
||||
jest.mocked(getCurrentUserId).mockResolvedValue('different-user');
|
||||
const msg = {
|
||||
data: {
|
||||
user_id: currentUserId,
|
||||
roles: 'role1 role2',
|
||||
},
|
||||
} as WebSocketMessage;
|
||||
await handleUserRoleUpdatedEvent(serverUrl, msg);
|
||||
expect(handleRole).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should update roles and user', async () => {
|
||||
jest.mocked(getCurrentUserId).mockResolvedValue(currentUserId);
|
||||
jest.mocked(getCurrentUser).mockResolvedValue({
|
||||
prepareUpdate: jest.fn(),
|
||||
} as unknown as UserModel);
|
||||
jest.mocked(fetchRolesIfNeeded).mockResolvedValue({
|
||||
roles: [{id: 'role1'} as Role],
|
||||
});
|
||||
|
||||
const msg = {
|
||||
data: {
|
||||
user_id: currentUserId,
|
||||
roles: 'role1 role2',
|
||||
},
|
||||
} as WebSocketMessage;
|
||||
|
||||
await handleUserRoleUpdatedEvent(serverUrl, msg);
|
||||
|
||||
expect(handleRole).toHaveBeenCalled();
|
||||
expect(batchRecords).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle missing current user', async () => {
|
||||
jest.mocked(getCurrentUserId).mockResolvedValue(currentUserId);
|
||||
jest.mocked(getCurrentUser).mockResolvedValue(undefined);
|
||||
jest.mocked(fetchRolesIfNeeded).mockResolvedValue({
|
||||
roles: [{id: 'role1'} as Role],
|
||||
});
|
||||
|
||||
const msg = {
|
||||
data: {
|
||||
user_id: currentUserId,
|
||||
roles: 'role1',
|
||||
},
|
||||
} as WebSocketMessage;
|
||||
|
||||
await handleUserRoleUpdatedEvent(serverUrl, msg);
|
||||
|
||||
expect(handleRole).toHaveBeenCalled();
|
||||
expect(batchRecords).toHaveBeenCalledWith(expect.arrayContaining([]), 'handleUserRoleUpdatedEvent');
|
||||
});
|
||||
|
||||
it('should handle no new roles from fetchRolesIfNeeded', async () => {
|
||||
jest.mocked(getCurrentUserId).mockResolvedValue(currentUserId);
|
||||
jest.mocked(getCurrentUser).mockResolvedValue({
|
||||
prepareUpdate: jest.fn(),
|
||||
} as unknown as UserModel);
|
||||
jest.mocked(fetchRolesIfNeeded).mockResolvedValue({
|
||||
roles: [],
|
||||
});
|
||||
|
||||
const msg = {
|
||||
data: {
|
||||
user_id: currentUserId,
|
||||
roles: 'role1',
|
||||
},
|
||||
} as WebSocketMessage;
|
||||
|
||||
await handleUserRoleUpdatedEvent(serverUrl, msg);
|
||||
|
||||
expect(handleRole).not.toHaveBeenCalled();
|
||||
expect(batchRecords).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('handleTeamMemberRoleUpdatedEvent', () => {
|
||||
it('should handle missing operator', async () => {
|
||||
DatabaseManager.serverDatabases = {};
|
||||
const msg = {
|
||||
data: {
|
||||
member: JSON.stringify({
|
||||
user_id: currentUserId,
|
||||
team_id: teamId,
|
||||
roles: 'role1',
|
||||
delete_at: 0,
|
||||
}),
|
||||
},
|
||||
} as WebSocketMessage;
|
||||
await handleTeamMemberRoleUpdatedEvent(serverUrl, msg);
|
||||
expect(handleRole).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle deleted member', async () => {
|
||||
const msg = {
|
||||
data: {
|
||||
member: JSON.stringify({
|
||||
user_id: currentUserId,
|
||||
team_id: teamId,
|
||||
roles: 'role1',
|
||||
delete_at: 1234,
|
||||
}),
|
||||
},
|
||||
} as WebSocketMessage;
|
||||
await handleTeamMemberRoleUpdatedEvent(serverUrl, msg);
|
||||
expect(handleRole).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle different user', async () => {
|
||||
jest.mocked(getCurrentUserId).mockResolvedValue('different-user');
|
||||
const msg = {
|
||||
data: {
|
||||
member: JSON.stringify({
|
||||
user_id: currentUserId,
|
||||
team_id: teamId,
|
||||
roles: 'role1',
|
||||
delete_at: 0,
|
||||
}),
|
||||
},
|
||||
} as WebSocketMessage;
|
||||
await handleTeamMemberRoleUpdatedEvent(serverUrl, msg);
|
||||
expect(handleRole).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should update roles, myTeam and teamMembership', async () => {
|
||||
jest.mocked(getCurrentUserId).mockResolvedValue(currentUserId);
|
||||
jest.mocked(fetchRolesIfNeeded).mockResolvedValue({
|
||||
roles: [{id: 'role1'} as Role],
|
||||
});
|
||||
|
||||
const msg = {
|
||||
data: {
|
||||
member: JSON.stringify({
|
||||
user_id: currentUserId,
|
||||
team_id: teamId,
|
||||
roles: 'role1',
|
||||
delete_at: 0,
|
||||
}),
|
||||
},
|
||||
} as WebSocketMessage;
|
||||
|
||||
await handleTeamMemberRoleUpdatedEvent(serverUrl, msg);
|
||||
|
||||
expect(handleRole).toHaveBeenCalled();
|
||||
expect(handleMyTeam).toHaveBeenCalled();
|
||||
expect(handleTeamMemberships).toHaveBeenCalled();
|
||||
expect(batchRecords).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle invalid JSON in member data', async () => {
|
||||
const msg = {
|
||||
data: {
|
||||
member: 'invalid json',
|
||||
},
|
||||
} as WebSocketMessage;
|
||||
|
||||
await handleTeamMemberRoleUpdatedEvent(serverUrl, msg);
|
||||
expect(handleRole).not.toHaveBeenCalled();
|
||||
expect(handleMyTeam).not.toHaveBeenCalled();
|
||||
expect(handleTeamMemberships).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle no new roles from fetchRolesIfNeeded', async () => {
|
||||
jest.mocked(getCurrentUserId).mockResolvedValue(currentUserId);
|
||||
jest.mocked(fetchRolesIfNeeded).mockResolvedValue({
|
||||
roles: [],
|
||||
});
|
||||
|
||||
const msg = {
|
||||
data: {
|
||||
member: JSON.stringify({
|
||||
user_id: currentUserId,
|
||||
team_id: teamId,
|
||||
roles: 'role1',
|
||||
delete_at: 0,
|
||||
}),
|
||||
},
|
||||
} as WebSocketMessage;
|
||||
|
||||
await handleTeamMemberRoleUpdatedEvent(serverUrl, msg);
|
||||
|
||||
expect(handleRole).not.toHaveBeenCalled();
|
||||
expect(handleMyTeam).toHaveBeenCalled();
|
||||
expect(handleTeamMemberships).toHaveBeenCalled();
|
||||
expect(batchRecords).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
157
app/actions/websocket/system.test.ts
Normal file
157
app/actions/websocket/system.test.ts
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {updateDmGmDisplayName} from '@actions/local/channel';
|
||||
import {storeConfig} from '@actions/local/systems';
|
||||
import {SYSTEM_IDENTIFIERS} from '@constants/database';
|
||||
import DatabaseManager from '@database/manager';
|
||||
import {getConfig, getLicense} from '@queries/servers/system';
|
||||
|
||||
import {handleLicenseChangedEvent, handleConfigChangedEvent} from './system';
|
||||
|
||||
import type ServerDataOperator from '@database/operator/server_data_operator';
|
||||
|
||||
jest.mock('@actions/local/channel');
|
||||
jest.mock('@actions/local/systems');
|
||||
jest.mock('@database/manager');
|
||||
jest.mock('@queries/servers/system');
|
||||
|
||||
describe('WebSocket System Actions', () => {
|
||||
const serverUrl = 'baseHandler.test.com';
|
||||
|
||||
let operator: ServerDataOperator;
|
||||
let handleSystem: jest.SpyInstance;
|
||||
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
await DatabaseManager.init([serverUrl]);
|
||||
operator = DatabaseManager.serverDatabases[serverUrl]!.operator;
|
||||
handleSystem = jest.spyOn(operator, 'handleSystem');
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await DatabaseManager.destroyServerDatabase(serverUrl);
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe('handleLicenseChangedEvent', () => {
|
||||
it('should handle missing operator', async () => {
|
||||
DatabaseManager.serverDatabases = {};
|
||||
const msg = {
|
||||
data: {
|
||||
license: {
|
||||
LockTeammateNameDisplay: true,
|
||||
},
|
||||
},
|
||||
} as WebSocketMessage;
|
||||
await handleLicenseChangedEvent(serverUrl, msg);
|
||||
expect(handleSystem).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle license update with no display name change', async () => {
|
||||
const mockLicense = {
|
||||
LockTeammateNameDisplay: false,
|
||||
};
|
||||
|
||||
jest.mocked(getLicense).mockResolvedValue({
|
||||
LockTeammateNameDisplay: 'false',
|
||||
} as ClientLicense);
|
||||
|
||||
const msg = {
|
||||
data: {
|
||||
license: mockLicense,
|
||||
},
|
||||
} as WebSocketMessage;
|
||||
|
||||
await handleLicenseChangedEvent(serverUrl, msg);
|
||||
|
||||
expect(handleSystem).toHaveBeenCalledWith({
|
||||
systems: [{
|
||||
id: SYSTEM_IDENTIFIERS.LICENSE,
|
||||
value: JSON.stringify(mockLicense),
|
||||
}],
|
||||
prepareRecordsOnly: false,
|
||||
});
|
||||
expect(updateDmGmDisplayName).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle license update with display name change', async () => {
|
||||
const mockLicense = {
|
||||
LockTeammateNameDisplay: true,
|
||||
};
|
||||
|
||||
jest.mocked(getLicense).mockResolvedValue({
|
||||
LockTeammateNameDisplay: 'false',
|
||||
} as ClientLicense);
|
||||
|
||||
const msg = {
|
||||
data: {
|
||||
license: mockLicense,
|
||||
},
|
||||
} as WebSocketMessage;
|
||||
|
||||
await handleLicenseChangedEvent(serverUrl, msg);
|
||||
|
||||
expect(handleSystem).toHaveBeenCalled();
|
||||
expect(updateDmGmDisplayName).toHaveBeenCalledWith(serverUrl);
|
||||
});
|
||||
});
|
||||
|
||||
describe('handleConfigChangedEvent', () => {
|
||||
it('should handle missing operator', async () => {
|
||||
DatabaseManager.serverDatabases = {};
|
||||
const msg = {
|
||||
data: {
|
||||
config: {
|
||||
LockTeammateNameDisplay: true,
|
||||
},
|
||||
},
|
||||
} as WebSocketMessage;
|
||||
await handleConfigChangedEvent(serverUrl, msg);
|
||||
expect(storeConfig).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle config update with no display name change', async () => {
|
||||
const mockConfig = {
|
||||
LockTeammateNameDisplay: false,
|
||||
};
|
||||
|
||||
jest.mocked(getConfig).mockResolvedValue({
|
||||
LockTeammateNameDisplay: 'false',
|
||||
} as ClientConfig);
|
||||
|
||||
const msg = {
|
||||
data: {
|
||||
config: mockConfig,
|
||||
},
|
||||
} as WebSocketMessage;
|
||||
|
||||
await handleConfigChangedEvent(serverUrl, msg);
|
||||
|
||||
expect(storeConfig).toHaveBeenCalledWith(serverUrl, mockConfig);
|
||||
expect(updateDmGmDisplayName).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle config update with display name change', async () => {
|
||||
const mockConfig = {
|
||||
LockTeammateNameDisplay: true,
|
||||
};
|
||||
|
||||
jest.mocked(getConfig).mockResolvedValue({
|
||||
LockTeammateNameDisplay: 'false',
|
||||
} as ClientConfig);
|
||||
|
||||
const msg = {
|
||||
data: {
|
||||
config: mockConfig,
|
||||
},
|
||||
} as WebSocketMessage;
|
||||
|
||||
await handleConfigChangedEvent(serverUrl, msg);
|
||||
|
||||
expect(storeConfig).toHaveBeenCalledWith(serverUrl, mockConfig);
|
||||
expect(updateDmGmDisplayName).toHaveBeenCalledWith(serverUrl);
|
||||
});
|
||||
});
|
||||
});
|
||||
221
app/actions/websocket/threads.test.ts
Normal file
221
app/actions/websocket/threads.test.ts
Normal file
|
|
@ -0,0 +1,221 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {markTeamThreadsAsRead, processReceivedThreads, updateThread} from '@actions/local/thread';
|
||||
import DatabaseManager from '@database/manager';
|
||||
import {getCurrentTeamId} from '@queries/servers/system';
|
||||
import EphemeralStore from '@store/ephemeral_store';
|
||||
|
||||
import {handleThreadUpdatedEvent, handleThreadReadChangedEvent, handleThreadFollowChangedEvent} from './threads';
|
||||
|
||||
jest.mock('@actions/local/thread');
|
||||
jest.mock('@database/manager');
|
||||
jest.mock('@queries/servers/system');
|
||||
jest.mock('@store/ephemeral_store');
|
||||
|
||||
describe('WebSocket Threads Actions', () => {
|
||||
const serverUrl = 'baseHandler.test.com';
|
||||
const teamId = 'team-id';
|
||||
const threadId = 'thread-id';
|
||||
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
await DatabaseManager.init([serverUrl]);
|
||||
DatabaseManager.getServerDatabaseAndOperator = jest.fn().mockReturnValue({
|
||||
database: {},
|
||||
operator: {},
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await DatabaseManager.destroyServerDatabase(serverUrl);
|
||||
});
|
||||
|
||||
describe('handleThreadUpdatedEvent', () => {
|
||||
it('should process received thread', async () => {
|
||||
const mockThread = {
|
||||
id: threadId,
|
||||
reply_count: 3,
|
||||
};
|
||||
|
||||
const msg = {
|
||||
data: {
|
||||
thread: JSON.stringify(mockThread),
|
||||
},
|
||||
broadcast: {
|
||||
team_id: teamId,
|
||||
},
|
||||
} as WebSocketMessage;
|
||||
|
||||
jest.mocked(getCurrentTeamId).mockResolvedValue(teamId);
|
||||
jest.mocked(processReceivedThreads);
|
||||
|
||||
await handleThreadUpdatedEvent(serverUrl, msg);
|
||||
|
||||
expect(processReceivedThreads).toHaveBeenCalledWith(
|
||||
serverUrl,
|
||||
[{...mockThread, is_following: true}],
|
||||
teamId,
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle missing team_id', async () => {
|
||||
const mockThread = {
|
||||
id: threadId,
|
||||
reply_count: 3,
|
||||
};
|
||||
|
||||
const msg = {
|
||||
data: {
|
||||
thread: JSON.stringify(mockThread),
|
||||
},
|
||||
broadcast: {},
|
||||
} as WebSocketMessage;
|
||||
|
||||
jest.mocked(getCurrentTeamId).mockResolvedValue(teamId);
|
||||
|
||||
await handleThreadUpdatedEvent(serverUrl, msg);
|
||||
|
||||
expect(processReceivedThreads).toHaveBeenCalledWith(
|
||||
serverUrl,
|
||||
[{...mockThread, is_following: true}],
|
||||
teamId,
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle error gracefully', async () => {
|
||||
const msg = {
|
||||
data: {
|
||||
thread: 'invalid-json',
|
||||
},
|
||||
broadcast: {},
|
||||
} as WebSocketMessage;
|
||||
|
||||
await handleThreadUpdatedEvent(serverUrl, msg);
|
||||
|
||||
expect(processReceivedThreads).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('handleThreadReadChangedEvent', () => {
|
||||
it('should update thread when thread_id is present', async () => {
|
||||
const timestamp = 1234567890;
|
||||
const msg = {
|
||||
data: {
|
||||
thread_id: threadId,
|
||||
timestamp,
|
||||
unread_mentions: 2,
|
||||
unread_replies: 5,
|
||||
},
|
||||
broadcast: {
|
||||
team_id: teamId,
|
||||
},
|
||||
} as WebSocketMessage<ThreadReadChangedData>;
|
||||
|
||||
jest.mocked(EphemeralStore.getCurrentThreadId).mockReturnValue('different-thread');
|
||||
|
||||
await handleThreadReadChangedEvent(serverUrl, msg);
|
||||
|
||||
expect(updateThread).toHaveBeenCalledWith(serverUrl, threadId, {
|
||||
unread_mentions: 2,
|
||||
unread_replies: 5,
|
||||
last_viewed_at: timestamp,
|
||||
viewed_at: timestamp,
|
||||
});
|
||||
});
|
||||
|
||||
it('should not update viewed_at when thread is currently visible', async () => {
|
||||
const timestamp = 1234567890;
|
||||
const msg = {
|
||||
data: {
|
||||
thread_id: threadId,
|
||||
timestamp,
|
||||
unread_mentions: 2,
|
||||
unread_replies: 5,
|
||||
},
|
||||
broadcast: {
|
||||
team_id: teamId,
|
||||
},
|
||||
} as WebSocketMessage<ThreadReadChangedData>;
|
||||
|
||||
jest.mocked(EphemeralStore.getCurrentThreadId).mockReturnValue(threadId);
|
||||
|
||||
await handleThreadReadChangedEvent(serverUrl, msg);
|
||||
|
||||
expect(updateThread).toHaveBeenCalledWith(serverUrl, threadId, {
|
||||
unread_mentions: 2,
|
||||
unread_replies: 5,
|
||||
last_viewed_at: timestamp,
|
||||
});
|
||||
});
|
||||
|
||||
it('should mark team threads as read when thread_id is missing', async () => {
|
||||
const msg = {
|
||||
data: {
|
||||
timestamp: 1234567890,
|
||||
},
|
||||
broadcast: {
|
||||
team_id: teamId,
|
||||
},
|
||||
} as WebSocketMessage<ThreadReadChangedData>;
|
||||
|
||||
await handleThreadReadChangedEvent(serverUrl, msg);
|
||||
|
||||
expect(markTeamThreadsAsRead).toHaveBeenCalledWith(serverUrl, teamId);
|
||||
});
|
||||
|
||||
it('should handle error gracefully', async () => {
|
||||
jest.mocked(updateThread).mockRejectedValue(new Error('test error'));
|
||||
|
||||
const msg = {
|
||||
data: {
|
||||
thread_id: threadId,
|
||||
timestamp: 1234567890,
|
||||
},
|
||||
broadcast: {
|
||||
team_id: teamId,
|
||||
},
|
||||
} as WebSocketMessage<ThreadReadChangedData>;
|
||||
|
||||
await handleThreadReadChangedEvent(serverUrl, msg);
|
||||
|
||||
expect(updateThread).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('handleThreadFollowChangedEvent', () => {
|
||||
it('should update thread following state', async () => {
|
||||
const msg = {
|
||||
data: {
|
||||
thread_id: threadId,
|
||||
state: true,
|
||||
reply_count: 5,
|
||||
},
|
||||
} as WebSocketMessage;
|
||||
|
||||
await handleThreadFollowChangedEvent(serverUrl, msg);
|
||||
|
||||
expect(updateThread).toHaveBeenCalledWith(serverUrl, threadId, {
|
||||
is_following: true,
|
||||
reply_count: 5,
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle error gracefully', async () => {
|
||||
jest.mocked(updateThread).mockRejectedValue(new Error('test error'));
|
||||
|
||||
const msg = {
|
||||
data: {
|
||||
thread_id: threadId,
|
||||
state: false,
|
||||
reply_count: 3,
|
||||
},
|
||||
} as WebSocketMessage;
|
||||
|
||||
await handleThreadFollowChangedEvent(serverUrl, msg);
|
||||
|
||||
expect(updateThread).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
Reference in a new issue