Files
NodeBB/test/plugins.js

407 lines
12 KiB
JavaScript
Raw Normal View History

2016-10-14 21:48:38 +03:00
'use strict';
const assert = require('assert');
2021-02-04 00:06:15 -07:00
const path = require('path');
const nconf = require('nconf');
2021-02-04 00:06:15 -07:00
const fs = require('fs');
2016-10-14 21:48:38 +03:00
2021-02-04 00:06:15 -07:00
const db = require('./mocks/databasemock');
const plugins = require('../src/plugins');
const request = require('../src/request');
2016-10-14 21:48:38 +03:00
2021-02-04 00:01:39 -07:00
describe('Plugins', () => {
it('should load plugin data', (done) => {
2021-02-04 00:06:15 -07:00
const pluginId = 'nodebb-plugin-markdown';
2021-02-04 00:01:39 -07:00
plugins.loadPlugin(path.join(nconf.get('base_dir'), `node_modules/${pluginId}`), (err) => {
2016-10-14 21:48:38 +03:00
assert.ifError(err);
assert(plugins.libraries[pluginId]);
assert(plugins.loadedHooks['static:app.load']);
done();
});
});
2021-02-04 00:01:39 -07:00
it('should return true if hook has listeners', (done) => {
2021-01-27 17:36:58 -05:00
assert(plugins.hooks.hasListeners('filter:parse.post'));
2016-10-14 21:48:38 +03:00
done();
});
2021-02-04 00:01:39 -07:00
it('should register and fire a filter hook', (done) => {
2016-10-14 21:48:38 +03:00
function filterMethod1(data, callback) {
data.foo += 1;
2016-10-14 21:48:38 +03:00
callback(null, data);
}
function filterMethod2(data, callback) {
data.foo += 5;
callback(null, data);
}
2021-01-27 17:36:58 -05:00
plugins.hooks.register('test-plugin', { hook: 'filter:test.hook', method: filterMethod1 });
plugins.hooks.register('test-plugin', { hook: 'filter:test.hook', method: filterMethod2 });
2016-10-14 21:48:38 +03:00
2021-02-04 00:01:39 -07:00
plugins.hooks.fire('filter:test.hook', { foo: 1 }, (err, data) => {
2016-10-14 21:48:38 +03:00
assert.ifError(err);
assert.equal(data.foo, 7);
done();
});
});
it('should register and fire a filter hook having 3 methods', async () => {
function method1(data, callback) {
data.foo += 1;
callback(null, data);
}
2021-03-11 22:12:05 -05:00
async function method2(data) {
2021-02-04 00:01:39 -07:00
return new Promise((resolve) => {
data.foo += 5;
resolve(data);
});
}
2021-04-28 15:00:43 -04:00
function method3(data) {
data.foo += 1;
return data;
}
2021-01-27 17:36:58 -05:00
plugins.hooks.register('test-plugin', { hook: 'filter:test.hook2', method: method1 });
plugins.hooks.register('test-plugin', { hook: 'filter:test.hook2', method: method2 });
2021-04-28 15:00:43 -04:00
plugins.hooks.register('test-plugin', { hook: 'filter:test.hook2', method: method3 });
const data = await plugins.hooks.fire('filter:test.hook2', { foo: 1 });
2021-04-28 15:00:43 -04:00
assert.strictEqual(data.foo, 8);
});
2021-04-29 10:47:23 -04:00
it('should not error with invalid hooks', async () => {
function method1(data, callback) {
data.foo += 1;
return data;
}
function method2(data, callback) {
data.foo += 2;
// this is invalid
callback(null, data);
return data;
}
plugins.hooks.register('test-plugin', { hook: 'filter:test.hook3', method: method1 });
plugins.hooks.register('test-plugin', { hook: 'filter:test.hook3', method: method2 });
const data = await plugins.hooks.fire('filter:test.hook3', { foo: 1 });
assert.strictEqual(data.foo, 4);
});
2021-02-04 00:01:39 -07:00
it('should register and fire a filter hook that returns a promise that gets rejected', (done) => {
2021-03-11 22:12:05 -05:00
async function method(data) {
2021-02-04 00:01:39 -07:00
return new Promise((resolve, reject) => {
data.foo += 5;
reject(new Error('nope'));
});
}
2021-04-29 10:47:23 -04:00
plugins.hooks.register('test-plugin', { hook: 'filter:test.hook4', method: method });
plugins.hooks.fire('filter:test.hook4', { foo: 1 }, (err) => {
assert(err);
done();
});
});
2021-02-04 00:01:39 -07:00
it('should register and fire an action hook', (done) => {
2016-10-14 21:48:38 +03:00
function actionMethod(data) {
assert.equal(data.bar, 'test');
done();
}
2021-01-27 17:36:58 -05:00
plugins.hooks.register('test-plugin', { hook: 'action:test.hook', method: actionMethod });
plugins.hooks.fire('action:test.hook', { bar: 'test' });
2016-10-14 21:48:38 +03:00
});
2021-02-04 00:01:39 -07:00
it('should register and fire a static hook', (done) => {
2016-10-14 21:48:38 +03:00
function actionMethod(data, callback) {
assert.equal(data.bar, 'test');
callback();
}
2021-01-27 17:36:58 -05:00
plugins.hooks.register('test-plugin', { hook: 'static:test.hook', method: actionMethod });
2021-02-04 00:01:39 -07:00
plugins.hooks.fire('static:test.hook', { bar: 'test' }, (err) => {
2016-10-14 21:48:38 +03:00
assert.ifError(err);
done();
2016-10-14 21:48:38 +03:00
});
});
2021-02-04 00:01:39 -07:00
it('should register and fire a static hook returning a promise', (done) => {
2021-03-11 22:12:05 -05:00
async function method(data) {
assert.equal(data.bar, 'test');
return new Promise((resolve) => {
resolve();
});
}
2021-01-27 17:36:58 -05:00
plugins.hooks.register('test-plugin', { hook: 'static:test.hook', method: method });
2021-02-04 00:01:39 -07:00
plugins.hooks.fire('static:test.hook', { bar: 'test' }, (err) => {
assert.ifError(err);
done();
});
});
2021-02-04 00:01:39 -07:00
it('should register and fire a static hook returning a promise that gets rejected with a error', (done) => {
2021-03-11 22:12:05 -05:00
async function method(data) {
assert.equal(data.bar, 'test');
2021-02-04 00:01:39 -07:00
return new Promise((resolve, reject) => {
reject(new Error('just because'));
});
}
2021-01-27 17:36:58 -05:00
plugins.hooks.register('test-plugin', { hook: 'static:test.hook', method: method });
2021-02-04 00:01:39 -07:00
plugins.hooks.fire('static:test.hook', { bar: 'test' }, (err) => {
assert.strictEqual(err.message, 'just because');
2021-01-27 17:36:58 -05:00
plugins.hooks.unregister('test-plugin', 'static:test.hook', method);
done();
});
});
2021-03-11 22:56:14 -05:00
it('should register and timeout a static hook returning a promise but takes too long', (done) => {
async function method(data) {
assert.equal(data.bar, 'test');
return new Promise((resolve) => {
setTimeout(resolve, 6000);
});
}
plugins.hooks.register('test-plugin', { hook: 'static:test.hook', method: method });
plugins.hooks.fire('static:test.hook', { bar: 'test' }, (err) => {
assert.ifError(err);
plugins.hooks.unregister('test-plugin', 'static:test.hook', method);
done();
});
});
2021-02-04 00:01:39 -07:00
it('should get plugin data from nbbpm', (done) => {
plugins.get('nodebb-plugin-markdown', (err, data) => {
2016-10-27 11:57:07 +03:00
assert.ifError(err);
2021-02-04 00:06:15 -07:00
const keys = ['id', 'name', 'url', 'description', 'latest', 'installed', 'active', 'latest'];
2016-10-27 11:57:07 +03:00
assert.equal(data.name, 'nodebb-plugin-markdown');
assert.equal(data.id, 'nodebb-plugin-markdown');
2021-02-04 00:01:39 -07:00
keys.forEach((key) => {
2016-10-27 11:57:07 +03:00
assert(data.hasOwnProperty(key));
});
done();
});
});
2021-02-04 00:01:39 -07:00
it('should get a list of plugins', (done) => {
plugins.list((err, data) => {
2016-10-27 11:57:07 +03:00
assert.ifError(err);
2021-02-04 00:06:15 -07:00
const keys = ['id', 'name', 'url', 'description', 'latest', 'installed', 'active', 'latest'];
2016-10-27 11:57:07 +03:00
assert(Array.isArray(data));
2021-02-04 00:01:39 -07:00
keys.forEach((key) => {
2016-10-27 11:57:07 +03:00
assert(data[0].hasOwnProperty(key));
});
done();
});
});
2021-02-04 00:01:39 -07:00
it('should show installed plugins', (done) => {
2021-02-06 14:10:15 -07:00
const { nodeModulesPath } = plugins;
2017-07-19 11:38:51 -06:00
plugins.nodeModulesPath = path.join(__dirname, './mocks/plugin_modules');
2021-02-04 00:01:39 -07:00
plugins.showInstalled((err, pluginsData) => {
2017-07-19 11:38:51 -06:00
assert.ifError(err);
2021-02-04 00:06:15 -07:00
const paths = pluginsData.map(plugin => path.relative(plugins.nodeModulesPath, plugin.path).replace(/\\/g, '/'));
2017-07-19 11:38:51 -06:00
assert(paths.indexOf('nodebb-plugin-xyz') > -1);
assert(paths.indexOf('@nodebb/nodebb-plugin-abc') > -1);
plugins.nodeModulesPath = nodeModulesPath;
done();
});
});
2021-11-22 21:10:12 -05:00
it('should submit usage info', (done) => {
2021-11-22 21:17:54 -05:00
plugins.submitUsageData((err) => {
2021-11-22 21:10:12 -05:00
assert.ifError(err);
done();
});
});
2021-02-04 00:01:39 -07:00
describe('install/activate/uninstall', () => {
2021-02-04 00:06:15 -07:00
let latest;
const pluginName = 'nodebb-plugin-imgur';
const oldValue = process.env.NODE_ENV;
before((done) => {
process.env.NODE_ENV = 'development';
done();
});
after((done) => {
process.env.NODE_ENV = oldValue;
done();
});
2016-11-18 14:03:06 +03:00
it('should install a plugin', function (done) {
2018-09-29 06:45:12 -04:00
this.timeout(0);
2021-02-04 00:01:39 -07:00
plugins.toggleInstall(pluginName, '1.0.16', (err, pluginData) => {
2016-11-18 14:03:06 +03:00
assert.ifError(err);
latest = pluginData.latest;
2016-11-18 14:03:06 +03:00
assert.equal(pluginData.name, pluginName);
assert.equal(pluginData.id, pluginName);
assert.equal(pluginData.url, 'https://github.com/barisusakli/nodebb-plugin-imgur#readme');
assert.equal(pluginData.description, 'A Plugin that uploads images to imgur');
assert.equal(pluginData.active, false);
assert.equal(pluginData.installed, true);
2021-02-04 00:06:15 -07:00
const packageFile = JSON.parse(fs.readFileSync(path.join(__dirname, '../package.json'), 'utf8'));
assert(packageFile.dependencies[pluginName]);
2016-11-18 14:03:06 +03:00
done();
});
});
2021-02-04 00:01:39 -07:00
it('should activate plugin', (done) => {
plugins.toggleActive(pluginName, (err) => {
2016-11-18 14:03:06 +03:00
assert.ifError(err);
2021-02-04 00:01:39 -07:00
plugins.isActive(pluginName, (err, isActive) => {
2016-11-18 14:03:06 +03:00
assert.ifError(err);
assert(isActive);
done();
});
});
});
2024-01-17 15:18:26 -05:00
it('should error if plugin id is invalid', async () => {
await assert.rejects(
plugins.toggleActive('\t\nnodebb-plugin'),
{ message: '[[error:invalid-plugin-id]]' }
);
await assert.rejects(
plugins.toggleActive('notaplugin'),
{ message: '[[error:invalid-plugin-id]]' }
);
});
2016-11-18 14:03:06 +03:00
it('should upgrade plugin', function (done) {
2018-09-29 06:45:12 -04:00
this.timeout(0);
2021-02-04 00:01:39 -07:00
plugins.upgrade(pluginName, 'latest', (err, isActive) => {
2016-11-18 14:03:06 +03:00
assert.ifError(err);
assert(isActive);
2021-02-04 00:01:39 -07:00
plugins.loadPluginInfo(path.join(nconf.get('base_dir'), 'node_modules', pluginName), (err, pluginInfo) => {
2016-11-18 14:03:06 +03:00
assert.ifError(err);
assert.equal(pluginInfo.version, latest);
done();
});
});
});
it('should uninstall a plugin', function (done) {
2018-09-29 06:45:12 -04:00
this.timeout(0);
2021-02-04 00:01:39 -07:00
plugins.toggleInstall(pluginName, 'latest', (err, pluginData) => {
2016-11-18 14:03:06 +03:00
assert.ifError(err);
assert.equal(pluginData.installed, false);
assert.equal(pluginData.active, false);
2021-02-04 00:06:15 -07:00
const packageFile = JSON.parse(fs.readFileSync(path.join(__dirname, '../package.json'), 'utf8'));
assert(!packageFile.dependencies[pluginName]);
2016-11-18 14:03:06 +03:00
done();
});
});
});
2021-02-04 00:01:39 -07:00
describe('static assets', () => {
it('should 404 if resource does not exist', async () => {
const { response, body } = await request.get(`${nconf.get('url')}/plugins/doesnotexist/should404.tpl`);
assert.equal(response.statusCode, 404);
assert(body);
2016-11-30 18:21:30 +03:00
});
it('should 404 if resource does not exist', async () => {
const url = `${nconf.get('url')}/plugins/nodebb-plugin-dbsearch/dbsearch/templates/admin/plugins/should404.tpl`;
const { response, body } = await request.get(url);
assert.equal(response.statusCode, 404);
assert(body);
2016-11-30 18:21:30 +03:00
});
it('should get resource', async () => {
2021-11-05 20:21:19 -04:00
const url = `${nconf.get('url')}/assets/templates/admin/plugins/dbsearch.tpl`;
const { response, body } = await request.get(url);
assert.equal(response.statusCode, 200);
assert(body);
2016-11-30 18:21:30 +03:00
});
});
feat: Allow defining active plugins in config (#10767) * Revert "Revert "feat: cross origin opener policy options (#10710)"" This reverts commit 46050ace1a65430fe1b567086727da22afab4f73. * Revert "Revert "chore(i18n): fallback strings for new resources: nodebb.admin-settings-advanced"" This reverts commit 9f291c07d3423a41550b38770620a998b45e5c55. * feat: closes #10719, don't trim children if category is marked section * feat: fire hook to allow plugins to filter the pids returned in a user profile /cc julianlam/nodebb-plugin-support-forum#14 * fix: use `user.hidePrivateData();` more consistently across user retrieval endpoints * feat: Allow defining active plugins in config resolves #10766 * fix: assign the db result to files properly * test: add tests with plugins in config * feat: better theme change handling * feat: add visual indication that plugins can't be activated * test: correct hooks * test: fix test definitions * test: remove instead of resetting nconf to avoid affecting other tests * test: ... I forgot how nconf worked * fix: remove negation * docs: improve wording of error message * feat: reduce code duplication * style: remove a redundant space * fix: remove unused imports * fix: use nconf instead of requiring config.json * fix: await... * fix: second missed await * fix: move back from getActiveIds to getActive * fix: use paths again? * fix: typo * fix: move require into the function * fix: forgot to change back to getActive * test: getActive returns only id * test: accedently commented out some stuff * feat: added note to top of plugins page if \!canChangeState Co-authored-by: Julian Lam <julian@nodebb.org> Co-authored-by: Barış Soner Uşaklı <barisusakli@gmail.com>
2022-07-25 20:04:55 +02:00
describe('plugin state set in configuration', () => {
const activePlugins = [
'nodebb-plugin-markdown',
'nodebb-plugin-mentions',
];
const inactivePlugin = 'nodebb-plugin-emoji';
beforeEach((done) => {
nconf.set('plugins:active', activePlugins);
done();
});
afterEach((done) => {
nconf.set('plugins:active', undefined);
done();
});
it('should return active plugin state from configuration', (done) => {
plugins.isActive(activePlugins[0], (err, isActive) => {
assert.ifError(err);
assert(isActive);
done();
});
});
it('should return inactive plugin state if not in configuration', (done) => {
plugins.isActive(inactivePlugin, (err, isActive) => {
assert.ifError(err);
assert(!isActive);
done();
});
});
it('should get a list of plugins from configuration', (done) => {
plugins.list((err, data) => {
assert.ifError(err);
const keys = ['id', 'name', 'url', 'description', 'latest', 'installed', 'active', 'latest'];
assert(Array.isArray(data));
keys.forEach((key) => {
assert(data[0].hasOwnProperty(key));
});
data.forEach((pluginData) => {
assert.equal(pluginData.active, activePlugins.includes(pluginData.id));
});
done();
});
});
it('should return a list of only active plugins from configuration', (done) => {
plugins.getActive((err, data) => {
assert.ifError(err);
assert(Array.isArray(data));
data.forEach((pluginData) => {
assert(activePlugins.includes(pluginData));
});
done();
});
});
it('should not deactivate a plugin if active plugins are set in configuration', (done) => {
assert.rejects(plugins.toggleActive(activePlugins[0]), Error).then(() => {
plugins.isActive(activePlugins[0], (err, isActive) => {
assert.ifError(err);
assert(isActive);
done();
});
});
});
it('should not activate a plugin if active plugins are set in configuration', (done) => {
assert.rejects(plugins.toggleActive(inactivePlugin), Error).then(() => {
plugins.isActive(inactivePlugin, (err, isActive) => {
assert.ifError(err);
assert(!isActive);
done();
});
});
});
});
2016-10-14 21:48:38 +03:00
});