Skip to content

Commit

Permalink
Destroy client and add seperate port for worker
Browse files Browse the repository at this point in the history
  • Loading branch information
bischofmax committed Dec 20, 2024
1 parent e050ec7 commit 1cd5881
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 35 deletions.
65 changes: 47 additions & 18 deletions src/modules/server/api/test/websocket.api.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ describe('Websocket Api Test', () => {
let app: INestApplication;
let authorizationService: DeepMocked<AuthorizationService>;
let tldrawServerConfig: TldrawServerConfig;
const allProvider: WebsocketProvider[] = [];

beforeAll(async () => {
const moduleFixture = await Test.createTestingModule({
Expand All @@ -32,7 +31,6 @@ describe('Websocket Api Test', () => {
});

afterAll(async () => {
allProvider.forEach((provider) => provider.destroy());
await app.close();
});

Expand All @@ -46,7 +44,6 @@ describe('Websocket Api Test', () => {
connect: true,
disableBc: true,
});
allProvider.push(provider);

return { ydoc, provider };
};
Expand Down Expand Up @@ -86,25 +83,30 @@ describe('Websocket Api Test', () => {
error: null,
});

const { ydoc: client1Doc } = createWsClient(room);
const { ydoc: client2Doc } = createWsClient(room);
const { ydoc: client1Doc, provider: provider1 } = createWsClient(room);
const { ydoc: client2Doc, provider: provider2 } = createWsClient(room);

return { client1Doc, client2Doc };
return { client1Doc, client2Doc, provider1, provider2 };
};

it('syncs doc changes of first client to second client', async () => {
const { client1Doc, client2Doc } = setup();
const { client1Doc, client2Doc, provider1, provider2 } = setup();

client1Doc.getMap().set('a', 1);

await waitUntilDocsEqual(client1Doc, client2Doc);

const result = client2Doc.getMap().get('a');
expect(result).toBe(1);

provider1.awareness.destroy();
provider2.awareness.destroy();
provider1.destroy();
provider2.destroy();
});

it('syncs subsequent doc changes of second client to first client', async () => {
const { client1Doc, client2Doc } = setup();
const { client1Doc, client2Doc, provider1, provider2 } = setup();

client1Doc.getMap().set('a', 1);
await waitUntilDocsEqual(client1Doc, client2Doc);
Expand All @@ -114,6 +116,11 @@ describe('Websocket Api Test', () => {

const result = client1Doc.getMap().get('a');
expect(result).toBe(2);

provider1.awareness.destroy();
provider2.awareness.destroy();
provider1.destroy();
provider2.destroy();
});

/* it('syncs nearly parallel doc changes of second client to first client', async () => {
Expand All @@ -135,43 +142,59 @@ describe('Websocket Api Test', () => {
const randomString = Math.random().toString(36).substring(7);
const room = randomString;

authorizationService.hasPermission.mockResolvedValue({
authorizationService.hasPermission.mockResolvedValueOnce({
hasWriteAccess: true,
room: randomString,
userid: 'userId1',
error: null,
});
authorizationService.hasPermission.mockResolvedValueOnce({
hasWriteAccess: true,
room: randomString,
userid: 'userId',
userid: 'userId2',
error: null,
});

const { ydoc: client1Doc } = createWsClient(room);
const { ydoc: client1Doc, provider } = createWsClient(room);

return { client1Doc, room };
return { client1Doc, room, provider };
};

it('syncs doc changes of first client to second client', async () => {
const { client1Doc, room } = setup();
const { client1Doc, room, provider } = setup();

client1Doc.getMap().set('a', 1);

const { ydoc: client2Doc } = createWsClient(room);
const { ydoc: client2Doc, provider: provider2 } = createWsClient(room);
await waitUntilDocsEqual(client1Doc, client2Doc);

const result = client2Doc.getMap().get('a');
expect(result).toBe(1);

provider.awareness.destroy();
provider.destroy();
provider2.awareness.destroy();
provider2.destroy();
});

it('syncs subsequent doc changes of second client to first client', async () => {
const { client1Doc, room } = setup();
const { client1Doc, room, provider } = setup();

client1Doc.getMap().set('a', 1);

const { ydoc: client2Doc } = createWsClient(room);
const { ydoc: client2Doc, provider: provider2 } = createWsClient(room);
await waitUntilDocsEqual(client1Doc, client2Doc);

client2Doc.getMap().set('a', 2);
await waitUntilDocsEqual(client1Doc, client2Doc);

const result = client1Doc.getMap().get('a');
expect(result).toBe(2);

provider.awareness.destroy();
provider.destroy();
provider2.awareness.destroy();
provider2.destroy();
});

/* it('syncs nearly parallel doc changes of second client to first client', async () => {
Expand Down Expand Up @@ -202,7 +225,7 @@ describe('Websocket Api Test', () => {
const room = randomString;

const errorResponse = ResponsePayloadBuilder.buildWithError(4401, 'Unauthorized');
authorizationService.hasPermission.mockResolvedValue(errorResponse);
authorizationService.hasPermission.mockResolvedValueOnce(errorResponse);

const { ydoc: client1Doc, provider } = createWsClient(room);

Expand All @@ -227,6 +250,9 @@ describe('Websocket Api Test', () => {
expect(error.reason).toBe('Unauthorized');
// @ts-ignore
expect(error.code).toBe(4401);

provider.awareness.destroy();
provider.destroy();
});
});

Expand All @@ -236,7 +262,7 @@ describe('Websocket Api Test', () => {
const room = randomString;

const response = ResponsePayloadBuilder.build(null, 'userId');
authorizationService.hasPermission.mockResolvedValue(response);
authorizationService.hasPermission.mockResolvedValueOnce(response);

const { ydoc: client1Doc, provider } = createWsClient(room);

Expand All @@ -261,6 +287,9 @@ describe('Websocket Api Test', () => {
expect(error.reason).toBe('Missing room or userid');
// @ts-ignore
expect(error.code).toBe(1008);

provider.awareness.destroy();
provider.destroy();
});
});
});
Expand Down
46 changes: 29 additions & 17 deletions src/modules/worker/worker.api.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { createMock, DeepMocked } from '@golevelup/ts-jest';
import { INestApplication } from '@nestjs/common';
import { Test } from '@nestjs/testing';
import { randomUUID } from 'crypto';
import { WebSocket } from 'ws';
import { WebsocketProvider } from 'y-websocket';
import * as Y from 'yjs';
Expand All @@ -11,18 +12,23 @@ import { StorageService } from '../../infra/storage/storage.service.js';
import { computeRedisRoomStreamName } from '../../infra/y-redis/helper.js';
import { REDIS_FOR_DELETION } from '../../modules/server/server.const.js';
import { ServerModule } from '../../modules/server/server.module.js';
import { TldrawServerConfig } from '../../modules/server/tldraw-server.config.js';
import { WorkerModule } from './worker.module.js';
import { WorkerService } from './worker.service.js';

describe('Worker Api Test', () => {
let app: INestApplication;
let authorizationService: DeepMocked<AuthorizationService>;
let tldrawServerConfig: TldrawServerConfig;
let workerService: WorkerService;
let storageService: StorageService;
let ioRedisAdapter: IoRedisAdapter;
const allProvider: WebsocketProvider[] = [];

// This port must be different from the one used in the server module
// because tests are executed parallel and therefore we can have port conflicts.
const port = 3398;
const url = `ws://localhost:${port}`;

process.env.TLDRAW_WEBSOCKET_URL = url;
process.env.TLDRAW_WEBSOCKET_PORT = port.toString();

beforeAll(async () => {
const moduleFixture = await Test.createTestingModule({
Expand All @@ -35,28 +41,25 @@ describe('Worker Api Test', () => {
app = moduleFixture.createNestApplication();
await app.init();
authorizationService = await app.resolve(AuthorizationService);
tldrawServerConfig = await app.resolve(TldrawServerConfig);
workerService = await app.resolve(WorkerService);
storageService = await app.resolve(StorageService);
ioRedisAdapter = await app.resolve(REDIS_FOR_DELETION);
});

afterAll(async () => {
allProvider.forEach((provider) => provider.destroy());
await app.close();
});

const createWsClient = (room: string) => {
const ydoc = new Doc();
const serverUrl = tldrawServerConfig.TLDRAW_WEBSOCKET_URL;
const serverUrl = url;
const prefix = 'y';
const provider = new WebsocketProvider(serverUrl, prefix + '-' + room, ydoc, {
// @ts-ignore
WebSocketPolyfill: WebSocket,
connect: true,
disableBc: true,
});
allProvider.push(provider);

return { ydoc, provider };
};
Expand All @@ -65,7 +68,7 @@ describe('Worker Api Test', () => {
const setup = () => {
workerService.start();

const room = Math.random().toString(36).substring(7);
const room = randomUUID();

authorizationService.hasPermission.mockResolvedValueOnce({
hasWriteAccess: true,
Expand All @@ -74,18 +77,18 @@ describe('Worker Api Test', () => {
error: null,
});

const { ydoc: client1Doc } = createWsClient(room);
const { ydoc: client1Doc, provider } = createWsClient(room);

const property = 'property';
const value = 'value';

client1Doc.getMap().set(property, value);

return { client1Doc, room, property, value };
return { client1Doc, room, property, value, provider };
};

it('saves doc to storage', async () => {
const { room, property, value } = setup();
const { room, property, value, provider } = setup();

let doc;
while (!doc) {
Expand All @@ -106,14 +109,17 @@ describe('Worker Api Test', () => {
expect(resultUpdateValue).toBe(value);

workerService.stop();
provider.awareness.destroy();
provider.disconnect();
provider.destroy();
}, 10000);
});

describe('when second client sends update', () => {
const setup = () => {
workerService.start();

const room = Math.random().toString(36).substring(7);
const room = randomUUID();

authorizationService.hasPermission.mockResolvedValueOnce({
hasWriteAccess: true,
Expand All @@ -129,8 +135,8 @@ describe('Worker Api Test', () => {
error: null,
});

const { ydoc: client1Doc } = createWsClient(room);
const { ydoc: client2Doc } = createWsClient(room);
const { ydoc: client1Doc, provider: provider1 } = createWsClient(room);
const { ydoc: client2Doc, provider: provider2 } = createWsClient(room);

const property1 = 'property1';
const property2 = 'property2';
Expand All @@ -140,11 +146,11 @@ describe('Worker Api Test', () => {
client1Doc.getMap().set(property1, value1);
client2Doc.getMap().set(property2, value2);

return { client1Doc, room, property2, value2 };
return { client1Doc, room, property2, value2, provider1, provider2 };
};

it('saves doc to storage', async () => {
const { room, property2, value2 } = setup();
const { room, property2, value2, provider1, provider2 } = setup();

let resultProperty;
let resultValue;
Expand All @@ -163,14 +169,18 @@ describe('Worker Api Test', () => {
expect(resultValue).toBe(value2);

workerService.stop();
provider1.awareness.destroy();
provider2.awareness.destroy();
provider1.destroy();
provider2.destroy();
});
});

describe('when deleted doc entry exists', () => {
const setup = async () => {
workerService.start();

const room = Math.random().toString(36).substring(7);
const room = randomUUID();

authorizationService.hasPermission.mockResolvedValueOnce({
hasWriteAccess: true,
Expand All @@ -192,6 +202,8 @@ describe('Worker Api Test', () => {
}

provider.disconnect();
provider.awareness.destroy();
provider.destroy();

const streamName = computeRedisRoomStreamName(room, 'index', 'y');
await ioRedisAdapter.markToDeleteByDocName(streamName);
Expand Down

0 comments on commit 1cd5881

Please sign in to comment.