✅(y-provider) add tests for y-provider server
We add jest tests for the y-provider server. The CI will be able to run the tests.
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: ['impress/next'],
|
||||
extends: ['impress/jest', 'impress/next'],
|
||||
parserOptions: {
|
||||
tsconfigRootDir: __dirname,
|
||||
project: ['./tsconfig.json'],
|
||||
|
||||
234
src/frontend/servers/y-provider/__tests__/server.test.ts
Normal file
234
src/frontend/servers/y-provider/__tests__/server.test.ts
Normal file
@@ -0,0 +1,234 @@
|
||||
import {
|
||||
HocuspocusProvider,
|
||||
HocuspocusProviderWebsocket,
|
||||
} from '@hocuspocus/provider';
|
||||
import request from 'supertest';
|
||||
import WebSocket from 'ws';
|
||||
|
||||
const port = 5555;
|
||||
const portWS = 6666;
|
||||
const origin = 'http://localhost:3000';
|
||||
|
||||
jest.mock('../src/env', () => {
|
||||
return {
|
||||
PORT: port,
|
||||
COLLABORATION_SERVER_ORIGIN: origin,
|
||||
COLLABORATION_SERVER_SECRET: 'test-secret-api-key',
|
||||
};
|
||||
});
|
||||
|
||||
console.error = jest.fn();
|
||||
|
||||
import { promiseDone } from '../src/helpers';
|
||||
import { hocuspocusServer, initServer } from '../src/server'; // Adjust the path to your server file
|
||||
|
||||
const { app, server } = initServer();
|
||||
|
||||
describe('Server Tests', () => {
|
||||
beforeAll(async () => {
|
||||
await hocuspocusServer.configure({ port: portWS }).listen();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
server.close();
|
||||
void hocuspocusServer.destroy();
|
||||
});
|
||||
|
||||
test('Ping Pong', async () => {
|
||||
const response = await request(app as any).get('/ping');
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.body.message).toBe('pong');
|
||||
});
|
||||
|
||||
test('POST /collaboration/api/reset-connections?room=[ROOM_ID] invalid origin', async () => {
|
||||
const response = await request(app as any)
|
||||
.post('/collaboration/api/reset-connections/?room=test-room')
|
||||
.set('Origin', 'http://invalid-origin.com')
|
||||
.send({ document_id: 'test-document' });
|
||||
|
||||
expect(response.status).toBe(403);
|
||||
expect(response.body.error).toBe('CORS policy violation: Invalid Origin');
|
||||
});
|
||||
|
||||
test('POST /collaboration/api/reset-connections?room=[ROOM_ID] with incorrect API key should return 403', async () => {
|
||||
const response = await request(app as any)
|
||||
.post('/collaboration/api/reset-connections/?room=test-room')
|
||||
.set('Origin', origin)
|
||||
.set('Authorization', 'wrong-api-key');
|
||||
|
||||
expect(response.status).toBe(403);
|
||||
expect(response.body.error).toBe('Forbidden: Invalid API Key');
|
||||
});
|
||||
|
||||
test('POST /collaboration/api/reset-connections?room=[ROOM_ID] failed if room not indicated', async () => {
|
||||
const response = await request(app as any)
|
||||
.post('/collaboration/api/reset-connections/')
|
||||
.set('Origin', origin)
|
||||
.set('Authorization', 'test-secret-api-key')
|
||||
.send({ document_id: 'test-document' });
|
||||
|
||||
expect(response.status).toBe(400);
|
||||
expect(response.body.error).toBe('Room name not provided');
|
||||
});
|
||||
|
||||
test('POST /collaboration/api/reset-connections?room=[ROOM_ID] with correct API key should reset connections', async () => {
|
||||
// eslint-disable-next-line jest/unbound-method
|
||||
const { closeConnections } = hocuspocusServer;
|
||||
const mockHandleConnection = jest.fn();
|
||||
(hocuspocusServer.closeConnections as jest.Mock) = mockHandleConnection;
|
||||
|
||||
const response = await request(app as any)
|
||||
.post('/collaboration/api/reset-connections?room=test-room')
|
||||
.set('Origin', origin)
|
||||
.set('Authorization', 'test-secret-api-key');
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.body.message).toBe('Connections reset');
|
||||
|
||||
expect(mockHandleConnection).toHaveBeenCalled();
|
||||
mockHandleConnection.mockClear();
|
||||
hocuspocusServer.closeConnections = closeConnections;
|
||||
});
|
||||
|
||||
['/collaboration/api/anything/', '/', '/anything'].forEach((path) => {
|
||||
test(`"${path}" endpoint should be forbidden`, async () => {
|
||||
const response = await request(app as any).post(path);
|
||||
|
||||
expect(response.status).toBe(403);
|
||||
expect(response.body.error).toBe('Forbidden');
|
||||
});
|
||||
});
|
||||
|
||||
test('WebSocket connection with correct API key can connect', () => {
|
||||
const { promise, done } = promiseDone();
|
||||
|
||||
// eslint-disable-next-line jest/unbound-method
|
||||
const { handleConnection } = hocuspocusServer;
|
||||
const mockHandleConnection = jest.fn();
|
||||
(hocuspocusServer.handleConnection as jest.Mock) = mockHandleConnection;
|
||||
|
||||
const clientWS = new WebSocket(
|
||||
`ws://localhost:${port}/collaboration/ws/?room=test-room`,
|
||||
{
|
||||
headers: {
|
||||
authorization: 'test-secret-api-key',
|
||||
Origin: origin,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
clientWS.on('open', () => {
|
||||
expect(mockHandleConnection).toHaveBeenCalled();
|
||||
clientWS.close();
|
||||
mockHandleConnection.mockClear();
|
||||
hocuspocusServer.handleConnection = handleConnection;
|
||||
done();
|
||||
});
|
||||
|
||||
return promise;
|
||||
});
|
||||
|
||||
test('WebSocket connection with bad origin should be closed', () => {
|
||||
const { promise, done } = promiseDone();
|
||||
|
||||
const ws = new WebSocket(
|
||||
`ws://localhost:${port}/collaboration/ws/?room=test-room`,
|
||||
{
|
||||
headers: {
|
||||
Origin: 'http://bad-origin.com',
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
ws.onclose = () => {
|
||||
expect(ws.readyState).toBe(ws.CLOSED);
|
||||
done();
|
||||
};
|
||||
|
||||
return promise;
|
||||
});
|
||||
|
||||
test('WebSocket connection with incorrect API key should be closed', () => {
|
||||
const { promise, done } = promiseDone();
|
||||
const ws = new WebSocket(
|
||||
`ws://localhost:${port}/collaboration/ws/?room=test-room`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: 'wrong-api-key',
|
||||
Origin: origin,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
ws.onclose = () => {
|
||||
expect(ws.readyState).toBe(ws.CLOSED);
|
||||
done();
|
||||
};
|
||||
|
||||
return promise;
|
||||
});
|
||||
|
||||
test('WebSocket connection not allowed if room not matching provider name', () => {
|
||||
const { promise, done } = promiseDone();
|
||||
|
||||
const wsHocus = new HocuspocusProviderWebsocket({
|
||||
url: `ws://localhost:${portWS}/?room=my-test`,
|
||||
WebSocketPolyfill: WebSocket,
|
||||
maxAttempts: 1,
|
||||
quiet: true,
|
||||
});
|
||||
|
||||
const provider = new HocuspocusProvider({
|
||||
websocketProvider: wsHocus,
|
||||
name: 'hocuspocus-test',
|
||||
broadcast: false,
|
||||
quiet: true,
|
||||
preserveConnection: false,
|
||||
onClose: (data) => {
|
||||
wsHocus.stopConnectionAttempt();
|
||||
expect(data.event.reason).toBe('Forbidden');
|
||||
wsHocus.webSocket?.close();
|
||||
wsHocus.disconnect();
|
||||
provider.destroy();
|
||||
wsHocus.destroy();
|
||||
done();
|
||||
},
|
||||
});
|
||||
|
||||
return promise;
|
||||
});
|
||||
|
||||
test('WebSocket connection read-only', () => {
|
||||
const { promise, done } = promiseDone();
|
||||
|
||||
const wsHocus = new HocuspocusProviderWebsocket({
|
||||
url: `ws://localhost:${portWS}/?room=hocuspocus-test`,
|
||||
WebSocketPolyfill: WebSocket,
|
||||
});
|
||||
|
||||
const provider = new HocuspocusProvider({
|
||||
websocketProvider: wsHocus,
|
||||
name: 'hocuspocus-test',
|
||||
broadcast: false,
|
||||
quiet: true,
|
||||
onConnect: () => {
|
||||
void hocuspocusServer
|
||||
.openDirectConnection('hocuspocus-test')
|
||||
.then((connection) => {
|
||||
connection.document?.getConnections().forEach((connection) => {
|
||||
expect(connection.readOnly).toBe(true);
|
||||
});
|
||||
|
||||
void connection.disconnect();
|
||||
});
|
||||
|
||||
provider.destroy();
|
||||
wsHocus.destroy();
|
||||
done();
|
||||
},
|
||||
});
|
||||
|
||||
return promise;
|
||||
});
|
||||
});
|
||||
11
src/frontend/servers/y-provider/jest.config.js
Normal file
11
src/frontend/servers/y-provider/jest.config.js
Normal file
@@ -0,0 +1,11 @@
|
||||
var config = {
|
||||
rootDir: './__tests__',
|
||||
testEnvironment: 'node',
|
||||
transform: {
|
||||
'^.+\\.(ts)$': 'ts-jest',
|
||||
},
|
||||
moduleNameMapper: {
|
||||
'^@/(.*)$': '<rootDir>/../src/$1',
|
||||
},
|
||||
};
|
||||
export default config;
|
||||
@@ -6,10 +6,11 @@
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "tsc -p ./src",
|
||||
"build": "tsc -p tsconfig.build.json && tsc-alias",
|
||||
"dev": "nodemon --config nodemon.json",
|
||||
"start": "node ./dist/server.js",
|
||||
"lint": "eslint . --ext .ts"
|
||||
"start": "node ./dist/start-server.js",
|
||||
"lint": "eslint . --ext .ts",
|
||||
"test": "jest"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
@@ -21,13 +22,21 @@
|
||||
"y-protocols": "1.0.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@hocuspocus/provider": "2.14.0",
|
||||
"@types/express": "5.0.0",
|
||||
"@types/express-ws": "3.0.5",
|
||||
"@types/jest": "29.5.14",
|
||||
"@types/node": "*",
|
||||
"@types/supertest": "6.0.2",
|
||||
"@types/ws": "8.5.13",
|
||||
"eslint-config-impress": "*",
|
||||
"jest": "29.7.0",
|
||||
"nodemon": "3.1.7",
|
||||
"supertest": "7.0.0",
|
||||
"ts-jest": "29.2.5",
|
||||
"ts-node": "10.9.2",
|
||||
"typescript": "*"
|
||||
"tsc-alias": "1.8.10",
|
||||
"typescript": "*",
|
||||
"ws": "8.18.0"
|
||||
}
|
||||
}
|
||||
|
||||
8
src/frontend/servers/y-provider/src/helpers.ts
Normal file
8
src/frontend/servers/y-provider/src/helpers.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export const promiseDone = () => {
|
||||
let done: (value: void | PromiseLike<void>) => void = () => {};
|
||||
const promise = new Promise<void>((resolve) => {
|
||||
done = resolve;
|
||||
});
|
||||
|
||||
return { done, promise };
|
||||
};
|
||||
7
src/frontend/servers/y-provider/tsconfig.build.json
Normal file
7
src/frontend/servers/y-provider/tsconfig.build.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
},
|
||||
"exclude": ["node_modules", "dist", "__tests__"],
|
||||
}
|
||||
@@ -13,6 +13,13 @@
|
||||
"jsx": "preserve",
|
||||
"incremental": false,
|
||||
"outDir": "./dist",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"tsc-alias": {
|
||||
"resolveFullPaths": true,
|
||||
"verbose": false
|
||||
},
|
||||
"include": ["**/*.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
|
||||
Reference in New Issue
Block a user