mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-05-14 07:04:45 -04:00
feat: add base implementation for realtime communication
Signed-off-by: Philip Molares <philip.molares@udo.edu> Signed-off-by: Erik Michelson <github@erik.michelson.eu> Co-authored-by: Erik Michelson <github@erik.michelson.eu> Co-authored-by: Philip Molares <philip.molares@udo.edu> Co-authored-by: Tilman Vatteroth <git@tilmanvatteroth.de> Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
parent
d9ef44766d
commit
ce29cc0a2e
44 changed files with 2151 additions and 65 deletions
190
src/realtime/realtime-note/websocket-connection.spec.ts
Normal file
190
src/realtime/realtime-note/websocket-connection.spec.ts
Normal file
|
@ -0,0 +1,190 @@
|
|||
/*
|
||||
* 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<WebSocket>({});
|
||||
mockedUser = Mock.of<User>({});
|
||||
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<WebsocketConnection>()]);
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue