hedgedoc/backend/src/realtime/realtime-note/test-utils/mock-connection.ts
Erik Michelson a9183e82bf
feat(knex): create database interfaces and knexjs nest integration
Co-authored-by: Philip Molares <philip.molares@udo.edu>
Signed-off-by: Philip Molares <philip.molares@udo.edu>
Signed-off-by: Erik Michelson <github@erik.michelson.eu>
2025-04-23 22:49:10 +02:00

142 lines
4.3 KiB
TypeScript

/*
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import {
MockedBackendTransportAdapter,
YDocSyncServerAdapter,
} from '@hedgedoc/commons';
import { Mock } from 'ts-mockery';
import { User } from '../../../database/user.entity';
import { RealtimeConnection } from '../realtime-connection';
import { RealtimeNote } from '../realtime-note';
import { RealtimeUserStatusAdapter } from '../realtime-user-status-adapter';
import { MockMessageTransporter } from './mock-message-transporter';
enum RealtimeUserState {
WITHOUT,
WITH_READWRITE,
WITH_READONLY,
}
const MOCK_FALLBACK_USERNAME: string = 'mock';
/**
* Creates a mocked {@link RealtimeConnection realtime connection}.
*/
export class MockConnectionBuilder {
private username: string | null;
private displayName: string | undefined;
private includeRealtimeUserStatus: RealtimeUserState =
RealtimeUserState.WITHOUT;
constructor(private readonly realtimeNote: RealtimeNote) {}
/**
* Defines that the user who belongs to the connection is a guest.
*
* @param displayName the display name of the guest user
*/
public withGuestUser(displayName: string): this {
this.username = null;
this.displayName = displayName;
return this;
}
/**
* Defines that the user who belongs to this connection is a logged-in user.
*
* @param username the username of the mocked user. If this value is omitted then the builder will user a {@link MOCK_FALLBACK_USERNAME fallback}.
*/
public withLoggedInUser(username?: string): this {
const newUsername = username ?? MOCK_FALLBACK_USERNAME;
this.username = newUsername;
this.displayName = newUsername;
return this;
}
/**
* Defines that the connection should contain a {@link RealtimeUserStatusAdapter} that is accepting cursor updates.
*/
public withAcceptingRealtimeUserStatus(): this {
this.includeRealtimeUserStatus = RealtimeUserState.WITH_READWRITE;
return this;
}
/**
* Defines that the connection should contain a {@link RealtimeUserStatusAdapter} that is declining cursor updates.
*/
public withDecliningRealtimeUserStatus(): this {
this.includeRealtimeUserStatus = RealtimeUserState.WITH_READONLY;
return this;
}
/**
* Creates a new connection based on the given configuration.
*
* @return {RealtimeConnection} The constructed mocked connection
* @throws Error if neither withGuestUser nor withLoggedInUser has been called.
*/
public build(): RealtimeConnection {
const displayName = this.deriveDisplayName();
const transporter = new MockMessageTransporter();
transporter.setAdapter(new MockedBackendTransportAdapter(''));
const realtimeUserStateAdapter: RealtimeUserStatusAdapter =
this.includeRealtimeUserStatus === RealtimeUserState.WITHOUT
? Mock.of<RealtimeUserStatusAdapter>({})
: new RealtimeUserStatusAdapter(
this.username ?? null,
displayName,
() =>
this.realtimeNote
.getConnections()
.map((connection) => connection.getRealtimeUserStateAdapter()),
transporter,
() =>
this.includeRealtimeUserStatus ===
RealtimeUserState.WITH_READWRITE,
);
const mockUser =
this.username === null
? null
: Mock.of<User>({
username: this.username,
displayName: this.displayName,
});
const yDocSyncServerAdapter = Mock.of<YDocSyncServerAdapter>({});
const connection = Mock.of<RealtimeConnection>({
getUser: jest.fn(() => mockUser),
getDisplayName: jest.fn(() => displayName),
getSyncAdapter: jest.fn(() => yDocSyncServerAdapter),
getTransporter: jest.fn(() => transporter),
getRealtimeUserStateAdapter: () => realtimeUserStateAdapter,
getRealtimeNote: () => this.realtimeNote,
});
transporter.on('disconnected', () =>
this.realtimeNote.removeClient(connection),
);
this.realtimeNote.addClient(connection);
transporter.markAsReady();
jest.advanceTimersByTime(0);
return connection;
}
private deriveDisplayName(): string {
if (this.displayName === undefined) {
throw new Error(
'Neither withGuestUser nor withLoggedInUser has been called.',
);
}
return this.displayName;
}
}