Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Websocket extension #8585

Merged
merged 8 commits into from
Feb 17, 2025
3 changes: 2 additions & 1 deletion app/actions/local/scheduled_post.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
// See LICENSE.txt for license information.

import DatabaseManager from '@database/manager';
import ScheduledPostModel from '@typings/database/models/servers/scheduled_post';
import {logError} from '@utils/log';

export async function handleScheduledPosts(serverUrl: string, actionType: string, scheduledPosts: ScheduledPost[], prepareRecordsOnly = false) {
export async function handleScheduledPosts(serverUrl: string, actionType: string, scheduledPosts: ScheduledPost[], prepareRecordsOnly = false): Promise<{models?: ScheduledPostModel[]; error?: any}> {
if (!scheduledPosts.length) {
return {models: undefined};
}
Expand Down
26 changes: 21 additions & 5 deletions app/actions/remote/scheduled_post.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,18 +65,32 @@ const throwFunc = () => {
throw Error('error');
};

const mockConnectionId = 'mock-connection-id';
const mockClient = {
createScheduledPost: jest.fn(() => ({...scheduledPost})),
createScheduledPost: jest.fn((post, connId) => ({...scheduledPost, connectionId: connId})),
getScheduledPostsForTeam: jest.fn(() => Promise.resolve({...scheduledPostsResponse})),
deleteScheduledPost: jest.fn((scheduledPostId) => {
return Promise.resolve(scheduledPostsResponse.bar.find((post) => post.id === scheduledPostId));
deleteScheduledPost: jest.fn((scheduledPostId, connId) => {
const post = scheduledPostsResponse.bar.find((p) => p.id === scheduledPostId);
return Promise.resolve({...post, connectionId: connId});
}),
};

const mockWebSocketClient = {
getConnectionId: jest.fn(() => mockConnectionId),
};
jest.mock('@queries/servers/system', () => ({
getConfigValue: jest.fn(),
getCurrentTeamId: jest.fn(),
}));

jest.mock('@managers/websocket_manager', () => ({

// default: {
// getClient: jest.fn(() => mockWebSocketClient),
// },
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unneeded comment?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

indeed, done.

getClient: jest.fn(() => mockWebSocketClient),
}));

const mockedGetConfigValue = jest.mocked(getConfigValue);

beforeAll(() => {
Expand Down Expand Up @@ -108,6 +122,7 @@ describe('scheduled_post', () => {
expect(result.error).toBeUndefined();
expect(result.data).toBe(true);
expect(result!.response!.id).toBe(scheduledPost.id);
expect(mockClient.createScheduledPost).toHaveBeenCalledWith(scheduledPost, mockConnectionId);
});

it('createScheduledPost - request error', async () => {
Expand Down Expand Up @@ -170,11 +185,12 @@ describe('deleteScheduledPost', () => {
it('delete Schedule post - handle scheduled post enabled', async () => {
const spyHandleScheduledPosts = jest.spyOn(operator, 'handleScheduledPosts');
const result = await deleteScheduledPost(serverUrl, 'scheduled_post_id');
expect(result.scheduledPost).toEqual(scheduledPostsResponse.bar[0]);
expect(result.scheduledPost).toEqual({...scheduledPostsResponse.bar[0], connectionId: mockConnectionId});
expect(spyHandleScheduledPosts).toHaveBeenCalledWith({
actionType: ActionType.SCHEDULED_POSTS.DELETE_SCHEDULED_POST,
scheduledPosts: [scheduledPostsResponse.bar[0]],
scheduledPosts: [{...scheduledPostsResponse.bar[0], connectionId: mockConnectionId}],
prepareRecordsOnly: false,
});
expect(mockClient.deleteScheduledPost).toHaveBeenCalledWith('scheduled_post_id', mockConnectionId);
});
});
5 changes: 4 additions & 1 deletion app/actions/remote/scheduled_post.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,15 @@ import {forceLogoutIfNecessary} from './session';

import type {CreateResponse} from '@hooks/handle_send_message';

export async function createScheduledPost(serverUrl: string, schedulePost: ScheduledPost, connectionId?: string): Promise<CreateResponse> {
export async function createScheduledPost(serverUrl: string, schedulePost: ScheduledPost): Promise<CreateResponse> {
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
if (!operator) {
return {error: `${serverUrl} database not found`};
}

const ws = websocketManager.getClient(serverUrl);
const connectionId = ws?.getConnectionId();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0/5 nitpick

Suggested change
const ws = websocketManager.getClient(serverUrl);
const connectionId = ws?.getConnectionId();
const connectionId = websocketManager.getClient(serverUrl)?.getConnectionId();

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.


try {
const client = NetworkManager.getClient(serverUrl);
const response = await client.createScheduledPost(schedulePost, connectionId);
Expand Down
87 changes: 43 additions & 44 deletions app/actions/websocket/scheduled_post.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,39 +7,23 @@ import DatabaseManager from '@database/manager';
import {handleCreateOrUpdateScheduledPost, handleDeleteScheduledPost} from './scheduled_post';

import type ServerDataOperator from '@database/operator/server_data_operator';
import type ScheduledPostModel from '@typings/database/models/servers/scheduled_post';

const serverUrl = 'baseHandler.test.com';
let operator: ServerDataOperator;

const scheduledPosts = [
{
channel_id: 'channel_id',
error_code: '',
files: [],
id: 'scheduled_post_id',
message: 'test scheduled post',
metadata: {},
processed_at: 0,
root_id: '',
scheduled_at: 123,
update_at: 456,
user_id: '',
},
{
id: 'scheduled_post_id_2',
channel_id: 'channel_id',
root_id: '',
message: 'test scheduled post 2',
files: [],
metadata: {},
scheduled_at: 123,
user_id: 'user_id',
processed_at: 0,
update_at: 456,
error_code: '',
},
];
const scheduledPost = {
channel_id: 'channel_id',
error_code: '',
files: [],
id: 'scheduled_post_id',
message: 'test scheduled post',
metadata: {},
processed_at: 0,
root_id: '',
scheduled_at: 123,
update_at: 456,
user_id: '',
};

beforeEach(async () => {
await DatabaseManager.init([serverUrl]);
Expand All @@ -51,48 +35,63 @@ afterEach(async () => {
});

describe('handleCreateOrUpdateSchedulePost', () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nicer to have the tests check if the posts were really created / deleted in the database, but 0/5

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

it('handleCreateOrUpdateScheduledPost - handle not found database', async () => {
const {error} = await handleCreateOrUpdateScheduledPost('foo', {data: {scheduled_post: JSON.stringify(scheduledPosts[0])}} as WebSocketMessage) as {error: Error};
it('handleCreateOrUpdateScheduledPost - handle empty payload', async () => {
const {error} = await handleCreateOrUpdateScheduledPost('foo', {data: {scheduledPost: JSON.stringify(scheduledPost)}} as WebSocketMessage);
expect(error.message).toBe('foo database not found');
});

it('handleCreateOrUpdateScheduledPost - wrong websocket scheduled post message', async () => {
const {error} = await handleCreateOrUpdateScheduledPost('foo', {} as WebSocketMessage) as {error: Error};
expect(error.message).toBe('Cannot read properties of undefined (reading \'scheduled_post\')');
const {models, error} = await handleCreateOrUpdateScheduledPost('foo', {} as WebSocketMessage);
expect(error).toBeUndefined();
expect(models).toBeUndefined();
});

it('handleCreateOrUpdateScheduledPost - no scheduled post', async () => {
const {models} = await handleCreateOrUpdateScheduledPost(serverUrl, {data: {scheduled_post: ''}} as WebSocketMessage) as {models: undefined};
const {models} = await handleCreateOrUpdateScheduledPost(serverUrl, {data: {scheduledPost: ''}} as WebSocketMessage);
expect(models).toBeUndefined();
});

it('handleCreateOrUpdateScheduledPost - success', async () => {
const {models} = await handleCreateOrUpdateScheduledPost(serverUrl, {data: {scheduled_post: JSON.stringify(scheduledPosts[0])}} as WebSocketMessage) as {models: ScheduledPostModel[]};
expect(models[0].id).toEqual(scheduledPosts[0].id);
const {models} = await handleCreateOrUpdateScheduledPost(serverUrl, {data: {scheduledPost: JSON.stringify(scheduledPost)}} as WebSocketMessage);
expect(models).toBeDefined();
expect(models![0].id).toEqual(scheduledPost.id);
});

it('handleCreateOrUpdateScheduledPost - should return error for invalid JSON payload', async () => {
const {models, error} = await handleCreateOrUpdateScheduledPost(serverUrl, {data: {scheduledPost: 'invalid_json'}} as WebSocketMessage);
expect(models).toBeUndefined();
expect(error).toBeDefined();
});
});

describe('handleDeleteScheduledPost', () => {
it('handleDeleteScheduledPost - handle not found database', async () => {
const {error} = await handleDeleteScheduledPost('foo', {} as WebSocketMessage) as {error: unknown};
expect(error).toBeTruthy();
it('handleDeleteScheduledPost - handle empty payload', async () => {
const {error, models} = await handleDeleteScheduledPost('foo', {} as WebSocketMessage);
expect(error).toBeUndefined();
expect(models).toBeUndefined();
});

it('handleDeleteScheduledPost - no scheduled post', async () => {
const {models} = await handleDeleteScheduledPost(serverUrl, {data: {scheduled_post: ''}} as WebSocketMessage) as {models: undefined};
const {models} = await handleDeleteScheduledPost(serverUrl, {data: {scheduledPost: ''}} as WebSocketMessage);
expect(models).toBeUndefined();
});

it('handleDeleteScheduledPost - success', async () => {
await operator.handleScheduledPosts({
actionType: ActionType.SCHEDULED_POSTS.CREATE_OR_UPDATED_SCHEDULED_POST,
scheduledPosts: [scheduledPosts[0]],
scheduledPosts: [scheduledPost],
prepareRecordsOnly: false,
});

const scheduledPost = scheduledPosts[0];
const deletedRecord = await handleDeleteScheduledPost(serverUrl, {data: {scheduledPost: JSON.stringify(scheduledPost)}} as WebSocketMessage);
expect(deletedRecord.models).toBeDefined();
expect(deletedRecord!.models!.length).toBe(1);
expect(deletedRecord!.models![0].id).toBe(scheduledPost.id);
});

const deletedRecord = await handleDeleteScheduledPost(serverUrl, {data: {scheduled_post: JSON.stringify(scheduledPost)}} as WebSocketMessage) as {models: ScheduledPostModel[]};
expect(deletedRecord.models[0].id).toBe(scheduledPost.id);
it('handleDeleteScheduledPost - should return error for invalid JSON payload', async () => {
const {models, error} = await handleDeleteScheduledPost(serverUrl, {data: {scheduledPost: 'invalid_json'}} as WebSocketMessage);
expect(models).toBeUndefined();
expect(error).toBeDefined();
});
});
10 changes: 5 additions & 5 deletions app/actions/websocket/scheduled_post.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ import {ActionType} from '@constants';
import {logError} from '@utils/log';

export type ScheduledPostWebsocketEventPayload = {
scheduled_post: string;
scheduledPost: string;
}

export async function handleCreateOrUpdateScheduledPost(serverUrl: string, msg: WebSocketMessage<ScheduledPostWebsocketEventPayload>, prepareRecordsOnly = false) {
try {
const scheduledPost: ScheduledPost = JSON.parse(msg.data.scheduled_post);
return handleScheduledPosts(serverUrl, ActionType.SCHEDULED_POSTS.CREATE_OR_UPDATED_SCHEDULED_POST, [scheduledPost], prepareRecordsOnly);
const scheduledPost: ScheduledPost[] = msg?.data?.scheduledPost ? [JSON.parse(msg.data.scheduledPost)] : [];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think msg is defined in this context. Not so sure about data.

Suggested change
const scheduledPost: ScheduledPost[] = msg?.data?.scheduledPost ? [JSON.parse(msg.data.scheduledPost)] : [];
const scheduledPost: ScheduledPost[] = msg.data?.scheduledPost ? [JSON.parse(msg.data.scheduledPost)] : [];

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

return handleScheduledPosts(serverUrl, ActionType.SCHEDULED_POSTS.CREATE_OR_UPDATED_SCHEDULED_POST, scheduledPost, prepareRecordsOnly);
} catch (error) {
logError('handleCreateOrUpdateScheduledPost cannot handle scheduled post added/update websocket event', error);
return {error};
Expand All @@ -21,8 +21,8 @@ export async function handleCreateOrUpdateScheduledPost(serverUrl: string, msg:

export async function handleDeleteScheduledPost(serverUrl: string, msg: WebSocketMessage<ScheduledPostWebsocketEventPayload>, prepareRecordsOnly = false) {
try {
const scheduledPost: ScheduledPost = JSON.parse(msg.data.scheduled_post);
return handleScheduledPosts(serverUrl, ActionType.SCHEDULED_POSTS.DELETE_SCHEDULED_POST, [scheduledPost], prepareRecordsOnly);
const scheduledPost: ScheduledPost[] = msg?.data?.scheduledPost ? [JSON.parse(msg.data.scheduledPost)] : [];
return handleScheduledPosts(serverUrl, ActionType.SCHEDULED_POSTS.DELETE_SCHEDULED_POST, scheduledPost, prepareRecordsOnly);
} catch (error) {
logError('handleDeleteScheduledPost cannot handle scheduled post deleted websocket event', error);
return {error};
Expand Down
Loading