154 lines
4.5 KiB
JavaScript
154 lines
4.5 KiB
JavaScript
|
|
const WebSocket = require('ws');
|
||
|
|
const request = require('supertest');
|
||
|
|
const { app, server } = require('../../backend/server');
|
||
|
|
const { getAuthToken, getAuthHeader, cleanDb, seedGame, seedSession } = require('../helpers/test-utils');
|
||
|
|
|
||
|
|
function connectWs() {
|
||
|
|
return new WebSocket(`ws://localhost:${server.address().port}/api/sessions/live`);
|
||
|
|
}
|
||
|
|
|
||
|
|
function waitForMessage(ws, type, timeoutMs = 3000) {
|
||
|
|
return new Promise((resolve, reject) => {
|
||
|
|
const timeout = setTimeout(() => reject(new Error(`Timeout waiting for ${type}`)), timeoutMs);
|
||
|
|
ws.on('message', function handler(data) {
|
||
|
|
const msg = JSON.parse(data.toString());
|
||
|
|
if (msg.type === type) {
|
||
|
|
clearTimeout(timeout);
|
||
|
|
ws.removeListener('message', handler);
|
||
|
|
resolve(msg);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
function authenticateAndSubscribe(ws, sessionId) {
|
||
|
|
return new Promise(async (resolve, reject) => {
|
||
|
|
try {
|
||
|
|
ws.send(JSON.stringify({ type: 'auth', token: getAuthToken() }));
|
||
|
|
await waitForMessage(ws, 'auth_success');
|
||
|
|
ws.send(JSON.stringify({ type: 'subscribe', sessionId }));
|
||
|
|
await waitForMessage(ws, 'subscribed');
|
||
|
|
resolve();
|
||
|
|
} catch (err) {
|
||
|
|
reject(err);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
beforeAll((done) => {
|
||
|
|
server.listen(0, () => done());
|
||
|
|
});
|
||
|
|
|
||
|
|
afterAll((done) => {
|
||
|
|
server.close(done);
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('WebSocket events (regression)', () => {
|
||
|
|
beforeEach(() => {
|
||
|
|
cleanDb();
|
||
|
|
});
|
||
|
|
|
||
|
|
test('auth flow: auth -> auth_success', (done) => {
|
||
|
|
const ws = connectWs();
|
||
|
|
ws.on('open', () => {
|
||
|
|
ws.send(JSON.stringify({ type: 'auth', token: getAuthToken() }));
|
||
|
|
});
|
||
|
|
ws.on('message', (data) => {
|
||
|
|
const msg = JSON.parse(data.toString());
|
||
|
|
if (msg.type === 'auth_success') {
|
||
|
|
ws.close();
|
||
|
|
done();
|
||
|
|
}
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
test('subscribe/unsubscribe flow', async () => {
|
||
|
|
const session = seedSession({ is_active: 1 });
|
||
|
|
const ws = connectWs();
|
||
|
|
await new Promise((resolve) => ws.on('open', resolve));
|
||
|
|
|
||
|
|
ws.send(JSON.stringify({ type: 'auth', token: getAuthToken() }));
|
||
|
|
await waitForMessage(ws, 'auth_success');
|
||
|
|
|
||
|
|
ws.send(JSON.stringify({ type: 'subscribe', sessionId: session.id }));
|
||
|
|
const subMsg = await waitForMessage(ws, 'subscribed');
|
||
|
|
expect(subMsg.sessionId).toBe(session.id);
|
||
|
|
|
||
|
|
ws.send(JSON.stringify({ type: 'unsubscribe', sessionId: session.id }));
|
||
|
|
const unsubMsg = await waitForMessage(ws, 'unsubscribed');
|
||
|
|
expect(unsubMsg.sessionId).toBe(session.id);
|
||
|
|
|
||
|
|
ws.close();
|
||
|
|
});
|
||
|
|
|
||
|
|
test('session.started broadcasts to all authenticated clients', async () => {
|
||
|
|
const ws = connectWs();
|
||
|
|
await new Promise((resolve) => ws.on('open', resolve));
|
||
|
|
|
||
|
|
ws.send(JSON.stringify({ type: 'auth', token: getAuthToken() }));
|
||
|
|
await waitForMessage(ws, 'auth_success');
|
||
|
|
|
||
|
|
const eventPromise = waitForMessage(ws, 'session.started');
|
||
|
|
|
||
|
|
await request(app)
|
||
|
|
.post('/api/sessions')
|
||
|
|
.set('Authorization', getAuthHeader())
|
||
|
|
.set('Content-Type', 'application/json')
|
||
|
|
.send({ notes: 'Test session' });
|
||
|
|
|
||
|
|
const event = await eventPromise;
|
||
|
|
expect(event.data.session).toEqual(
|
||
|
|
expect.objectContaining({
|
||
|
|
is_active: 1,
|
||
|
|
notes: 'Test session',
|
||
|
|
})
|
||
|
|
);
|
||
|
|
|
||
|
|
ws.close();
|
||
|
|
});
|
||
|
|
|
||
|
|
test('session.ended broadcasts to session subscribers', async () => {
|
||
|
|
const session = seedSession({ is_active: 1 });
|
||
|
|
const ws = connectWs();
|
||
|
|
await new Promise((resolve) => ws.on('open', resolve));
|
||
|
|
|
||
|
|
await authenticateAndSubscribe(ws, session.id);
|
||
|
|
|
||
|
|
const eventPromise = waitForMessage(ws, 'session.ended');
|
||
|
|
|
||
|
|
await request(app)
|
||
|
|
.post(`/api/sessions/${session.id}/close`)
|
||
|
|
.set('Authorization', getAuthHeader())
|
||
|
|
.set('Content-Type', 'application/json');
|
||
|
|
|
||
|
|
const event = await eventPromise;
|
||
|
|
expect(event.data.session.id).toBe(session.id);
|
||
|
|
expect(event.data.session.is_active).toBe(0);
|
||
|
|
|
||
|
|
ws.close();
|
||
|
|
});
|
||
|
|
|
||
|
|
test('game.added broadcasts to session subscribers', async () => {
|
||
|
|
const game = seedGame({ title: 'Quiplash 3', pack_name: 'Party Pack 7' });
|
||
|
|
const session = seedSession({ is_active: 1 });
|
||
|
|
const ws = connectWs();
|
||
|
|
await new Promise((resolve) => ws.on('open', resolve));
|
||
|
|
|
||
|
|
await authenticateAndSubscribe(ws, session.id);
|
||
|
|
|
||
|
|
const eventPromise = waitForMessage(ws, 'game.added');
|
||
|
|
|
||
|
|
await request(app)
|
||
|
|
.post(`/api/sessions/${session.id}/games`)
|
||
|
|
.set('Authorization', getAuthHeader())
|
||
|
|
.set('Content-Type', 'application/json')
|
||
|
|
.send({ game_id: game.id });
|
||
|
|
|
||
|
|
const event = await eventPromise;
|
||
|
|
expect(event.data.game.title).toBe('Quiplash 3');
|
||
|
|
expect(event.data.session.id).toBe(session.id);
|
||
|
|
|
||
|
|
ws.close();
|
||
|
|
});
|
||
|
|
});
|