chat: New option to completely disable chat

This commit is contained in:
Richard Hansen 2021-12-03 00:00:35 -05:00
parent 310a371234
commit dda284cbe9
13 changed files with 274 additions and 126 deletions

View file

@ -2,6 +2,8 @@
const assert = require('assert').strict;
const common = require('../../common');
const plugins = require('../../../../static/js/pluginfw/plugins');
const settings = require('../../../../node/utils/Settings');
let agent;
const apiKey = common.apiKey;
@ -13,7 +15,12 @@ const timestamp = Date.now();
const endPoint = (point) => `/api/${apiVersion}/${point}?apikey=${apiKey}`;
describe(__filename, function () {
const backups = {settings: {}};
before(async function () {
backups.settings.integratedChat = settings.integratedChat;
settings.integratedChat = true;
await plugins.update();
agent = await common.init();
await agent.get('/api/')
.expect(200)
@ -37,7 +44,16 @@ describe(__filename, function () {
});
});
describe('message sequence', function () {
after(async function () {
Object.assign(settings, backups.settings);
await plugins.update();
});
describe('settings.integratedChat = true', function () {
beforeEach(async function () {
settings.integratedChat = true;
});
it('appendChatMessage', async function () {
await agent.get(`${endPoint('appendChatMessage')}&padID=${padID}&text=blalblalbha` +
`&authorID=${authorID}&time=${timestamp}`)
@ -68,6 +84,40 @@ describe(__filename, function () {
});
});
});
describe('settings.integratedChat = false', function () {
beforeEach(async function () {
settings.integratedChat = false;
});
it('appendChatMessage returns an error', async function () {
await agent.get(`${endPoint('appendChatMessage')}&padID=${padID}&text=blalblalbha` +
`&authorID=${authorID}&time=${timestamp}`)
.expect(500)
.expect('Content-Type', /json/)
.expect((res) => {
assert.equal(res.body.code, 2);
});
});
it('getChatHead returns an error', async function () {
await agent.get(`${endPoint('getChatHead')}&padID=${padID}`)
.expect(500)
.expect('Content-Type', /json/)
.expect((res) => {
assert.equal(res.body.code, 2);
});
});
it('getChatHistory returns an error', async function () {
await agent.get(`${endPoint('getChatHistory')}&padID=${padID}`)
.expect(500)
.expect('Content-Type', /json/)
.expect((res) => {
assert.equal(res.body.code, 2);
});
});
});
});
function makeid() {

View file

@ -6,29 +6,37 @@ const assert = require('assert').strict;
const common = require('../common');
const padManager = require('../../../node/db/PadManager');
const pluginDefs = require('../../../static/js/pluginfw/plugin_defs');
const plugins = require('../../../static/js/pluginfw/plugins');
const settings = require('../../../node/utils/Settings');
const logger = common.logger;
const checkHook = async (hookName, checkFn) => {
if (pluginDefs.hooks[hookName] == null) pluginDefs.hooks[hookName] = [];
await new Promise((resolve, reject) => {
pluginDefs.hooks[hookName].push({
hook_fn: async (hookName, context) => {
if (checkFn == null) return;
logger.debug(`hook ${hookName} invoked`);
try {
// Make sure checkFn is called only once.
const _checkFn = checkFn;
checkFn = null;
await _checkFn(context);
} catch (err) {
reject(err);
return;
}
resolve();
},
let hook;
try {
await new Promise((resolve, reject) => {
hook = {
hook_fn: async (hookName, context) => {
if (checkFn == null) return;
logger.debug(`hook ${hookName} invoked`);
try {
// Make sure checkFn is called only once.
const _checkFn = checkFn;
checkFn = null;
await _checkFn(context);
} catch (err) {
reject(err);
return;
}
resolve();
},
};
pluginDefs.hooks[hookName].push(hook);
});
});
} finally {
pluginDefs.hooks[hookName] = pluginDefs.hooks[hookName].filter((h) => h !== hook);
}
};
const sendMessage = async (socket, data) => (
@ -37,119 +45,146 @@ const sendChat = async (socket, message) => (
await sendMessage(socket, {type: 'CHAT_MESSAGE', message}));
describe(__filename, function () {
const backups = {settings: {}};
let clientVars;
const padId = 'testChatPad';
const hooksBackup = {};
let socket;
const connect = async () => {
socket = await common.connect();
({data: clientVars} = await common.handshake(socket, padId));
};
before(async function () {
for (const [name, defs] of Object.entries(pluginDefs.hooks)) {
if (defs == null) continue;
hooksBackup[name] = defs;
}
backups.settings.integratedChat = settings.integratedChat;
});
beforeEach(async function () {
for (const [name, defs] of Object.entries(hooksBackup)) pluginDefs.hooks[name] = [...defs];
for (const name of Object.keys(pluginDefs.hooks)) {
if (hooksBackup[name] == null) delete pluginDefs.hooks[name];
}
if (await padManager.doesPadExist(padId)) {
const pad = await padManager.getPad(padId);
await pad.remove();
}
});
after(async function () {
Object.assign(pluginDefs.hooks, hooksBackup);
for (const name of Object.keys(pluginDefs.hooks)) {
if (hooksBackup[name] == null) delete pluginDefs.hooks[name];
afterEach(async function () {
if (socket) {
socket.close();
socket = null;
}
});
describe('chatNewMessage hook', function () {
let authorId;
let socket;
after(async function () {
Object.assign(settings, backups.settings);
await plugins.update();
});
describe('settings.integratedChat = true', function () {
before(async function () {
settings.integratedChat = true;
await plugins.update();
});
beforeEach(async function () {
socket = await common.connect();
const {data: clientVars} = await common.handshake(socket, padId);
authorId = clientVars.userId;
await connect();
});
afterEach(async function () {
socket.close();
});
it('message', async function () {
const start = Date.now();
await Promise.all([
checkHook('chatNewMessage', ({message}) => {
assert(message != null);
assert(message instanceof ChatMessage);
assert.equal(message.authorId, authorId);
assert.equal(message.text, this.test.title);
assert(message.time >= start);
assert(message.time <= Date.now());
}),
sendChat(socket, {text: this.test.title}),
]);
});
it('pad', async function () {
await Promise.all([
checkHook('chatNewMessage', ({pad}) => {
assert(pad != null);
assert(pad instanceof Pad);
assert.equal(pad.id, padId);
}),
sendChat(socket, {text: this.test.title}),
]);
});
it('padId', async function () {
await Promise.all([
checkHook('chatNewMessage', (context) => {
assert.equal(context.padId, padId);
}),
sendChat(socket, {text: this.test.title}),
]);
});
it('mutations propagate', async function () {
const listen = async (type) => await new Promise((resolve) => {
const handler = (msg) => {
if (msg.type !== 'COLLABROOM') return;
if (msg.data == null || msg.data.type !== type) return;
resolve(msg.data);
socket.off('message', handler);
};
socket.on('message', handler);
describe('chatNewMessage hook', function () {
it('message', async function () {
const start = Date.now();
await Promise.all([
checkHook('chatNewMessage', ({message}) => {
assert(message != null);
assert(message instanceof ChatMessage);
assert.equal(message.authorId, clientVars.userId);
assert.equal(message.text, this.test.title);
assert(message.time >= start);
assert(message.time <= Date.now());
}),
sendChat(socket, {text: this.test.title}),
]);
});
const modifiedText = `${this.test.title} <added changes>`;
const customMetadata = {foo: this.test.title};
await Promise.all([
checkHook('chatNewMessage', ({message}) => {
message.text = modifiedText;
message.customMetadata = customMetadata;
}),
(async () => {
const {message} = await listen('CHAT_MESSAGE');
assert(message != null);
assert.equal(message.text, modifiedText);
assert.deepEqual(message.customMetadata, customMetadata);
})(),
sendChat(socket, {text: this.test.title}),
]);
// Simulate fetch of historical chat messages when a pad is first loaded.
await Promise.all([
(async () => {
const {messages: [message]} = await listen('CHAT_MESSAGES');
assert(message != null);
assert.equal(message.text, modifiedText);
assert.deepEqual(message.customMetadata, customMetadata);
})(),
sendMessage(socket, {type: 'GET_CHAT_MESSAGES', start: 0, end: 0}),
]);
it('pad', async function () {
await Promise.all([
checkHook('chatNewMessage', ({pad}) => {
assert(pad != null);
assert(pad instanceof Pad);
assert.equal(pad.id, padId);
}),
sendChat(socket, {text: this.test.title}),
]);
});
it('padId', async function () {
await Promise.all([
checkHook('chatNewMessage', (context) => {
assert.equal(context.padId, padId);
}),
sendChat(socket, {text: this.test.title}),
]);
});
it('mutations propagate', async function () {
const listen = async (type) => await new Promise((resolve) => {
const handler = (msg) => {
if (msg.type !== 'COLLABROOM') return;
if (msg.data == null || msg.data.type !== type) return;
resolve(msg.data);
socket.off('message', handler);
};
socket.on('message', handler);
});
const modifiedText = `${this.test.title} <added changes>`;
const customMetadata = {foo: this.test.title};
await Promise.all([
checkHook('chatNewMessage', ({message}) => {
message.text = modifiedText;
message.customMetadata = customMetadata;
}),
(async () => {
const {message} = await listen('CHAT_MESSAGE');
assert(message != null);
assert.equal(message.text, modifiedText);
assert.deepEqual(message.customMetadata, customMetadata);
})(),
sendChat(socket, {text: this.test.title}),
]);
// Simulate fetch of historical chat messages when a pad is first loaded.
await Promise.all([
(async () => {
const {messages: [message]} = await listen('CHAT_MESSAGES');
assert(message != null);
assert.equal(message.text, modifiedText);
assert.deepEqual(message.customMetadata, customMetadata);
})(),
sendMessage(socket, {type: 'GET_CHAT_MESSAGES', start: 0, end: 0}),
]);
});
});
});
describe('settings.integratedChat = false', function () {
before(async function () {
settings.integratedChat = false;
await plugins.update();
});
beforeEach(async function () {
await connect();
});
it('clientVars.chatHead is unset', async function () {
assert(!('chatHead' in clientVars), `chatHead should be unset, is ${clientVars.chatHead}`);
});
it('rejects CHAT_MESSAGE messages', async function () {
await assert.rejects(sendChat(socket, {text: 'this is a test'}), /unknown message type/);
});
it('rejects GET_CHAT_MESSAGES messages', async function () {
const msg = {type: 'GET_CHAT_MESSAGES', start: 0, end: 0};
await assert.rejects(sendMessage(socket, msg), /unknown message type/);
});
});
});