diff --git a/app/actions/websocket/burn_on_read.test.ts b/app/actions/websocket/burn_on_read.test.ts index a07c1b5b92..3953f7c260 100644 --- a/app/actions/websocket/burn_on_read.test.ts +++ b/app/actions/websocket/burn_on_read.test.ts @@ -1,15 +1,18 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. +import {removePost} from '@actions/local/post'; import {handleNewPostEvent, handlePostEdited} from '@actions/websocket/posts'; +import {PostTypes} from '@constants/post'; import DatabaseManager from '@database/manager'; import {getPostById} from '@queries/servers/post'; import TestHelper from '@test/test_helper'; -import {handleBoRPostRevealedEvent} from './burn_on_read'; +import {handleBoRPostRevealedEvent, handleBoRPostBurnedEvent} from './burn_on_read'; jest.mock('@actions/websocket/posts'); jest.mock('@queries/servers/post'); +jest.mock('@actions/local/post'); const serverUrl = 'burnOnRead.test.com'; @@ -19,6 +22,7 @@ describe('WebSocket Burn on Read Actions', () => { const mockedGetPostById = jest.mocked(getPostById); const mockedHandleNewPostEvent = jest.mocked(handleNewPostEvent); const mockedHandlePostEdited = jest.mocked(handlePostEdited); + const mockedRemovePost = jest.mocked(removePost); beforeEach(async () => { await DatabaseManager.init([serverUrl]); @@ -99,10 +103,87 @@ describe('WebSocket Burn on Read Actions', () => { const result = await handleBoRPostRevealedEvent(serverUrl, invalidJsonMsg); - expect(result).toBeNull(); + expect(result).toEqual({}); expect(mockedGetPostById).not.toHaveBeenCalled(); expect(mockedHandleNewPostEvent).not.toHaveBeenCalled(); expect(mockedHandlePostEdited).not.toHaveBeenCalled(); }); }); + + describe('handleBoRPostBurnedEvent', () => { + const burnOnReadPost = TestHelper.fakePostModel({ + id: 'post1', + type: PostTypes.BURN_ON_READ, + }); + + const regularPost = TestHelper.fakePostModel({ + id: 'post2', + type: '', + }); + + const msg = { + data: { + post_id: 'post1', + }, + } as WebSocketMessage; + + it('should remove burn-on-read post when it exists locally', async () => { + mockedGetPostById.mockResolvedValue(burnOnReadPost); + + const result = await handleBoRPostBurnedEvent(serverUrl, msg); + + expect(mockedGetPostById).toHaveBeenCalledWith(expect.any(Object), 'post1'); + expect(mockedRemovePost).toHaveBeenCalledWith(serverUrl, burnOnReadPost); + expect(result).toEqual({}); + }); + + it('should not remove post when post does not exist locally', async () => { + mockedGetPostById.mockResolvedValue(undefined); + + const result = await handleBoRPostBurnedEvent(serverUrl, msg); + + expect(mockedGetPostById).toHaveBeenCalledWith(expect.any(Object), 'post1'); + expect(mockedRemovePost).not.toHaveBeenCalled(); + expect(result).toBeNull(); + }); + + it('should not remove post when post is not burn-on-read type', async () => { + mockedGetPostById.mockResolvedValue(regularPost); + + const result = await handleBoRPostBurnedEvent(serverUrl, msg); + + expect(mockedGetPostById).toHaveBeenCalledWith(expect.any(Object), 'post1'); + expect(mockedRemovePost).not.toHaveBeenCalled(); + expect(result).toBeNull(); + }); + + it('should handle missing server database gracefully', async () => { + const result = await handleBoRPostBurnedEvent('invalid-server-url', msg); + + expect(mockedGetPostById).not.toHaveBeenCalled(); + expect(mockedRemovePost).not.toHaveBeenCalled(); + expect(result).toBeNull(); + }); + + it('should handle missing operator gracefully', async () => { + // Mock a server database without an operator + DatabaseManager.serverDatabases[serverUrl] = {} as any; + + const result = await handleBoRPostBurnedEvent(serverUrl, msg); + + expect(mockedGetPostById).not.toHaveBeenCalled(); + expect(mockedRemovePost).not.toHaveBeenCalled(); + expect(result).toBeNull(); + }); + + it('should handle errors gracefully and return error object', async () => { + mockedGetPostById.mockRejectedValue(new Error('Database error')); + + const result = await handleBoRPostBurnedEvent(serverUrl, msg); + + expect(result).toHaveProperty('error'); + expect(result!.error).toBeInstanceOf(Error); + expect(mockedRemovePost).not.toHaveBeenCalled(); + }); + }); }); diff --git a/app/actions/websocket/burn_on_read.ts b/app/actions/websocket/burn_on_read.ts index ec9f777ebc..59d4eb8195 100644 --- a/app/actions/websocket/burn_on_read.ts +++ b/app/actions/websocket/burn_on_read.ts @@ -1,7 +1,9 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. +import {removePost} from '@actions/local/post'; import {handleNewPostEvent, handlePostEdited} from '@actions/websocket/posts'; +import {PostTypes} from '@constants/post'; import DatabaseManager from '@database/manager'; import {getPostById} from '@queries/servers/post'; import {logError} from '@utils/log'; @@ -10,7 +12,7 @@ export async function handleBoRPostRevealedEvent(serverUrl: string, msg: WebSock try { const operator = DatabaseManager.serverDatabases[serverUrl]?.operator; if (!operator) { - return null; + return {}; } const {database} = operator; @@ -18,7 +20,7 @@ export async function handleBoRPostRevealedEvent(serverUrl: string, msg: WebSock try { post = JSON.parse(msg.data.post); } catch { - return null; + return {}; } const existingPost = await getPostById(database, post.id); @@ -34,3 +36,29 @@ export async function handleBoRPostRevealedEvent(serverUrl: string, msg: WebSock return {error}; } } + +export async function handleBoRPostBurnedEvent(serverUrl: string, msg: WebSocketMessage) { + try { + const postId = msg.data.post_id; + const operator = DatabaseManager.serverDatabases[serverUrl]?.operator; + if (!operator) { + return null; + } + + const {database} = operator; + const post = await getPostById(database, postId); + if (!post) { + return null; + } + + if (post.type !== PostTypes.BURN_ON_READ) { + return null; + } + + await removePost(serverUrl, post); + return {}; + } catch (error) { + logError('handleBoRPostBurnedEvent could not handle websocket event for burned burn-on-read post', error); + return {error}; + } +} diff --git a/app/actions/websocket/event.ts b/app/actions/websocket/event.ts index e7cdbaa0cd..ba731545fe 100644 --- a/app/actions/websocket/event.ts +++ b/app/actions/websocket/event.ts @@ -4,7 +4,7 @@ import {handleAgentPostUpdate} from '@agents/actions/websocket'; import * as bookmark from '@actions/local/channel_bookmark'; -import {handleBoRPostRevealedEvent} from '@actions/websocket/burn_on_read'; +import {handleBoRPostBurnedEvent, handleBoRPostRevealedEvent} from '@actions/websocket/burn_on_read'; import * as scheduledPost from '@actions/websocket/scheduled_post'; import * as calls from '@calls/connection/websocket_event_handlers'; import {WebsocketEvents} from '@constants'; @@ -314,6 +314,9 @@ export async function handleWebSocketEvent(serverUrl: string, msg: WebSocketMess case WebsocketEvents.BOR_POST_REVEALED: handleBoRPostRevealedEvent(serverUrl, msg); break; + case WebsocketEvents.BOR_POST_BURNED: + handleBoRPostBurnedEvent(serverUrl, msg); + break; } handlePlaybookEvents(serverUrl, msg); } diff --git a/app/constants/websocket.ts b/app/constants/websocket.ts index 893f10cdfc..000cca1605 100644 --- a/app/constants/websocket.ts +++ b/app/constants/websocket.ts @@ -111,6 +111,7 @@ const WebsocketEvents = { // Burn on Read BOR_POST_REVEALED: 'post_revealed', + BOR_POST_BURNED: 'post_burned', }; export default WebsocketEvents;