hedgedoc/backend/src/realtime/realtime-note/realtime-connection.spec.ts
Philip Molares 0a8945d934 feat(backend): handle username always in lowercase
This should make all usernames of new users into lowercase. Usernames are also searched in the DB as lowercase.

Signed-off-by: Philip Molares <philip.molares@udo.edu>
Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
2023-06-04 21:55:19 +02:00

227 lines
6.6 KiB
TypeScript

/*
* 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<YDocSyncServerAdapter>({})),
} as Record<string, unknown>),
);
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<Note>({}), '');
mockedUser = Mock.of<User>({
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<RealtimeUserStatusAdapter>();
const realtimeUserStatus2 = Mock.of<RealtimeUserStatusAdapter>();
const realtimeUserStatus3 = Mock.of<RealtimeUserStatusAdapter>();
const realtimeConnections = [
Mock.of<RealtimeConnection>({
getRealtimeUserStateAdapter: () => realtimeUserStatus1,
}),
Mock.of<RealtimeConnection>({
getRealtimeUserStateAdapter: () => realtimeUserStatus2,
}),
Mock.of<RealtimeConnection>({
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<YDocSyncServerAdapter>({});
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<User>({ 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<User>({});
const sut = new RealtimeConnection(
mockedMessageTransporter,
mockedUser,
mockedRealtimeNote,
true,
);
expect(sut.getDisplayName()).toBe(randomName);
});
});