/* * SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file) * * SPDX-License-Identifier: AGPL-3.0-only */ import { MessageTransporter, MockedBackendTransportAdapter, YDocSyncServerAdapter, } from '@hedgedoc/commons'; import * as HedgeDocCommonsModule from '@hedgedoc/commons'; import { Mock } from 'ts-mockery'; import { Note } from '../../notes/note.entity'; import { User } from '../../users/user.entity'; import { Username } from '../../utils/username'; import * as NameRandomizerModule from './random-word-lists/name-randomizer'; import { RealtimeConnection } from './realtime-connection'; import { RealtimeNote } from './realtime-note'; import { OtherAdapterCollector, RealtimeUserStatusAdapter, } from './realtime-user-status-adapter'; import * as RealtimeUserStatusModule from './realtime-user-status-adapter'; jest.mock('./random-word-lists/name-randomizer'); jest.mock('./realtime-user-status-adapter'); jest.mock( '@hedgedoc/commons', () => ({ ...jest.requireActual('@hedgedoc/commons'), // eslint-disable-next-line @typescript-eslint/naming-convention YDocSyncServerAdapter: jest.fn(() => Mock.of({})), } as Record), ); describe('websocket connection', () => { let mockedRealtimeNote: RealtimeNote; let mockedUser: User; let mockedMessageTransporter: MessageTransporter; const mockedUserName: Username = 'mocked-user-name'; const mockedDisplayName = 'mockedDisplayName'; beforeEach(() => { mockedRealtimeNote = new RealtimeNote(Mock.of({}), ''); mockedUser = Mock.of({ username: mockedUserName, displayName: mockedDisplayName, }); mockedMessageTransporter = new MessageTransporter(); mockedMessageTransporter.setAdapter(new MockedBackendTransportAdapter('')); }); afterEach(() => { jest.resetAllMocks(); jest.resetModules(); }); it('returns the correct transporter', () => { const sut = new RealtimeConnection( mockedMessageTransporter, mockedUser, mockedRealtimeNote, true, ); expect(sut.getTransporter()).toBe(mockedMessageTransporter); }); it('returns the correct realtime note', () => { const sut = new RealtimeConnection( mockedMessageTransporter, mockedUser, mockedRealtimeNote, true, ); expect(sut.getRealtimeNote()).toBe(mockedRealtimeNote); }); it.each([true, false])( 'returns the correct realtime user status with acceptEdits %s', (acceptEdits) => { const realtimeUserStatus1 = Mock.of(); const realtimeUserStatus2 = Mock.of(); const realtimeUserStatus3 = Mock.of(); const realtimeConnections = [ Mock.of({ getRealtimeUserStateAdapter: () => realtimeUserStatus1, }), Mock.of({ getRealtimeUserStateAdapter: () => realtimeUserStatus2, }), Mock.of({ getRealtimeUserStateAdapter: () => realtimeUserStatus3, }), ]; jest .spyOn(mockedRealtimeNote, 'getConnections') .mockImplementation(() => realtimeConnections); const constructor = jest .spyOn(RealtimeUserStatusModule, 'RealtimeUserStatusAdapter') .mockImplementation( ( username, displayName, otherAdapterCollector: OtherAdapterCollector, messageTransporter, acceptCursorUpdateProvider, ) => { expect(username).toBe(mockedUserName); expect(displayName).toBe(mockedDisplayName); expect(otherAdapterCollector()).toStrictEqual([ realtimeUserStatus1, realtimeUserStatus2, realtimeUserStatus3, ]); expect(messageTransporter).toBe(mockedMessageTransporter); expect(acceptCursorUpdateProvider()).toBe(acceptEdits); return realtimeUserStatus1; }, ); const sut = new RealtimeConnection( mockedMessageTransporter, mockedUser, mockedRealtimeNote, acceptEdits, ); expect(constructor).toHaveBeenCalledWith( mockedUserName, mockedDisplayName, expect.anything(), mockedMessageTransporter, expect.anything(), ); expect(sut.getRealtimeUserStateAdapter()).toBe(realtimeUserStatus1); }, ); it.each([true, false])( 'creates a sync adapter with acceptEdits %s', (acceptEdits) => { const yDocSyncServerAdapter = Mock.of({}); jest .spyOn(HedgeDocCommonsModule, 'YDocSyncServerAdapter') .mockImplementation((messageTransporter, doc, acceptEditsProvider) => { expect(messageTransporter).toBe(mockedMessageTransporter); expect(acceptEditsProvider()).toBe(acceptEdits); expect(doc).toBe(mockedRealtimeNote.getRealtimeDoc()); return yDocSyncServerAdapter; }); const sut = new RealtimeConnection( mockedMessageTransporter, mockedUser, mockedRealtimeNote, acceptEdits, ); expect(sut.getSyncAdapter()).toBe(yDocSyncServerAdapter); }, ); it('removes the client from the note on transporter disconnect', () => { const sut = new RealtimeConnection( mockedMessageTransporter, mockedUser, mockedRealtimeNote, true, ); const removeClientSpy = jest.spyOn(mockedRealtimeNote, 'removeClient'); mockedMessageTransporter.disconnect(); expect(removeClientSpy).toHaveBeenCalledWith(sut); }); it('saves the correct user', () => { const sut = new RealtimeConnection( mockedMessageTransporter, mockedUser, mockedRealtimeNote, true, ); expect(sut.getUser()).toBe(mockedUser); }); it('returns the correct username', () => { const mockedUserWithUsername = Mock.of({ displayName: 'MockUser' }); const sut = new RealtimeConnection( mockedMessageTransporter, mockedUserWithUsername, mockedRealtimeNote, true, ); expect(sut.getDisplayName()).toBe('MockUser'); }); it('returns a random fallback display name if the provided user has no display name', () => { const randomName = 'I am a random name'; jest .spyOn(NameRandomizerModule, 'generateRandomName') .mockReturnValue(randomName); mockedUser = Mock.of({}); const sut = new RealtimeConnection( mockedMessageTransporter, mockedUser, mockedRealtimeNote, true, ); expect(sut.getDisplayName()).toBe(randomName); }); });