/* * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) * * SPDX-License-Identifier: AGPL-3.0-only */ import * as hedgedocRealtimeModule from '@hedgedoc/realtime'; import { WebsocketTransporter } from '@hedgedoc/realtime'; import { Mock } from 'ts-mockery'; import WebSocket from 'ws'; import * as yProtocolsAwarenessModule from 'y-protocols/awareness'; import { User } from '../../users/user.entity'; import * as realtimeNoteModule from './realtime-note'; import { RealtimeNote } from './realtime-note'; import { mockAwareness } from './test-utils/mock-awareness'; import { mockRealtimeNote } from './test-utils/mock-realtime-note'; import { mockWebsocketDoc } from './test-utils/mock-websocket-doc'; import { mockWebsocketTransporter } from './test-utils/mock-websocket-transporter'; import * as websocketAwarenessModule from './websocket-awareness'; import { ClientIdUpdate, WebsocketAwareness } from './websocket-awareness'; import { WebsocketConnection } from './websocket-connection'; import * as websocketDocModule from './websocket-doc'; import { WebsocketDoc } from './websocket-doc'; import SpyInstance = jest.SpyInstance; describe('websocket connection', () => { let mockedDoc: WebsocketDoc; let mockedAwareness: WebsocketAwareness; let mockedRealtimeNote: RealtimeNote; let mockedWebsocket: WebSocket; let mockedUser: User; let mockedWebsocketTransporter: WebsocketTransporter; let removeAwarenessSpy: SpyInstance; beforeEach(() => { jest.resetAllMocks(); jest.resetModules(); mockedDoc = mockWebsocketDoc(); mockedAwareness = mockAwareness(); mockedRealtimeNote = mockRealtimeNote(mockedDoc, mockedAwareness); mockedWebsocket = Mock.of({}); mockedUser = Mock.of({}); mockedWebsocketTransporter = mockWebsocketTransporter(); jest .spyOn(realtimeNoteModule, 'RealtimeNote') .mockImplementation(() => mockedRealtimeNote); jest .spyOn(websocketDocModule, 'WebsocketDoc') .mockImplementation(() => mockedDoc); jest .spyOn(websocketAwarenessModule, 'WebsocketAwareness') .mockImplementation(() => mockedAwareness); jest .spyOn(hedgedocRealtimeModule, 'WebsocketTransporter') .mockImplementation(() => mockedWebsocketTransporter); removeAwarenessSpy = jest .spyOn(yProtocolsAwarenessModule, 'removeAwarenessStates') .mockImplementation(); }); afterAll(() => { jest.resetAllMocks(); jest.resetModules(); }); it('sets up the websocket in the constructor', () => { const setupWebsocketSpy = jest.spyOn( mockedWebsocketTransporter, 'setupWebsocket', ); new WebsocketConnection(mockedWebsocket, mockedUser, mockedRealtimeNote); expect(setupWebsocketSpy).toBeCalledWith(mockedWebsocket); }); it('forwards sent messages to the transporter', () => { const sut = new WebsocketConnection( mockedWebsocket, mockedUser, mockedRealtimeNote, ); const sendFunctionSpy = jest.spyOn(mockedWebsocketTransporter, 'send'); const sendContent = new Uint8Array(); sut.send(sendContent); expect(sendFunctionSpy).toBeCalledWith(sendContent); }); it('forwards disconnect calls to the transporter', () => { const sut = new WebsocketConnection( mockedWebsocket, mockedUser, mockedRealtimeNote, ); const disconnectFunctionSpy = jest.spyOn( mockedWebsocketTransporter, 'disconnect', ); sut.disconnect(); expect(disconnectFunctionSpy).toBeCalled(); }); it('forwards isSynced checks to the transporter', () => { const sut = new WebsocketConnection( mockedWebsocket, mockedUser, mockedRealtimeNote, ); const isSyncedFunctionSpy = jest.spyOn( mockedWebsocketTransporter, 'isSynced', ); expect(sut.isSynced()).toBe(false); isSyncedFunctionSpy.mockReturnValue(true); expect(sut.isSynced()).toBe(true); }); it('removes the client from the note on transporter disconnect', () => { const sut = new WebsocketConnection( mockedWebsocket, mockedUser, mockedRealtimeNote, ); const removeClientSpy = jest.spyOn(mockedRealtimeNote, 'removeClient'); mockedWebsocketTransporter.emit('disconnected'); expect(removeClientSpy).toBeCalledWith(sut); }); it('remembers the controlled awareness-ids on awareness update', () => { const sut = new WebsocketConnection( mockedWebsocket, mockedUser, mockedRealtimeNote, ); const update: ClientIdUpdate = { added: [0], removed: [1], updated: [2] }; mockedAwareness.emit('update', [update, sut]); expect(sut.getControlledAwarenessIds()).toEqual(new Set([0])); }); it("doesn't remembers the controlled awareness-ids of other connections on awareness update", () => { const sut = new WebsocketConnection( mockedWebsocket, mockedUser, mockedRealtimeNote, ); const update: ClientIdUpdate = { added: [0], removed: [1], updated: [2] }; mockedAwareness.emit('update', [update, Mock.of()]); expect(sut.getControlledAwarenessIds()).toEqual(new Set([])); }); it('removes the controlled awareness ids on transport disconnect', () => { const sut = new WebsocketConnection( mockedWebsocket, mockedUser, mockedRealtimeNote, ); const update: ClientIdUpdate = { added: [0], removed: [1], updated: [2] }; mockedAwareness.emit('update', [update, sut]); mockedWebsocketTransporter.emit('disconnected'); expect(removeAwarenessSpy).toBeCalledWith(mockedAwareness, [0], sut); }); it('saves the correct user', () => { const sut = new WebsocketConnection( mockedWebsocket, mockedUser, mockedRealtimeNote, ); expect(sut.getUser()).toBe(mockedUser); }); });