👋Request, 🐶 Fetch, closes #10341 (#12236)

* axios migration

* controller tests

* add missing deps

* feeds

* remove unused async

* flags

* locale-detect

* messaging/middleware

* remove log

* meta

* plugins

* posts

* search

* topics/thumbs

* user/emails

* uploads.js

* socket.io

* cleaunup

* test native fetch

* cleanup

* increase engine to 18

fix remaining tests

* remove testing file

* fix comments,typo

* revert debug
This commit is contained in:
Barış Soner Uşaklı
2023-12-18 12:08:34 -05:00
committed by GitHub
parent 451430006e
commit c15bdd4cf0
31 changed files with 2895 additions and 5301 deletions

View File

@@ -67,6 +67,7 @@
"express": "4.18.2",
"express-session": "1.17.3",
"express-useragent": "1.0.15",
"fetch-cookie": "2.1.0",
"file-loader": "6.2.0",
"fs-extra": "11.2.0",
"graceful-fs": "4.2.11",
@@ -119,8 +120,6 @@
"progress-webpack-plugin": "1.0.16",
"prompt": "1.3.0",
"ioredis": "5.3.2",
"request": "2.88.2",
"request-promise-native": "1.0.9",
"rimraf": "5.0.5",
"rss": "1.2.2",
"rtlcss": "4.1.1",
@@ -142,6 +141,7 @@
"timeago": "1.6.7",
"tinycon": "0.6.8",
"toobusy-js": "0.5.1",
"tough-cookie": "4.1.3",
"validator": "13.11.0",
"webpack": "5.89.0",
"webpack-merge": "5.10.0",
@@ -181,7 +181,7 @@
"url": "https://github.com/NodeBB/NodeBB/issues"
},
"engines": {
"node": ">=16"
"node": ">=18"
},
"maintainers": [
{

View File

@@ -62,9 +62,6 @@ define('forum/flags/detail', [
Detail.reloadHistory(payload.history);
}).catch(alerts.error);
},
onShown: (e) => {
console.log(e);
},
});
break;
}

View File

@@ -1,15 +1,15 @@
'use strict';
const request = require('request');
const request = require('../request');
const meta = require('../meta');
let versionCache = '';
let versionCacheLastModified = '';
const isPrerelease = /^v?\d+\.\d+\.\d+-.+$/;
const latestReleaseUrl = 'https://api.github.com/repos/NodeBB/NodeBB/releases/latest';
function getLatestVersion(callback) {
async function getLatestVersion() {
const headers = {
Accept: 'application/vnd.github.v3+json',
'User-Agent': encodeURIComponent(`NodeBB Admin Control Panel/${meta.config.title}`),
@@ -19,31 +19,23 @@ function getLatestVersion(callback) {
headers['If-Modified-Since'] = versionCacheLastModified;
}
request('https://api.github.com/repos/NodeBB/NodeBB/releases/latest', {
json: true,
const { body: latestRelease, response } = await request.get(latestReleaseUrl, {
headers: headers,
timeout: 2000,
}, (err, res, latestRelease) => {
if (err) {
return callback(err);
});
if (response.statusCode === 304) {
return versionCache;
}
if (res.statusCode === 304) {
return callback(null, versionCache);
if (response.statusCode !== 200) {
throw new Error(response.statusText);
}
if (res.statusCode !== 200) {
return callback(new Error(res.statusMessage));
}
if (!latestRelease || !latestRelease.tag_name) {
return callback(new Error('[[error:cant-get-latest-release]]'));
throw new Error('[[error:cant-get-latest-release]]');
}
const tagName = latestRelease.tag_name.replace(/^v/, '');
versionCache = tagName;
versionCacheLastModified = res.headers['last-modified'];
callback(null, versionCache);
});
versionCacheLastModified = response.headers['last-modified'];
return versionCache;
}
exports.getLatestVersion = getLatestVersion;

View File

@@ -1,13 +1,13 @@
'use strict';
const prompt = require('prompt');
const request = require('request-promise-native');
const cproc = require('child_process');
const semver = require('semver');
const fs = require('fs');
const path = require('path');
const chalk = require('chalk');
const request = require('../request');
const { paths, pluginNamePattern } = require('../constants');
const pkgInstall = require('./package-install');
@@ -74,11 +74,10 @@ async function getCurrentVersion() {
}
async function getSuggestedModules(nbbVersion, toCheck) {
let body = await request({
method: 'GET',
url: `https://packages.nodebb.org/api/v1/suggest?version=${nbbVersion}&package[]=${toCheck.join('&package[]=')}`,
json: true,
});
let { response, body } = await request.get(`https://packages.nodebb.org/api/v1/suggest?version=${nbbVersion}&package[]=${toCheck.join('&package[]=')}`);
if (!response.ok) {
throw new Error(`Unable to get suggested module for NodeBB(${nbbVersion}) ${toCheck.join(',')}`);
}
if (!Array.isArray(body) && toCheck.length === 1) {
body = [body];
}

View File

@@ -6,8 +6,8 @@ const winston = require('winston');
const semver = require('semver');
const nconf = require('nconf');
const chalk = require('chalk');
const request = require('request-promise-native');
const request = require('../request');
const user = require('../user');
const posts = require('../posts');
@@ -153,10 +153,10 @@ Plugins.reloadRoutes = async function (params) {
Plugins.get = async function (id) {
const url = `${nconf.get('registry') || 'https://packages.nodebb.org'}/api/v1/plugins/${id}`;
const body = await request(url, {
json: true,
});
const { response, body } = await request.get(url);
if (!response.ok) {
throw new Error(`[[error:unable-to-load-plugin, ${id}]]`);
}
let normalised = await Plugins.normalise([body ? body.payload : {}]);
normalised = normalised.filter(plugin => plugin.id === id);
return normalised.length ? normalised[0] : undefined;
@@ -169,9 +169,10 @@ Plugins.list = async function (matching) {
const { version } = require(paths.currentPackage);
const url = `${nconf.get('registry') || 'https://packages.nodebb.org'}/api/v1/plugins${matching !== false ? `?version=${version}` : ''}`;
try {
const body = await request(url, {
json: true,
});
const { response, body } = await request.get(url);
if (!response.ok) {
throw new Error(`[[error:unable-to-load-plugins-from-nbbpm]]`);
}
return await Plugins.normalise(body);
} catch (err) {
winston.error(`Error loading ${url}`, err);
@@ -181,9 +182,11 @@ Plugins.list = async function (matching) {
Plugins.listTrending = async () => {
const url = `${nconf.get('registry') || 'https://packages.nodebb.org'}/api/v1/analytics/top/week`;
return await request(url, {
json: true,
});
const { response, body } = await request.get(url);
if (!response.ok) {
throw new Error(`[[error:unable-to-load-trending-plugins]]`);
}
return body;
};
Plugins.normalise = async function (apiReturn) {

View File

@@ -7,8 +7,8 @@ const nconf = require('nconf');
const os = require('os');
const cproc = require('child_process');
const util = require('util');
const request = require('request-promise-native');
const request = require('../request');
const db = require('../database');
const meta = require('../meta');
const pubsub = require('../pubsub');
@@ -74,12 +74,10 @@ module.exports = function (Plugins) {
};
Plugins.checkWhitelist = async function (id, version) {
const body = await request({
method: 'GET',
url: `https://packages.nodebb.org/api/v1/plugins/${encodeURIComponent(id)}`,
json: true,
});
const { response, body } = await request.get(`https://packages.nodebb.org/api/v1/plugins/${encodeURIComponent(id)}`);
if (!response.ok) {
throw new Error(`[[error:cant-connect-to-nbbpm]]`);
}
if (body && body.code === 'ok' && (version === 'latest' || body.payload.valid.includes(version))) {
return;
}
@@ -88,11 +86,10 @@ module.exports = function (Plugins) {
};
Plugins.suggest = async function (pluginId, nbbVersion) {
const body = await request({
method: 'GET',
url: `https://packages.nodebb.org/api/v1/suggest?package=${encodeURIComponent(pluginId)}&version=${encodeURIComponent(nbbVersion)}`,
json: true,
});
const { response, body } = await request.get(`https://packages.nodebb.org/api/v1/suggest?package=${encodeURIComponent(pluginId)}&version=${encodeURIComponent(nbbVersion)}`);
if (!response.ok) {
throw new Error(`[[error:cant-connect-to-nbbpm]]`);
}
return body;
};

View File

@@ -1,48 +1,45 @@
'use strict';
const nconf = require('nconf');
const request = require('request');
const winston = require('winston');
const crypto = require('crypto');
const cronJob = require('cron').CronJob;
const request = require('../request');
const pkg = require('../../package.json');
const meta = require('../meta');
module.exports = function (Plugins) {
Plugins.startJobs = function () {
new cronJob('0 0 0 * * *', (() => {
Plugins.submitUsageData();
new cronJob('0 0 0 * * *', (async () => {
await Plugins.submitUsageData();
}), null, true);
};
Plugins.submitUsageData = function (callback) {
callback = callback || function () {};
Plugins.submitUsageData = async function () {
if (!meta.config.submitPluginUsage || !Plugins.loadedPlugins.length || global.env !== 'production') {
return callback();
return;
}
const hash = crypto.createHash('sha256');
hash.update(nconf.get('url'));
request.post(`${nconf.get('registry') || 'https://packages.nodebb.org'}/api/v1/plugin/usage`, {
form: {
const url = `${nconf.get('registry') || 'https://packages.nodebb.org'}/api/v1/plugin/usage`;
try {
const { response, body } = await request.post(url, {
body: {
id: hash.digest('hex'),
version: pkg.version,
plugins: Plugins.loadedPlugins,
},
timeout: 5000,
}, (err, res, body) => {
if (err) {
winston.error(err.stack);
return callback(err);
}
if (res.statusCode !== 200) {
winston.error(`[plugins.submitUsageData] received ${res.statusCode} ${body}`);
callback(new Error(`[[error:nbbpm-${res.statusCode}]]`));
} else {
callback();
}
});
if (!response.ok) {
winston.error(`[plugins.submitUsageData] received ${response.status} ${body}`);
}
} catch (err) {
winston.error(err.stack);
}
};
};

79
src/request.js Normal file
View File

@@ -0,0 +1,79 @@
'use strict';
const { CookieJar } = require('tough-cookie');
const fetchCookie = require('fetch-cookie');
exports.jar = function () {
return new CookieJar();
};
async function call(url, method, { body, timeout, jar, ...config } = {}) {
let fetchImpl = fetch;
if (jar) {
fetchImpl = fetchCookie(fetch, jar);
}
const opts = {
...config,
method,
headers: {
'content-type': 'application/json',
...config.headers,
},
};
if (timeout > 0) {
opts.signal = AbortSignal.timeout(timeout);
}
if (body && ['POST', 'PUT', 'PATCH', 'DEL', 'DELETE'].includes(method)) {
if (opts.headers['content-type'] && opts.headers['content-type'].startsWith('application/json')) {
opts.body = JSON.stringify(body);
} else {
opts.body = body;
}
}
const response = await fetchImpl(url, opts);
const { headers } = response;
const contentType = headers.get('content-type');
const isJSON = contentType && contentType.indexOf('application/json') !== -1;
let respBody = await response.text();
if (isJSON && respBody) {
try {
respBody = JSON.parse(respBody);
} catch (err) {
throw new Error('invalid json in response body', url);
}
}
return {
body: respBody,
response: {
ok: response.ok,
status: response.status,
statusCode: response.status,
statusText: response.statusText,
headers: Object.fromEntries(response.headers.entries()),
},
};
}
/*
const { body, response } = await request.get('someurl?foo=1&baz=2')
*/
exports.get = async (url, config) => call(url, 'GET', config);
exports.head = async (url, config) => call(url, 'HEAD', config);
exports.del = async (url, config) => call(url, 'DELETE', config);
exports.delete = exports.del;
exports.options = async (url, config) => call(url, 'OPTIONS', config);
/*
const { body, response } = await request.post('someurl', { body: { foo: 1, baz: 2}})
*/
exports.post = async (url, config) => call(url, 'POST', config);
exports.put = async (url, config) => call(url, 'PUT', config);
exports.patch = async (url, config) => call(url, 'PATCH', config);

View File

@@ -100,8 +100,8 @@ SocketAdmin.getSearchDict = async function (socket) {
return await getAdminSearchDict(lang);
};
SocketAdmin.deleteAllSessions = function (socket, data, callback) {
user.auth.deleteAllSessions(callback);
SocketAdmin.deleteAllSessions = async function () {
await user.auth.deleteAllSessions();
};
SocketAdmin.reloadAllSessions = function (socket, data, callback) {

View File

@@ -5,13 +5,13 @@ const assert = require('assert');
const path = require('path');
const fs = require('fs');
const SwaggerParser = require('@apidevtools/swagger-parser');
const request = require('request-promise-native');
const nconf = require('nconf');
const jwt = require('jsonwebtoken');
const util = require('util');
const wait = util.promisify(setTimeout);
const request = require('../src/request');
const db = require('./mocks/databasemock');
const helpers = require('./helpers');
const meta = require('../src/meta');
@@ -314,12 +314,7 @@ describe('API', async () => {
({ jar } = await helpers.loginUser('admin', '123456'));
// Retrieve CSRF token using cookie, to test Write API
const config = await request({
url: `${nconf.get('url')}/api/config`,
json: true,
jar: jar,
});
csrfToken = config.csrf_token;
csrfToken = await helpers.getCsrfToken(jar);
setup = true;
}
@@ -409,7 +404,7 @@ describe('API', async () => {
paths.forEach((path) => {
const context = api.paths[path];
let schema;
let response;
let result;
let url;
let method;
const headers = {};
@@ -498,26 +493,16 @@ describe('API', async () => {
try {
if (type === 'json') {
response = await request(url, {
method: method,
const searchParams = new URLSearchParams(qs);
result = await request[method](`${url}?${searchParams}`, {
jar: !unauthenticatedRoutes.includes(path) ? jar : undefined,
json: true,
followRedirect: false, // all responses are significant (e.g. 302)
simple: false, // don't throw on non-200 (e.g. 302)
resolveWithFullResponse: true, // send full request back (to check statusCode)
maxRedirect: 0,
redirect: 'manual',
headers: headers,
qs: qs,
body: body,
});
} else if (type === 'form') {
response = await new Promise((resolve, reject) => {
helpers.uploadFile(url, pathLib.join(__dirname, './files/test.png'), {}, jar, csrfToken, (err, res) => {
if (err) {
return reject(err);
}
resolve(res);
});
});
result = await helpers.uploadFile(url, pathLib.join(__dirname, './files/test.png'), {}, jar, csrfToken);
}
} catch (e) {
assert(!e, `${method.toUpperCase()} ${path} errored with: ${e.message}`);
@@ -526,13 +511,18 @@ describe('API', async () => {
it('response status code should match one of the schema defined responses', () => {
// HACK: allow HTTP 418 I am a teapot, for now 👇
assert(context[method].responses.hasOwnProperty('418') || Object.keys(context[method].responses).includes(String(response.statusCode)), `${method.toUpperCase()} ${path} sent back unexpected HTTP status code: ${response.statusCode}`);
const { responses } = context[method];
assert(
responses.hasOwnProperty('418') ||
Object.keys(responses).includes(String(result.response.statusCode)),
`${method.toUpperCase()} ${path} sent back unexpected HTTP status code: ${result.response.statusCode}`
);
});
// Recursively iterate through schema properties, comparing type
it('response body should match schema definition', () => {
const http302 = context[method].responses['302'];
if (http302 && response.statusCode === 302) {
if (http302 && result.response.statusCode === 302) {
// Compare headers instead
const expectedHeaders = Object.keys(http302.headers).reduce((memo, name) => {
const value = http302.headers[name].schema.example;
@@ -541,13 +531,13 @@ describe('API', async () => {
}, {});
for (const header of Object.keys(expectedHeaders)) {
assert(response.headers[header.toLowerCase()]);
assert.strictEqual(response.headers[header.toLowerCase()], expectedHeaders[header]);
assert(result.response.headers[header.toLowerCase()]);
assert.strictEqual(result.response.headers[header.toLowerCase()], expectedHeaders[header]);
}
return;
}
if (response.statusCode === 400 && context[method].responses['400']) {
if (result.response.statusCode === 400 && context[method].responses['400']) {
// TODO: check 400 schema to response.body?
return;
}
@@ -557,12 +547,12 @@ describe('API', async () => {
return;
}
assert.strictEqual(response.statusCode, 200, `HTTP 200 expected (path: ${method} ${path}`);
assert.strictEqual(result.response.statusCode, 200, `HTTP 200 expected (path: ${method} ${path}`);
const hasJSON = http200.content && http200.content['application/json'];
if (hasJSON) {
schema = context[method].responses['200'].content['application/json'].schema;
compare(schema, response.body, method.toUpperCase(), path, 'root');
compare(schema, result.body, method.toUpperCase(), path, 'root');
}
// TODO someday: text/csv, binary file type checking?
@@ -576,12 +566,7 @@ describe('API', async () => {
mocks.delete['/users/{uid}/sessions/{uuid}'][1].example = Object.keys(sessionUUIDs).pop();
// Retrieve CSRF token using cookie, to test Write API
const config = await request({
url: `${nconf.get('url')}/api/config`,
json: true,
jar: jar,
});
csrfToken = config.csrf_token;
csrfToken = await helpers.getCsrfToken(jar);
}
});
});

View File

@@ -3,12 +3,9 @@
const assert = require('assert');
const url = require('url');
const async = require('async');
const nconf = require('nconf');
const request = require('request');
const requestAsync = require('request-promise-native');
const util = require('util');
const request = require('../src/request');
const db = require('./mocks/databasemock');
const user = require('../src/user');
const utils = require('../src/utils');
@@ -45,8 +42,8 @@ describe('authentication', () => {
it('should allow login with email for uid 1', async () => {
const oldValue = meta.config.allowLoginWith;
meta.config.allowLoginWith = 'username-email';
const { res } = await helpers.loginUser('regular@nodebb.org', 'regularpwd');
assert.strictEqual(res.statusCode, 200);
const { response } = await helpers.loginUser('regular@nodebb.org', 'regularpwd');
assert.strictEqual(response.statusCode, 200);
meta.config.allowLoginWith = oldValue;
});
@@ -54,70 +51,55 @@ describe('authentication', () => {
const oldValue = meta.config.allowLoginWith;
meta.config.allowLoginWith = 'username-email';
const uid = await user.create({ username: '2nduser', password: '2ndpassword', email: '2nduser@nodebb.org' });
const { res, body } = await helpers.loginUser('2nduser@nodebb.org', '2ndpassword');
assert.strictEqual(res.statusCode, 403);
const { response, body } = await helpers.loginUser('2nduser@nodebb.org', '2ndpassword');
assert.strictEqual(response.statusCode, 403);
assert.strictEqual(body, '[[error:invalid-login-credentials]]');
meta.config.allowLoginWith = oldValue;
});
it('should fail to create user if username is too short', (done) => {
helpers.registerUser({
it('should fail to create user if username is too short', async () => {
const { response, body } = await helpers.registerUser({
username: 'a',
password: '123456',
}, (err, jar, response, body) => {
assert.ifError(err);
});
assert.equal(response.statusCode, 400);
assert.equal(body, '[[error:username-too-short]]');
done();
});
});
it('should fail to create user if userslug is too short', (done) => {
helpers.registerUser({
it('should fail to create user if userslug is too short', async () => {
const { response, body } = await helpers.registerUser({
username: '----a-----',
password: '123456',
}, (err, jar, response, body) => {
assert.ifError(err);
});
assert.equal(response.statusCode, 400);
assert.equal(body, '[[error:username-too-short]]');
done();
});
});
it('should fail to create user if userslug is too short', (done) => {
helpers.registerUser({
it('should fail to create user if userslug is too short', async () => {
const { response, body } = await helpers.registerUser({
username: ' a',
password: '123456',
}, (err, jar, response, body) => {
assert.ifError(err);
});
assert.equal(response.statusCode, 400);
assert.equal(body, '[[error:username-too-short]]');
done();
});
});
it('should fail to create user if userslug is too short', (done) => {
helpers.registerUser({
it('should fail to create user if userslug is too short', async () => {
const { response, body } = await helpers.registerUser({
username: 'a ',
password: '123456',
}, (err, jar, response, body) => {
assert.ifError(err);
});
assert.equal(response.statusCode, 400);
assert.equal(body, '[[error:username-too-short]]');
done();
});
});
it('should register and login a user', (done) => {
request({
url: `${nconf.get('url')}/api/config`,
json: true,
jar: jar,
}, (err, response, body) => {
assert.ifError(err);
it('should register and login a user', async () => {
const jar = request.jar();
const csrf_token = await helpers.getCsrfToken(jar);
request.post(`${nconf.get('url')}/register`, {
form: {
const { body } = await request.post(`${nconf.get('url')}/register`, {
jar,
body: {
email: 'admin@nodebb.org',
username: 'admin',
password: 'adminpwd',
@@ -125,79 +107,56 @@ describe('authentication', () => {
userLang: 'it',
gdpr_consent: true,
},
json: true,
jar: jar,
headers: {
'x-csrf-token': body.csrf_token,
'x-csrf-token': csrf_token,
},
}, async (err, response, body) => {
});
const validationPending = await user.email.isValidationPending(body.uid, 'admin@nodebb.org');
assert.strictEqual(validationPending, true);
assert.ifError(err);
assert(body);
assert(body.hasOwnProperty('uid') && body.uid > 0);
const newUid = body.uid;
request({
url: `${nconf.get('url')}/api/self`,
json: true,
jar: jar,
}, (err, response, body) => {
assert.ifError(err);
assert(body);
assert.equal(body.username, 'admin');
assert.equal(body.uid, newUid);
user.getSettings(body.uid, (err, settings) => {
assert.ifError(err);
const { body: self } = await request.get(`${nconf.get('url')}/api/self`, {
jar,
});
assert(self);
assert.equal(self.username, 'admin');
assert.equal(self.uid, newUid);
const settings = await user.getSettings(body.uid);
assert.equal(settings.userLang, 'it');
done();
});
});
});
});
});
it('should logout a user', (done) => {
helpers.logoutUser(jar, (err) => {
assert.ifError(err);
request({
url: `${nconf.get('url')}/api/me`,
json: true,
it('should logout a user', async () => {
await helpers.logoutUser(jar);
const { response, body } = await request.get(`${nconf.get('url')}/api/me`, {
jar: jar,
}, (err, res, body) => {
assert.ifError(err);
assert.equal(res.statusCode, 401);
});
assert.equal(response.statusCode, 401);
assert.strictEqual(body.status.code, 'not-authorised');
done();
});
});
});
it('should regenerate the session identifier on successful login', async () => {
const matchRegexp = /express\.sid=s%3A(.+?);/;
const { hostname, path } = url.parse(nconf.get('url'));
const sid = String(jar._jar.store.idx[hostname][path]['express.sid']).match(matchRegexp)[1];
const sid = String(jar.store.idx[hostname][path]['express.sid']).match(matchRegexp)[1];
await helpers.logoutUser(jar);
const newJar = (await helpers.loginUser('regular', 'regularpwd')).jar;
const newSid = String(newJar._jar.store.idx[hostname][path]['express.sid']).match(matchRegexp)[1];
const newSid = String(newJar.store.idx[hostname][path]['express.sid']).match(matchRegexp)[1];
assert.notStrictEqual(newSid, sid);
});
it('should revoke all sessions', (done) => {
it('should revoke all sessions', async () => {
const socketAdmin = require('../src/socket.io/admin');
db.sortedSetCard(`uid:${regularUid}:sessions`, (err, count) => {
assert.ifError(err);
assert(count);
socketAdmin.deleteAllSessions({ uid: 1 }, {}, (err) => {
assert.ifError(err);
db.sortedSetCard(`uid:${regularUid}:sessions`, (err, count) => {
assert.ifError(err);
assert(!count);
done();
});
});
});
let sessionCount = await db.sortedSetCard(`uid:${regularUid}:sessions`);
assert(sessionCount);
await socketAdmin.deleteAllSessions({ uid: 1 }, {});
sessionCount = await db.sortedSetCard(`uid:${regularUid}:sessions`);
assert(!sessionCount);
});
describe('login', () => {
@@ -205,11 +164,12 @@ describe('authentication', () => {
let password;
let uid;
function getCookieExpiry(res) {
assert(res.headers['set-cookie']);
assert.strictEqual(res.headers['set-cookie'][0].includes('Expires'), true);
function getCookieExpiry(response) {
const { headers } = response;
assert(headers['set-cookie']);
assert.strictEqual(headers['set-cookie'].includes('Expires'), true);
const values = res.headers['set-cookie'][0].split(';');
const values = headers['set-cookie'].split(';');
return values.reduce((memo, cur) => {
if (!memo) {
const [name, value] = cur.split('=');
@@ -230,9 +190,7 @@ describe('authentication', () => {
it('should login a user', async () => {
const { jar, body: loginBody } = await helpers.loginUser(username, password);
assert(loginBody);
const body = await requestAsync({
url: `${nconf.get('url')}/api/self`,
json: true,
const { body } = await request.get(`${nconf.get('url')}/api/self`, {
jar,
});
assert(body);
@@ -243,11 +201,11 @@ describe('authentication', () => {
});
it('should set a cookie that only lasts for the life of the browser session', async () => {
const { res } = await helpers.loginUser(username, password);
const { response } = await helpers.loginUser(username, password);
assert(res.headers);
assert(res.headers['set-cookie']);
assert.strictEqual(res.headers['set-cookie'][0].includes('Expires'), false);
assert(response.headers);
assert(response.headers['set-cookie']);
assert.strictEqual(response.headers['set-cookie'].includes('Expires'), false);
});
it('should set a different expiry if sessionDuration is set', async () => {
@@ -255,9 +213,9 @@ describe('authentication', () => {
const days = 1;
meta.config.sessionDuration = days * 24 * 60 * 60;
const { res } = await helpers.loginUser(username, password);
const { response } = await helpers.loginUser(username, password);
const expiry = getCookieExpiry(res);
const expiry = getCookieExpiry(response);
const expected = new Date();
expected.setUTCDate(expected.getUTCDate() + days);
@@ -267,9 +225,9 @@ describe('authentication', () => {
});
it('should set a cookie that lasts for x days where x is loginDays setting, if asked to remember', async () => {
const { res } = await helpers.loginUser(username, password, { remember: 'on' });
const { response } = await helpers.loginUser(username, password, { remember: 'on' });
const expiry = getCookieExpiry(res);
const expiry = getCookieExpiry(response);
const expected = new Date();
expected.setUTCDate(expected.getUTCDate() + meta.config.loginDays);
@@ -280,9 +238,9 @@ describe('authentication', () => {
const _loginDays = meta.config.loginDays;
meta.config.loginDays = 5;
const { res } = await helpers.loginUser(username, password, { remember: 'on' });
const { response } = await helpers.loginUser(username, password, { remember: 'on' });
const expiry = getCookieExpiry(res);
const expiry = getCookieExpiry(response);
const expected = new Date();
expected.setUTCDate(expected.getUTCDate() + meta.config.loginDays);
@@ -295,9 +253,9 @@ describe('authentication', () => {
const _loginSeconds = meta.config.loginSeconds;
meta.config.loginSeconds = 60;
const { res } = await helpers.loginUser(username, password, { remember: 'on' });
const { response } = await helpers.loginUser(username, password, { remember: 'on' });
const expiry = getCookieExpiry(res);
const expiry = getCookieExpiry(response);
const expected = new Date();
expected.setUTCSeconds(expected.getUTCSeconds() + meta.config.loginSeconds);
@@ -308,158 +266,128 @@ describe('authentication', () => {
});
});
it('should fail to login if ip address is invalid', (done) => {
it('should fail to login if ip address is invalid', async () => {
const jar = request.jar();
request({
url: `${nconf.get('url')}/api/config`,
json: true,
jar: jar,
}, (err, response, body) => {
if (err) {
return done(err);
}
const csrf_token = await helpers.getCsrfToken(jar);
request.post(`${nconf.get('url')}/login`, {
form: {
const { response } = await request.post(`${nconf.get('url')}/login`, {
body: {
username: 'regular',
password: 'regularpwd',
},
json: true,
jar: jar,
headers: {
'x-csrf-token': body.csrf_token,
'x-csrf-token': csrf_token,
'x-forwarded-for': '<script>alert("xss")</script>',
},
}, (err, response, body) => {
assert.ifError(err);
assert.equal(response.statusCode, 500);
done();
});
});
assert.equal(response.status, 500);
});
it('should fail to login if user does not exist', async () => {
const { res, body } = await helpers.loginUser('doesnotexist', 'nopassword');
assert.equal(res.statusCode, 403);
const { response, body } = await helpers.loginUser('doesnotexist', 'nopassword');
assert.equal(response.statusCode, 403);
assert.equal(body, '[[error:invalid-login-credentials]]');
});
it('should fail to login if username is empty', async () => {
const { res, body } = await helpers.loginUser('', 'some password');
assert.equal(res.statusCode, 403);
const { response, body } = await helpers.loginUser('', 'some password');
assert.equal(response.statusCode, 403);
assert.equal(body, '[[error:invalid-username-or-password]]');
});
it('should fail to login if password is empty', async () => {
const { res, body } = await helpers.loginUser('someuser', '');
assert.equal(res.statusCode, 403);
const { response, body } = await helpers.loginUser('someuser', '');
assert.equal(response.statusCode, 403);
assert.equal(body, '[[error:invalid-username-or-password]]');
});
it('should fail to login if username and password are empty', async () => {
const { res, body } = await helpers.loginUser('', '');
assert.equal(res.statusCode, 403);
const { response, body } = await helpers.loginUser('', '');
assert.equal(response.statusCode, 403);
assert.equal(body, '[[error:invalid-username-or-password]]');
});
it('should fail to login if user does not have password field in db', async () => {
await user.create({ username: 'hasnopassword', email: 'no@pass.org' });
const { res, body } = await helpers.loginUser('hasnopassword', 'doesntmatter');
assert.equal(res.statusCode, 403);
const { response, body } = await helpers.loginUser('hasnopassword', 'doesntmatter');
assert.equal(response.statusCode, 403);
assert.equal(body, '[[error:invalid-login-credentials]]');
});
it('should fail to login if password is longer than 4096', async () => {
let longPassword;
let longPassword = '';
for (let i = 0; i < 5000; i++) {
longPassword += 'a';
}
const { res, body } = await helpers.loginUser('someuser', longPassword);
assert.equal(res.statusCode, 403);
const { response, body } = await helpers.loginUser('someuser', longPassword);
assert.equal(response.statusCode, 403);
assert.equal(body, '[[error:password-too-long]]');
});
it('should fail to login if local login is disabled', async () => {
await privileges.global.rescind(['groups:local:login'], 'registered-users');
const { res, body } = await helpers.loginUser('regular', 'regularpwd');
assert.equal(res.statusCode, 403);
const { response, body } = await helpers.loginUser('regular', 'regularpwd');
assert.equal(response.statusCode, 403);
assert.equal(body, '[[error:local-login-disabled]]');
await privileges.global.give(['groups:local:login'], 'registered-users');
});
it('should fail to register if registraton is disabled', (done) => {
it('should fail to register if registraton is disabled', async () => {
meta.config.registrationType = 'disabled';
helpers.registerUser({
const { response, body } = await helpers.registerUser({
username: 'someuser',
password: 'somepassword',
}, (err, jar, response, body) => {
assert.ifError(err);
});
assert.equal(response.statusCode, 403);
assert.equal(body, 'Forbidden');
done();
});
});
it('should return error if invitation is not valid', (done) => {
it('should return error if invitation is not valid', async () => {
meta.config.registrationType = 'invite-only';
helpers.registerUser({
const { response, body } = await helpers.registerUser({
username: 'someuser',
password: 'somepassword',
}, (err, jar, response, body) => {
});
meta.config.registrationType = 'normal';
assert.ifError(err);
assert.equal(response.statusCode, 400);
assert.equal(body, '[[register:invite.error-invite-only]]');
done();
});
});
it('should fail to register if username is falsy or too short', (done) => {
helpers.registerUser({
username: '',
password: 'somepassword',
}, (err, jar, response, body) => {
assert.ifError(err);
it('should fail to register if username is falsy or too short', async () => {
const userData = [
{ username: '', password: 'somepassword' },
{ username: 'a', password: 'somepassword' },
];
for (const user of userData) {
// eslint-disable-next-line no-await-in-loop
const { response, body } = await helpers.registerUser(user);
assert.equal(response.statusCode, 400);
assert.equal(body, '[[error:username-too-short]]');
helpers.registerUser({
username: 'a',
password: 'somepassword',
}, (err, jar, response, body) => {
assert.ifError(err);
assert.equal(response.statusCode, 400);
assert.equal(body, '[[error:username-too-short]]');
done();
});
});
}
});
it('should fail to register if username is too long', (done) => {
helpers.registerUser({
it('should fail to register if username is too long', async () => {
const { response, body } = await helpers.registerUser({
username: 'thisisareallylongusername',
password: '123456',
}, (err, jar, response, body) => {
assert.ifError(err);
assert.equal(response.statusCode, 400);
assert.equal(body, '[[error:username-too-long]]');
done();
});
});
it('should queue user if ip is used before', (done) => {
assert.equal(response.statusCode, 400);
assert.equal(body, '[[error:username-too-long]]');
});
it('should queue user if ip is used before', async () => {
meta.config.registrationApprovalType = 'admin-approval-ip';
helpers.registerUser({
const { response, body } = await helpers.registerUser({
email: 'another@user.com',
username: 'anotheruser',
password: 'anotherpwd',
gdpr_consent: 1,
}, (err, jar, response, body) => {
});
meta.config.registrationApprovalType = 'normal';
assert.ifError(err);
assert.equal(response.statusCode, 200);
assert.equal(body.message, '[[register:registration-added-to-queue]]');
done();
});
});
@@ -468,41 +396,32 @@ describe('authentication', () => {
const uid = await user.create({ username: 'ginger', password: '123456', email });
await user.setUserField(uid, 'email', email);
await user.email.confirmByUid(uid);
const { res } = await helpers.loginUser('ginger@nodebb.org', '123456');
assert.equal(res.statusCode, 200);
const { response } = await helpers.loginUser('ginger@nodebb.org', '123456');
assert.equal(response.statusCode, 200);
});
it('should fail to login if login type is username and an email is sent', async () => {
meta.config.allowLoginWith = 'username';
const { res, body } = await helpers.loginUser('ginger@nodebb.org', '123456');
const { response, body } = await helpers.loginUser('ginger@nodebb.org', '123456');
meta.config.allowLoginWith = 'username-email';
assert.equal(res.statusCode, 400);
assert.equal(response.statusCode, 400);
assert.equal(body, '[[error:wrong-login-type-username]]');
});
it('should send 200 if not logged in', (done) => {
it('should send 200 if not logged in', async () => {
const jar = request.jar();
request({
url: `${nconf.get('url')}/api/config`,
json: true,
jar: jar,
}, (err, response, body) => {
assert.ifError(err);
const csrf_token = await helpers.getCsrfToken(jar);
request.post(`${nconf.get('url')}/logout`, {
form: {},
json: true,
const { response, body } = await request.post(`${nconf.get('url')}/logout`, {
data: {},
jar: jar,
headers: {
'x-csrf-token': body.csrf_token,
'x-csrf-token': csrf_token,
},
}, (err, res, body) => {
assert.ifError(err);
assert.equal(res.statusCode, 200);
});
assert.equal(response.statusCode, 200);
assert.equal(body, 'not-logged-in');
done();
});
});
});
describe('banned user authentication', () => {
@@ -518,7 +437,7 @@ describe('authentication', () => {
it('should prevent banned user from logging in', async () => {
await user.bans.ban(bannedUser.uid, 0, 'spammer');
const { res: res1, body: body1 } = await helpers.loginUser(bannedUser.username, bannedUser.pw);
const { response: res1, body: body1 } = await helpers.loginUser(bannedUser.username, bannedUser.pw);
assert.equal(res1.statusCode, 403);
delete body1.timestamp;
assert.deepStrictEqual(body1, {
@@ -532,7 +451,7 @@ describe('authentication', () => {
await user.bans.unban(bannedUser.uid);
const expiry = Date.now() + 10000;
await user.bans.ban(bannedUser.uid, expiry, '');
const { res: res2, body: body2 } = await helpers.loginUser(bannedUser.username, bannedUser.pw);
const { response: res2, body: body2 } = await helpers.loginUser(bannedUser.username, bannedUser.pw);
assert.equal(res2.statusCode, 403);
assert(body2.banned_until);
assert(body2.reason, '[[user:info.banned-no-reason]]');
@@ -540,15 +459,15 @@ describe('authentication', () => {
it('should allow banned user to log in if the "banned-users" group has "local-login" privilege', async () => {
await privileges.global.give(['groups:local:login'], 'banned-users');
const { res } = await helpers.loginUser(bannedUser.username, bannedUser.pw);
assert.strictEqual(res.statusCode, 200);
const { response } = await helpers.loginUser(bannedUser.username, bannedUser.pw);
assert.strictEqual(response.statusCode, 200);
});
it('should allow banned user to log in if the user herself has "local-login" privilege', async () => {
await privileges.global.rescind(['groups:local:login'], 'banned-users');
await privileges.categories.give(['local:login'], 0, bannedUser.uid);
const { res } = await helpers.loginUser(bannedUser.username, bannedUser.pw);
assert.strictEqual(res.statusCode, 200);
const { response } = await helpers.loginUser(bannedUser.username, bannedUser.pw);
assert.strictEqual(response.statusCode, 200);
});
});
@@ -561,10 +480,10 @@ describe('authentication', () => {
let data = await helpers.loginUser('lockme', 'abcdef');
meta.config.loginAttempts = 5;
assert.equal(data.res.statusCode, 403);
assert.equal(data.response.statusCode, 403);
assert.equal(data.body, '[[error:account-locked]]');
data = await helpers.loginUser('lockme', 'abcdef');
assert.equal(data.res.statusCode, 403);
assert.equal(data.response.statusCode, 403);
assert.equal(data.body, '[[error:account-locked]]');
const locked = await db.exists(`lockout:${uid}`);
assert(locked);
@@ -594,57 +513,46 @@ describe('authentication', () => {
});
it('should fail with invalid token', async () => {
const { res, body } = await helpers.request('get', `/api/self`, {
form: {
_uid: newUid,
},
json: true,
const { response, body } = await helpers.request('get', `/api/self?_uid${newUid}`, {
jar: jar,
headers: {
Authorization: `Bearer sdfhaskfdja-jahfdaksdf`,
},
});
assert.strictEqual(res.statusCode, 401);
assert.strictEqual(response.statusCode, 401);
assert.strictEqual(body, 'not-authorized');
});
it('should use a token tied to an uid', async () => {
const { res, body } = await helpers.request('get', `/api/self`, {
json: true,
const { response, body } = await helpers.request('get', `/api/self`, {
headers: {
Authorization: `Bearer ${userToken}`,
},
});
assert.strictEqual(res.statusCode, 200);
assert.strictEqual(response.statusCode, 200);
assert.strictEqual(body.username, 'apiUserTarget');
});
it('should fail if _uid is not passed in with master token', async () => {
const { res, body } = await helpers.request('get', `/api/self`, {
form: {},
json: true,
const { response, body } = await helpers.request('get', `/api/self`, {
headers: {
Authorization: `Bearer ${masterToken}`,
},
});
assert.strictEqual(res.statusCode, 500);
assert.strictEqual(response.statusCode, 500);
assert.strictEqual(body.error, '[[error:api.master-token-no-uid]]');
});
it('should use master api token and _uid', async () => {
const { res, body } = await helpers.request('get', `/api/self`, {
form: {
_uid: newUid,
},
json: true,
const { response, body } = await helpers.request('get', `/api/self?_uid=${newUid}`, {
headers: {
Authorization: `Bearer ${masterToken}`,
},
});
assert.strictEqual(res.statusCode, 200);
assert.strictEqual(response.statusCode, 200);
assert.strictEqual(body.username, 'apiUserTarget');
});
});

View File

@@ -2,8 +2,8 @@
const assert = require('assert');
const nconf = require('nconf');
const request = require('request');
const request = require('../src/request');
const db = require('./mocks/databasemock');
const Categories = require('../src/categories');
const Topics = require('../src/topics');
@@ -76,14 +76,11 @@ describe('Categories', () => {
});
});
it('should load a category route', (done) => {
request(`${nconf.get('url')}/api/category/${categoryObj.cid}/test-category`, { json: true }, (err, response, body) => {
assert.ifError(err);
it('should load a category route', async () => {
const { response, body } = await request.get(`${nconf.get('url')}/api/category/${categoryObj.cid}/test-category`);
assert.equal(response.statusCode, 200);
assert.equal(body.name, 'Test Category &amp; NodeBB');
assert(body);
done();
});
});
describe('Categories.getRecentTopicReplies', () => {

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,14 +1,12 @@
'use strict';
const assert = require('assert');
const async = require('async');
const request = require('request');
const nconf = require('nconf');
const db = require('./mocks/databasemock');
const request = require('../src/request');
const topics = require('../src/topics');
const categories = require('../src/categories');
const groups = require('../src/groups');
const user = require('../src/user');
const meta = require('../src/meta');
const privileges = require('../src/privileges');
@@ -16,38 +14,27 @@ const helpers = require('./helpers');
describe('feeds', () => {
let tid;
let pid;
let fooUid;
let cid;
before((done) => {
before(async () => {
meta.config['feeds:disableRSS'] = 1;
async.series({
category: function (next) {
categories.create({
const category = await categories.create({
name: 'Test Category',
description: 'Test category created by testing script',
}, next);
},
user: function (next) {
user.create({ username: 'foo', password: 'barbar', email: 'foo@test.com' }, next);
},
}, (err, results) => {
if (err) {
return done(err);
}
cid = results.category.cid;
fooUid = results.user;
});
cid = category.cid;
fooUid = await user.create({ username: 'foo', password: 'barbar', email: 'foo@test.com' });
topics.post({ uid: results.user, title: 'test topic title', content: 'test topic content', cid: results.category.cid }, (err, result) => {
const result = await topics.post({
cid: cid,
uid: fooUid,
title: 'test topic title',
content: 'test topic content',
});
tid = result.topicData.tid;
pid = result.postData.pid;
done(err);
});
});
});
it('should 404', (done) => {
it('should 404', async () => {
const feedUrls = [
`${nconf.get('url')}/topic/${tid}.rss`,
`${nconf.get('url')}/category/${cid}.rss`,
@@ -61,67 +48,45 @@ describe('feeds', () => {
`${nconf.get('url')}/user/foo/topics.rss`,
`${nconf.get('url')}/tags/nodebb.rss`,
];
async.eachSeries(feedUrls, (url, next) => {
request(url, (err, res) => {
assert.ifError(err);
assert.equal(res.statusCode, 404);
next();
});
}, (err) => {
assert.ifError(err);
for (const url of feedUrls) {
// eslint-disable-next-line no-await-in-loop
const { response } = await request.get(url);
assert.equal(response.statusCode, 404);
}
meta.config['feeds:disableRSS'] = 0;
done();
});
});
it('should 404 if topic does not exist', (done) => {
request(`${nconf.get('url')}/topic/${1000}.rss`, (err, res) => {
assert.ifError(err);
assert.equal(res.statusCode, 404);
done();
});
it('should 404 if topic does not exist', async () => {
const { response } = await request.get(`${nconf.get('url')}/topic/${1000}.rss`);
assert.equal(response.statusCode, 404);
});
it('should 404 if category id is not a number', (done) => {
request(`${nconf.get('url')}/category/invalid.rss`, (err, res) => {
assert.ifError(err);
assert.equal(res.statusCode, 404);
done();
});
it('should 404 if category id is not a number', async () => {
const { response } = await request.get(`${nconf.get('url')}/category/invalid.rss`);
assert.equal(response.statusCode, 404);
});
it('should redirect if we do not have read privilege', (done) => {
privileges.categories.rescind(['groups:topics:read'], cid, 'guests', (err) => {
assert.ifError(err);
request(`${nconf.get('url')}/topic/${tid}.rss`, (err, res, body) => {
assert.ifError(err);
assert.equal(res.statusCode, 200);
it('should redirect if we do not have read privilege', async () => {
await privileges.categories.rescind(['groups:topics:read'], cid, 'guests');
const { response, body } = await request.get(`${nconf.get('url')}/topic/${tid}.rss`);
assert.equal(response.statusCode, 200);
assert(body);
assert(body.includes('Login to your account'));
privileges.categories.give(['groups:topics:read'], cid, 'guests', done);
});
});
await privileges.categories.give(['groups:topics:read'], cid, 'guests');
});
it('should 404 if user is not found', (done) => {
request(`${nconf.get('url')}/user/doesnotexist/topics.rss`, (err, res) => {
assert.ifError(err);
assert.equal(res.statusCode, 404);
done();
});
it('should 404 if user is not found', async () => {
const { response } = await request.get(`${nconf.get('url')}/user/doesnotexist/topics.rss`);
assert.equal(response.statusCode, 404);
});
it('should redirect if we do not have read privilege', (done) => {
privileges.categories.rescind(['groups:read'], cid, 'guests', (err) => {
assert.ifError(err);
request(`${nconf.get('url')}/category/${cid}.rss`, (err, res, body) => {
assert.ifError(err);
assert.equal(res.statusCode, 200);
it('should redirect if we do not have read privilege', async () => {
await privileges.categories.rescind(['groups:read'], cid, 'guests');
const { response, body } = await request.get(`${nconf.get('url')}/category/${cid}.rss`);
assert.equal(response.statusCode, 200);
assert(body);
assert(body.includes('Login to your account'));
privileges.categories.give(['groups:read'], cid, 'guests', done);
});
});
await privileges.categories.give(['groups:read'], cid, 'guests');
});
describe('private feeds and tokens', () => {
@@ -131,69 +96,45 @@ describe('feeds', () => {
({ jar } = await helpers.loginUser('foo', 'barbar'));
});
it('should load feed if its not private', (done) => {
request(`${nconf.get('url')}/category/${cid}.rss`, { }, (err, res, body) => {
assert.ifError(err);
assert.equal(res.statusCode, 200);
it('should load feed if its not private', async () => {
const { response, body } = await request.get(`${nconf.get('url')}/category/${cid}.rss`);
assert.equal(response.statusCode, 200);
assert(body);
done();
});
});
it('should not allow access if uid or token is missing', (done) => {
privileges.categories.rescind(['groups:read'], cid, 'guests', (err) => {
assert.ifError(err);
async.parallel({
test1: function (next) {
request(`${nconf.get('url')}/category/${cid}.rss?uid=${fooUid}`, { }, next);
},
test2: function (next) {
request(`${nconf.get('url')}/category/${cid}.rss?token=sometoken`, { }, next);
},
}, (err, results) => {
assert.ifError(err);
assert.equal(results.test1[0].statusCode, 200);
assert.equal(results.test2[0].statusCode, 200);
assert(results.test1[0].body.includes('Login to your account'));
assert(results.test2[0].body.includes('Login to your account'));
done();
});
});
it('should not allow access if uid or token is missing', async () => {
await privileges.categories.rescind(['groups:read'], cid, 'guests');
const [test1, test2] = await Promise.all([
request.get(`${nconf.get('url')}/category/${cid}.rss?uid=${fooUid}`, { }),
request.get(`${nconf.get('url')}/category/${cid}.rss?token=sometoken`, { }),
]);
assert.equal(test1.response.statusCode, 200);
assert.equal(test2.response.statusCode, 200);
assert(test1.body.includes('Login to your account'));
assert(test2.body.includes('Login to your account'));
});
it('should not allow access if token is wrong', (done) => {
request(`${nconf.get('url')}/category/${cid}.rss?uid=${fooUid}&token=sometoken`, { }, (err, res, body) => {
assert.ifError(err);
assert.equal(res.statusCode, 200);
it('should not allow access if token is wrong', async () => {
const { response, body } = await request.get(`${nconf.get('url')}/category/${cid}.rss?uid=${fooUid}&token=sometoken`);
assert.equal(response.statusCode, 200);
assert(body.includes('Login to your account'));
done();
});
});
it('should allow access if token is correct', (done) => {
request(`${nconf.get('url')}/api/category/${cid}`, { jar: jar, json: true }, (err, res, body) => {
assert.ifError(err);
rssToken = body.rssFeedUrl.split('token')[1].slice(1);
request(`${nconf.get('url')}/category/${cid}.rss?uid=${fooUid}&token=${rssToken}`, { }, (err, res, body) => {
assert.ifError(err);
assert.equal(res.statusCode, 200);
assert(body.startsWith('<?xml version="1.0"'));
done();
});
});
it('should allow access if token is correct', async () => {
const { body: body1 } = await request.get(`${nconf.get('url')}/api/category/${cid}`, { jar });
rssToken = body1.rssFeedUrl.split('token')[1].slice(1);
const { response, body: body2 } = await request.get(`${nconf.get('url')}/category/${cid}.rss?uid=${fooUid}&token=${rssToken}`);
assert.equal(response.statusCode, 200);
assert(body2.startsWith('<?xml version="1.0"'));
});
it('should not allow access if token is correct but has no privilege', (done) => {
privileges.categories.rescind(['groups:read'], cid, 'registered-users', (err) => {
assert.ifError(err);
request(`${nconf.get('url')}/category/${cid}.rss?uid=${fooUid}&token=${rssToken}`, { }, (err, res, body) => {
assert.ifError(err);
assert.equal(res.statusCode, 200);
it('should not allow access if token is correct but has no privilege', async () => {
await privileges.categories.rescind(['groups:read'], cid, 'registered-users');
const { response, body } = await request.get(`${nconf.get('url')}/category/${cid}.rss?uid=${fooUid}&token=${rssToken}`);
assert.equal(response.statusCode, 200);
assert(body.includes('Login to your account'));
done();
});
});
});
});
});

View File

@@ -2,15 +2,13 @@
const assert = require('assert');
const nconf = require('nconf');
const async = require('async');
const request = require('request-promise-native');
const util = require('util');
const sleep = util.promisify(setTimeout);
const db = require('./mocks/databasemock');
const helpers = require('./helpers');
const request = require('../src/request');
const Flags = require('../src/flags');
const Categories = require('../src/categories');
const Topics = require('../src/topics');
@@ -243,13 +241,11 @@ describe('Flags', () => {
it('should show user history for admins', async () => {
await Groups.join('administrators', moderatorUid);
const flagData = await request({
uri: `${nconf.get('url')}/api/flags/1`,
const { body: flagData } = await request.get(`${nconf.get('url')}/api/flags/1`, {
jar,
headers: {
'x-csrf-token': csrfToken,
},
json: true,
});
assert(flagData.history);
@@ -260,13 +256,11 @@ describe('Flags', () => {
it('should show user history for global moderators', async () => {
await Groups.join('Global Moderators', moderatorUid);
const flagData = await request({
uri: `${nconf.get('url')}/api/flags/1`,
const { body: flagData } = await request.get(`${nconf.get('url')}/api/flags/1`, {
jar,
headers: {
'x-csrf-token': csrfToken,
},
json: true,
});
assert(flagData.history);
@@ -895,9 +889,7 @@ describe('Flags', () => {
describe('.create()', () => {
it('should create a flag with no errors', async () => {
await request({
method: 'post',
uri: `${nconf.get('url')}/api/v3/flags`,
await request.post(`${nconf.get('url')}/api/v3/flags`, {
jar,
headers: {
'x-csrf-token': csrfToken,
@@ -907,7 +899,6 @@ describe('Flags', () => {
id: pid,
reason: 'foobar',
},
json: true,
});
const exists = await Flags.exists('post', pid, 2);
@@ -921,9 +912,7 @@ describe('Flags', () => {
content: 'This is flaggable content',
});
const { response } = await request({
method: 'post',
uri: `${nconf.get('url')}/api/v3/flags`,
const { body } = await request.post(`${nconf.get('url')}/api/v3/flags`, {
jar,
headers: {
'x-csrf-token': csrfToken,
@@ -933,10 +922,9 @@ describe('Flags', () => {
id: postData.pid,
reason: '"<script>alert(\'ok\');</script>',
},
json: true,
});
const flagData = await Flags.get(response.flagId);
const flagData = await Flags.get(body.response.flagId);
assert.strictEqual(flagData.reports[0].value, '&quot;&lt;script&gt;alert(&#x27;ok&#x27;);&lt;&#x2F;script&gt;');
});
@@ -953,15 +941,9 @@ describe('Flags', () => {
});
const login = await helpers.loginUser('unprivileged', 'abcdef');
const jar3 = login.jar;
const config = await request({
url: `${nconf.get('url')}/api/config`,
json: true,
jar: jar3,
});
const csrfToken = config.csrf_token;
const { statusCode, body } = await request({
method: 'post',
uri: `${nconf.get('url')}/api/v3/flags`,
const csrfToken = await helpers.getCsrfToken(jar3);
const { response, body } = await request.post(`${nconf.get('url')}/api/v3/flags`, {
jar: jar3,
headers: {
'x-csrf-token': csrfToken,
@@ -971,11 +953,8 @@ describe('Flags', () => {
id: result.postData.pid,
reason: 'foobar',
},
json: true,
simple: false,
resolveWithFullResponse: true,
});
assert.strictEqual(statusCode, 403);
assert.strictEqual(response.statusCode, 403);
// Handle dev mode test
delete body.stack;
@@ -992,9 +971,7 @@ describe('Flags', () => {
describe('.update()', () => {
it('should update a flag\'s properties', async () => {
const { response } = await request({
method: 'put',
uri: `${nconf.get('url')}/api/v3/flags/4`,
const { body } = await request.put(`${nconf.get('url')}/api/v3/flags/4`, {
jar,
headers: {
'x-csrf-token': csrfToken,
@@ -1002,10 +979,9 @@ describe('Flags', () => {
body: {
state: 'wip',
},
json: true,
});
const { history } = response;
const { history } = body.response;
assert(Array.isArray(history));
assert(history[0].fields.hasOwnProperty('state'));
assert.strictEqual('[[flags:state-wip]]', history[0].fields.state);
@@ -1014,14 +990,11 @@ describe('Flags', () => {
describe('.rescind()', () => {
it('should remove a flag\'s report', async () => {
const response = await request({
method: 'delete',
uri: `${nconf.get('url')}/api/v3/flags/4/report`,
const { response } = await request.del(`${nconf.get('url')}/api/v3/flags/4/report`, {
jar,
headers: {
'x-csrf-token': csrfToken,
},
resolveWithFullResponse: true,
});
assert.strictEqual(response.statusCode, 200);
@@ -1030,9 +1003,7 @@ describe('Flags', () => {
describe('.appendNote()', () => {
it('should append a note to the flag', async () => {
const { response } = await request({
method: 'post',
uri: `${nconf.get('url')}/api/v3/flags/4/notes`,
const { body } = await request.post(`${nconf.get('url')}/api/v3/flags/4/notes`, {
jar,
headers: {
'x-csrf-token': csrfToken,
@@ -1041,9 +1012,8 @@ describe('Flags', () => {
note: 'lorem ipsum dolor sit amet',
datetime: 1626446956652,
},
json: true,
});
const { response } = body;
assert(response.hasOwnProperty('notes'));
assert(Array.isArray(response.notes));
assert.strictEqual('lorem ipsum dolor sit amet', response.notes[0].content);
@@ -1058,16 +1028,13 @@ describe('Flags', () => {
describe('.deleteNote()', () => {
it('should delete a note from a flag', async () => {
const { response } = await request({
method: 'delete',
uri: `${nconf.get('url')}/api/v3/flags/4/notes/1626446956652`,
const { body } = await request.del(`${nconf.get('url')}/api/v3/flags/4/notes/1626446956652`, {
jar,
headers: {
'x-csrf-token': csrfToken,
},
json: true,
});
const { response } = body;
assert(Array.isArray(response.history));
assert(Array.isArray(response.notes));
assert.strictEqual(response.notes.length, 0);
@@ -1088,7 +1055,7 @@ describe('Flags', () => {
before(async () => {
uid = await User.create({ username: 'flags-access-control', password: 'abcdef' });
({ jar, csrf_token } = await helpers.loginUser('flags-access-control', 'abcdef'));
console.log('cs', csrfToken);
flaggerUid = await User.create({ username: 'flags-access-control-flagger', password: 'abcdef' });
});
@@ -1106,68 +1073,44 @@ describe('Flags', () => {
});
({ flagId } = await Flags.create('post', postData.pid, flaggerUid, 'spam'));
const commonOpts = {
jar,
headers: {
'x-csrf-token': csrf_token,
},
};
requests = new Set([
{
...commonOpts,
method: 'get',
uri: `${nconf.get('url')}/api/v3/flags/${flagId}`,
jar,
headers: {
'x-csrf-token': csrf_token,
},
json: true,
simple: false,
resolveWithFullResponse: true,
},
{
...commonOpts,
method: 'put',
uri: `${nconf.get('url')}/api/v3/flags/${flagId}`,
jar,
headers: {
'x-csrf-token': csrf_token,
},
body: {
state: 'wip',
},
json: true,
simple: false,
resolveWithFullResponse: true,
},
{
...commonOpts,
method: 'post',
uri: `${nconf.get('url')}/api/v3/flags/${flagId}/notes`,
jar,
headers: {
'x-csrf-token': csrf_token,
},
body: {
note: 'test note',
datetime: noteTime,
},
json: true,
simple: false,
resolveWithFullResponse: true,
},
{
...commonOpts,
method: 'delete',
uri: `${nconf.get('url')}/api/v3/flags/${flagId}/notes/${noteTime}`,
jar,
headers: {
'x-csrf-token': csrf_token,
},
json: true,
simple: false,
resolveWithFullResponse: true,
},
{
...commonOpts,
method: 'delete',
uri: `${nconf.get('url')}/api/v3/flags/${flagId}`,
jar,
headers: {
'x-csrf-token': csrf_token,
},
json: true,
simple: false,
resolveWithFullResponse: true,
},
]);
});
@@ -1179,7 +1122,8 @@ describe('Flags', () => {
delete opts.headers;
// eslint-disable-next-line no-await-in-loop
const { statusCode } = await request(opts);
const { response } = await request[opts.method](opts.uri, opts);
const { statusCode } = response;
assert(statusCode.toString().startsWith(4), `${opts.method.toUpperCase()} ${opts.uri} => ${statusCode}`);
}
});
@@ -1187,7 +1131,8 @@ describe('Flags', () => {
it('should not allow access to privileged flag endpoints to regular users', async () => {
for (const opts of requests) {
// eslint-disable-next-line no-await-in-loop
const { statusCode } = await request(opts);
const { response } = await request[opts.method](opts.uri, opts);
const { statusCode } = response;
assert(statusCode.toString().startsWith(4), `${opts.method.toUpperCase()} ${opts.uri} => ${statusCode}`);
}
});
@@ -1197,7 +1142,8 @@ describe('Flags', () => {
for (const opts of requests) {
// eslint-disable-next-line no-await-in-loop
const { statusCode } = await request(opts);
const { response } = await request[opts.method](opts.uri, opts);
const { statusCode } = response;
assert.strictEqual(statusCode, 200, `${opts.method.toUpperCase()} ${opts.uri} => ${statusCode}`);
}
});
@@ -1207,7 +1153,8 @@ describe('Flags', () => {
for (const opts of requests) {
// eslint-disable-next-line no-await-in-loop
const { statusCode } = await request(opts);
const { response } = await request[opts.method](opts.uri, opts);
const { statusCode } = response;
assert.strictEqual(statusCode, 200, `${opts.method.toUpperCase()} ${opts.uri} => ${statusCode}`);
}
});
@@ -1217,7 +1164,8 @@ describe('Flags', () => {
for (const opts of requests) {
// eslint-disable-next-line no-await-in-loop
const { statusCode } = await request(opts);
const { response } = await request[opts.method](opts.uri, opts);
const { statusCode } = response;
assert.strictEqual(statusCode, 200, `${opts.method.toUpperCase()} ${opts.uri} => ${statusCode}`);
}
});
@@ -1231,7 +1179,8 @@ describe('Flags', () => {
for (const opts of requests) {
// eslint-disable-next-line no-await-in-loop
const { statusCode } = await request(opts);
const { response } = await request[opts.method](opts.uri, opts);
const { statusCode } = response;
assert(statusCode.toString().startsWith(4), `${opts.method.toUpperCase()} ${opts.uri} => ${statusCode}`);
}
});

View File

@@ -1,26 +1,22 @@
'use strict';
const request = require('request');
const requestAsync = require('request-promise-native');
const nconf = require('nconf');
const fs = require('fs');
const path = require('path');
const winston = require('winston');
const utils = require('../../src/utils');
const request = require('../../src/request');
const helpers = module.exports;
helpers.getCsrfToken = async (jar) => {
const { csrf_token: token } = await requestAsync({
url: `${nconf.get('url')}/api/config`,
json: true,
const { body } = await request.get(`${nconf.get('url')}/api/config`, {
jar,
});
return token;
return body.csrf_token;
};
helpers.request = async function (method, uri, options) {
helpers.request = async function (method, uri, options = {}) {
const ignoreMethods = ['GET', 'HEAD', 'OPTIONS'];
const lowercaseMethod = String(method).toLowerCase();
let csrf_token;
@@ -28,79 +24,44 @@ helpers.request = async function (method, uri, options) {
csrf_token = await helpers.getCsrfToken(options.jar);
}
return new Promise((resolve, reject) => {
options.headers = options.headers || {};
if (csrf_token) {
options.headers['x-csrf-token'] = csrf_token;
}
request[lowercaseMethod](`${nconf.get('url')}${uri}`, options, (err, res, body) => {
if (err) reject(err);
else resolve({ res, body });
});
});
return await request[lowercaseMethod](`${nconf.get('url')}${uri}`, options);
};
helpers.loginUser = async (username, password, payload = {}) => {
const jar = request.jar();
const form = { username, password, ...payload };
const data = { username, password, ...payload };
const { statusCode, body: configBody } = await requestAsync({
url: `${nconf.get('url')}/api/config`,
json: true,
const csrf_token = await helpers.getCsrfToken(jar);
const { response, body } = await request.post(`${nconf.get('url')}/login`, {
body: data,
jar: jar,
followRedirect: false,
simple: false,
resolveWithFullResponse: true,
});
if (statusCode !== 200) {
throw new Error('[[error:invalid-response]]');
}
const { csrf_token } = configBody;
const res = await requestAsync.post(`${nconf.get('url')}/login`, {
form,
json: true,
jar: jar,
followRedirect: false,
simple: false,
resolveWithFullResponse: true,
headers: {
'x-csrf-token': csrf_token,
},
});
return { jar, res, body: res.body, csrf_token: csrf_token };
return { jar, response, body, csrf_token };
};
helpers.logoutUser = function (jar, callback) {
request({
url: `${nconf.get('url')}/api/config`,
json: true,
jar: jar,
}, (err, response, body) => {
if (err) {
return callback(err, response, body);
}
request.post(`${nconf.get('url')}/logout`, {
form: {},
json: true,
jar: jar,
helpers.logoutUser = async function (jar) {
const csrf_token = await helpers.getCsrfToken(jar);
const { response, body } = await request.post(`${nconf.get('url')}/logout`, {
body: {},
jar,
headers: {
'x-csrf-token': body.csrf_token,
'x-csrf-token': csrf_token,
},
}, (err, response, body) => {
callback(err, response, body);
});
});
return { response, body };
};
helpers.connectSocketIO = function (res, csrf_token, callback) {
helpers.connectSocketIO = function (res, csrf_token) {
const io = require('socket.io-client');
let cookies = res.headers['set-cookie'];
cookies = cookies.filter(c => /express.sid=[^;]+;/.test(c));
const cookie = cookies[0];
const cookie = res.headers['set-cookie'];
const socket = io(nconf.get('base_url'), {
path: `${nconf.get('relative_path')}/socket.io`,
extraHeaders: {
@@ -111,73 +72,71 @@ helpers.connectSocketIO = function (res, csrf_token, callback) {
_csrf: csrf_token,
},
});
return new Promise((resolve, reject) => {
let error;
socket.on('connect', () => {
if (error) {
return;
}
callback(null, socket);
resolve(socket);
});
socket.on('error', (err) => {
error = err;
console.log('socket.io error', err.stack);
callback(err);
reject(err);
});
});
};
helpers.uploadFile = function (uploadEndPoint, filePath, body, jar, csrf_token, callback) {
let formData = {
files: [
fs.createReadStream(filePath),
],
};
formData = utils.merge(formData, body);
request.post({
url: uploadEndPoint,
formData: formData,
json: true,
jar: jar,
helpers.uploadFile = async function (uploadEndPoint, filePath, data, jar, csrf_token) {
const mime = require('mime');
const form = new FormData();
const file = await fs.promises.readFile(filePath);
const blob = new Blob([file], { type: mime.getType(filePath) });
form.append('files', blob, path.basename(filePath));
if (data && data.params) {
form.append('params', data.params);
}
const response = await fetch(uploadEndPoint, {
method: 'post',
body: form,
headers: {
'x-csrf-token': csrf_token,
cookie: await jar.getCookieString(uploadEndPoint),
},
}, (err, res, body) => {
if (err) {
return callback(err);
}
if (res.statusCode !== 200) {
winston.error(JSON.stringify(body));
}
callback(null, res, body);
});
const body = await response.json();
return {
body,
response: {
status: response.status,
statusCode: response.status,
statusText: response.statusText,
headers: Object.fromEntries(response.headers.entries()),
},
};
};
helpers.registerUser = function (data, callback) {
helpers.registerUser = async function (data) {
const jar = request.jar();
request({
url: `${nconf.get('url')}/api/config`,
json: true,
jar: jar,
}, (err, response, body) => {
if (err) {
return callback(err);
}
const csrf_token = await helpers.getCsrfToken(jar);
if (!data.hasOwnProperty('password-confirm')) {
data['password-confirm'] = data.password;
}
request.post(`${nconf.get('url')}/register`, {
form: data,
json: true,
jar: jar,
const { response, body } = await request.post(`${nconf.get('url')}/register`, {
body: data,
jar,
headers: {
'x-csrf-token': body.csrf_token,
'x-csrf-token': csrf_token,
},
}, (err, response, body) => {
callback(err, jar, response, body);
});
});
return { jar, response, body };
};
// http://stackoverflow.com/a/14387791/583363
@@ -205,37 +164,26 @@ helpers.copyFile = function (source, target, callback) {
}
};
helpers.invite = async function (body, uid, jar, csrf_token) {
console.log('making call');
const res = await requestAsync.post(`${nconf.get('url')}/api/v3/users/${uid}/invites`, {
helpers.invite = async function (data, uid, jar, csrf_token) {
return await request.post(`${nconf.get('url')}/api/v3/users/${uid}/invites`, {
jar: jar,
// using "form" since client "api" module make requests with "application/x-www-form-urlencoded" content-type
form: body,
body: data,
headers: {
'x-csrf-token': csrf_token,
},
simple: false,
resolveWithFullResponse: true,
});
console.log(res.statusCode, res.body);
res.body = JSON.parse(res.body);
return { res, body };
};
helpers.createFolder = function (path, folderName, jar, csrf_token) {
return requestAsync.put(`${nconf.get('url')}/api/v3/files/folder`, {
helpers.createFolder = async function (path, folderName, jar, csrf_token) {
return await request.put(`${nconf.get('url')}/api/v3/files/folder`, {
jar,
body: {
path,
folderName,
},
json: true,
headers: {
'x-csrf-token': csrf_token,
},
simple: false,
resolveWithFullResponse: true,
});
};

View File

@@ -2,45 +2,34 @@
const assert = require('assert');
const nconf = require('nconf');
const request = require('request');
const db = require('./mocks/databasemock');
const meta = require('../src/meta');
const request = require('../src/request');
describe('Language detection', () => {
it('should detect the language for a guest', (done) => {
meta.configs.set('autoDetectLang', 1, (err) => {
assert.ifError(err);
request(`${nconf.get('url')}/api/config`, {
it('should detect the language for a guest', async () => {
await meta.configs.set('autoDetectLang', 1);
const { body } = await request.get(`${nconf.get('url')}/api/config`, {
headers: {
'Accept-Language': 'de-DE,de;q=0.5',
},
json: true,
}, (err, res, body) => {
assert.ifError(err);
});
assert.ok(body);
assert.strictEqual(body.userLang, 'de');
done();
});
});
});
it('should do nothing when disabled', (done) => {
meta.configs.set('autoDetectLang', 0, (err) => {
assert.ifError(err);
request(`${nconf.get('url')}/api/config`, {
it('should do nothing when disabled', async () => {
await meta.configs.set('autoDetectLang', 0);
const { body } = await request.get(`${nconf.get('url')}/api/config`, {
headers: {
'Accept-Language': 'de-DE,de;q=0.5',
},
json: true,
}, (err, res, body) => {
assert.ifError(err);
assert.ok(body);
});
assert.ok(body);
assert.strictEqual(body.userLang, 'en-GB');
done();
});
});
});
});

View File

@@ -1,7 +1,7 @@
'use strict';
const assert = require('assert');
const request = require('request-promise-native');
const nconf = require('nconf');
const util = require('util');
@@ -14,7 +14,7 @@ const Groups = require('../src/groups');
const Messaging = require('../src/messaging');
const api = require('../src/api');
const helpers = require('./helpers');
const socketModules = require('../src/socket.io/modules');
const request = require('../src/request');
const utils = require('../src/utils');
const translator = require('../src/translator');
@@ -33,12 +33,8 @@ describe('Messaging Library', () => {
const callv3API = async (method, path, body, user) => {
const options = {
method,
body,
json: true,
jar: mocks.users[user].jar,
resolveWithFullResponse: true,
simple: false,
};
if (method !== 'get') {
@@ -47,7 +43,7 @@ describe('Messaging Library', () => {
};
}
return request(`${nconf.get('url')}/api/v3${path}`, options);
return request[method](`${nconf.get('url')}/api/v3${path}`, options);
};
before(async () => {
@@ -162,11 +158,11 @@ describe('Messaging Library', () => {
uids: [mocks.users.baz.uid],
}, 'foo');
const { statusCode, body } = await callv3API('post', `/chats`, {
const { response, body } = await callv3API('post', `/chats`, {
uids: [mocks.users.baz.uid],
}, 'foo');
assert.equal(statusCode, 400);
assert.equal(response.statusCode, 400);
assert.equal(body.status.code, 'bad-request');
assert.equal(body.status.message, await translator.translate('[[error:too-many-messages]]'));
meta.config.chatMessageDelay = oldValue;
@@ -190,20 +186,20 @@ describe('Messaging Library', () => {
assert.strictEqual(messages[0].system, 1);
assert.strictEqual(messages[0].content, 'user-join');
const { statusCode, body: body2 } = await callv3API('put', `/chats/${roomId}/messages/${messages[0].messageId}`, {
const { response, body: body2 } = await callv3API('put', `/chats/${roomId}/messages/${messages[0].messageId}`, {
message: 'test',
}, 'foo');
assert.strictEqual(statusCode, 400);
assert.strictEqual(response.statusCode, 400);
assert.equal(body2.status.message, await translator.translate('[[error:cant-edit-chat-message]]'));
});
it('should fail to add user to room with invalid data', async () => {
let { statusCode, body } = await callv3API('post', `/chats/${roomId}/users`, {}, 'foo');
assert.strictEqual(statusCode, 400);
let { response, body } = await callv3API('post', `/chats/${roomId}/users`, {}, 'foo');
assert.strictEqual(response.statusCode, 400);
assert.strictEqual(body.status.message, await translator.translate('[[error:required-parameters-missing, uids]]'));
({ statusCode, body } = await callv3API('post', `/chats/${roomId}/users`, { uids: [null] }, 'foo'));
assert.strictEqual(statusCode, 400);
({ response, body } = await callv3API('post', `/chats/${roomId}/users`, { uids: [null] }, 'foo'));
assert.strictEqual(response.statusCode, 400);
assert.strictEqual(body.status.message, await translator.translate('[[error:no-user]]'));
});
@@ -220,38 +216,38 @@ describe('Messaging Library', () => {
});
it('should throw error if user is not in room', async () => {
const { statusCode, body } = await callv3API('get', `/chats/${roomId}/users`, {}, 'bar');
assert.strictEqual(statusCode, 403);
const { response, body } = await callv3API('get', `/chats/${roomId}/users`, {}, 'bar');
assert.strictEqual(response.statusCode, 403);
assert.equal(body.status.message, await translator.translate('[[error:no-privileges]]'));
});
it('should fail to add users to room if max is reached', async () => {
meta.config.maximumUsersInChatRoom = 2;
const { statusCode, body } = await callv3API('post', `/chats/${roomId}/users`, { uids: [mocks.users.bar.uid] }, 'foo');
assert.strictEqual(statusCode, 400);
const { response, body } = await callv3API('post', `/chats/${roomId}/users`, { uids: [mocks.users.bar.uid] }, 'foo');
assert.strictEqual(response.statusCode, 400);
assert.equal(body.status.message, await translator.translate('[[error:cant-add-more-users-to-chat-room]]'));
meta.config.maximumUsersInChatRoom = 0;
});
it('should fail to add users to room if user does not exist', async () => {
const { statusCode, body } = await callv3API('post', `/chats/${roomId}/users`, { uids: [98237498234] }, 'foo');
assert.strictEqual(statusCode, 400);
const { response, body } = await callv3API('post', `/chats/${roomId}/users`, { uids: [98237498234] }, 'foo');
assert.strictEqual(response.statusCode, 400);
assert.strictEqual(body.status.message, await translator.translate('[[error:no-user]]'));
});
it('should fail to add self to room', async () => {
const { statusCode, body } = await callv3API('post', `/chats/${roomId}/users`, { uids: [mocks.users.foo.uid] }, 'foo');
assert.strictEqual(statusCode, 400);
const { response, body } = await callv3API('post', `/chats/${roomId}/users`, { uids: [mocks.users.foo.uid] }, 'foo');
assert.strictEqual(response.statusCode, 400);
assert.strictEqual(body.status.message, await translator.translate('[[error:cant-chat-with-yourself]]'));
});
it('should fail to leave room with invalid data', async () => {
let { statusCode, body } = await callv3API('delete', `/chats/${roomId}/users`, {}, 'foo');
assert.strictEqual(statusCode, 400);
let { response, body } = await callv3API('delete', `/chats/${roomId}/users`, {}, 'foo');
assert.strictEqual(response.statusCode, 400);
assert.strictEqual(body.status.message, await translator.translate('[[error:required-parameters-missing, uids]]'));
({ statusCode, body } = await callv3API('delete', `/chats/${roomId}/users`, { uids: [98237423] }, 'foo'));
assert.strictEqual(statusCode, 400);
({ response, body } = await callv3API('delete', `/chats/${roomId}/users`, { uids: [98237423] }, 'foo'));
assert.strictEqual(response.statusCode, 400);
assert.strictEqual(body.status.message, await translator.translate('[[error:no-user]]'));
});
@@ -303,9 +299,7 @@ describe('Messaging Library', () => {
const { jar: senderJar, csrf_token: senderCsrf } = await helpers.loginUser('deleted_chat_user', 'barbar');
const receiver = await User.create({ username: 'receiver' });
const { response } = await request(`${nconf.get('url')}/api/v3/chats`, {
method: 'post',
json: true,
const { body } = await request.post(`${nconf.get('url')}/api/v3/chats`, {
jar: senderJar,
body: {
uids: [receiver],
@@ -315,31 +309,31 @@ describe('Messaging Library', () => {
},
});
await User.deleteAccount(sender);
assert(await Messaging.isRoomOwner(receiver, response.roomId));
assert(await Messaging.isRoomOwner(receiver, body.response.roomId));
});
it('should fail to remove user from room', async () => {
let { statusCode, body } = await callv3API('delete', `/chats/${roomId}/users`, {}, 'foo');
assert.strictEqual(statusCode, 400);
let { response, body } = await callv3API('delete', `/chats/${roomId}/users`, {}, 'foo');
assert.strictEqual(response.statusCode, 400);
assert.strictEqual(body.status.message, await translator.translate('[[error:required-parameters-missing, uids]]'));
({ statusCode, body } = await callv3API('delete', `/chats/${roomId}/users`, { uids: [null] }, 'foo'));
assert.strictEqual(statusCode, 400);
({ response, body } = await callv3API('delete', `/chats/${roomId}/users`, { uids: [null] }, 'foo'));
assert.strictEqual(response.statusCode, 400);
assert.strictEqual(body.status.message, await translator.translate('[[error:no-user]]'));
});
it('should fail to remove user from room if user does not exist', async () => {
const { statusCode, body } = await callv3API('delete', `/chats/${roomId}/users`, { uids: [99] }, 'foo');
assert.strictEqual(statusCode, 400);
const { response, body } = await callv3API('delete', `/chats/${roomId}/users`, { uids: [99] }, 'foo');
assert.strictEqual(response.statusCode, 400);
assert.strictEqual(body.status.message, await translator.translate('[[error:no-user]]'));
});
it('should remove user from room', async () => {
const { statusCode, body } = await callv3API('post', `/chats`, {
const { response, body } = await callv3API('post', `/chats`, {
uids: [mocks.users.herp.uid],
}, 'foo');
const { roomId } = body.response;
assert.strictEqual(statusCode, 200);
assert.strictEqual(response.statusCode, 200);
let isInRoom = await Messaging.isUserInRoom(mocks.users.herp.uid, roomId);
assert(isInRoom);
@@ -488,8 +482,8 @@ describe('Messaging Library', () => {
});
it('should rename room', async () => {
const { statusCode } = await callv3API('put', `/chats/${roomId}`, { name: 'new room name' }, 'foo');
assert.strictEqual(statusCode, 200);
const { response } = await callv3API('put', `/chats/${roomId}`, { name: 'new room name' }, 'foo');
assert.strictEqual(response.statusCode, 200);
});
it('should send a room-rename system message when a room is renamed', async () => {
@@ -638,46 +632,46 @@ describe('Messaging Library', () => {
});
it('should fail to edit message with invalid data', async () => {
let { statusCode, body } = await callv3API('put', `/chats/1/messages/10000`, { message: 'foo' }, 'foo');
assert.strictEqual(statusCode, 400);
let { response, body } = await callv3API('put', `/chats/1/messages/10000`, { message: 'foo' }, 'foo');
assert.strictEqual(response.statusCode, 400);
assert.strictEqual(body.status.message, await translator.translate('[[error:invalid-mid]]'));
({ statusCode, body } = await callv3API('put', `/chats/${roomId}/messages/${mid}`, {}, 'foo'));
assert.strictEqual(statusCode, 400);
({ response, body } = await callv3API('put', `/chats/${roomId}/messages/${mid}`, {}, 'foo'));
assert.strictEqual(response.statusCode, 400);
assert.strictEqual(body.status.message, await translator.translate('[[error:invalid-chat-message]]'));
});
it('should fail to edit message if new content is empty string', async () => {
const { statusCode, body } = await callv3API('put', `/chats/${roomId}/messages/${mid}`, { message: ' ' }, 'foo');
assert.strictEqual(statusCode, 400);
const { response, body } = await callv3API('put', `/chats/${roomId}/messages/${mid}`, { message: ' ' }, 'foo');
assert.strictEqual(response.statusCode, 400);
assert.strictEqual(body.status.message, await translator.translate('[[error:invalid-chat-message]]'));
});
it('should fail to edit message if not own message', async () => {
const { statusCode, body } = await callv3API('put', `/chats/${roomId}/messages/${mid}`, { message: 'message edited' }, 'herp');
assert.strictEqual(statusCode, 400);
const { response, body } = await callv3API('put', `/chats/${roomId}/messages/${mid}`, { message: 'message edited' }, 'herp');
assert.strictEqual(response.statusCode, 400);
assert.strictEqual(body.status.message, await translator.translate('[[error:cant-edit-chat-message]]'));
});
it('should fail to edit message if message not in room', async () => {
const { statusCode, body } = await callv3API('put', `/chats/${roomId}/messages/1014`, { message: 'message edited' }, 'herp');
assert.strictEqual(statusCode, 400);
const { response, body } = await callv3API('put', `/chats/${roomId}/messages/1014`, { message: 'message edited' }, 'herp');
assert.strictEqual(response.statusCode, 400);
assert.strictEqual(body.status.message, await translator.translate('[[error:invalid-mid]]'));
});
it('should edit message', async () => {
let { statusCode, body } = await callv3API('put', `/chats/${roomId}/messages/${mid}`, { message: 'message edited' }, 'foo');
assert.strictEqual(statusCode, 200);
let { response, body } = await callv3API('put', `/chats/${roomId}/messages/${mid}`, { message: 'message edited' }, 'foo');
assert.strictEqual(response.statusCode, 200);
assert.strictEqual(body.response.content, 'message edited');
({ statusCode, body } = await callv3API('get', `/chats/${roomId}/messages/${mid}`, {}, 'foo'));
assert.strictEqual(statusCode, 200);
({ response, body } = await callv3API('get', `/chats/${roomId}/messages/${mid}`, {}, 'foo'));
assert.strictEqual(response.statusCode, 200);
assert.strictEqual(body.response.content, 'message edited');
});
it('should fail to delete message if not owner', async () => {
const { statusCode, body } = await callv3API('delete', `/chats/${roomId}/messages/${mid}`, {}, 'herp');
assert.strictEqual(statusCode, 400);
const { response, body } = await callv3API('delete', `/chats/${roomId}/messages/${mid}`, {}, 'herp');
assert.strictEqual(response.statusCode, 400);
assert.strictEqual(body.status.message, 'You are not allowed to delete this message');
});
@@ -716,8 +710,8 @@ describe('Messaging Library', () => {
});
it('should error out if a message is deleted again', async () => {
const { statusCode, body } = await callv3API('delete', `/chats/${roomId}/messages/${mid}`, {}, 'foo');
assert.strictEqual(statusCode, 400);
const { response, body } = await callv3API('delete', `/chats/${roomId}/messages/${mid}`, {}, 'foo');
assert.strictEqual(response.statusCode, 400);
assert.strictEqual(body.status.message, 'This chat message has already been deleted.');
});
@@ -728,8 +722,8 @@ describe('Messaging Library', () => {
});
it('should error out if a message is restored again', async () => {
const { statusCode, body } = await callv3API('post', `/chats/${roomId}/messages/${mid}`, {}, 'foo');
assert.strictEqual(statusCode, 400);
const { response, body } = await callv3API('post', `/chats/${roomId}/messages/${mid}`, {}, 'foo');
assert.strictEqual(response.statusCode, 400);
assert.strictEqual(body.status.message, 'This chat message has already been restored.');
});
@@ -743,8 +737,8 @@ describe('Messaging Library', () => {
});
it('should error out for regular users', async () => {
const { statusCode, body } = await callv3API('delete', `/chats/${roomId}/messages/${mid2}`, {}, 'baz');
assert.strictEqual(statusCode, 400);
const { response, body } = await callv3API('delete', `/chats/${roomId}/messages/${mid2}`, {}, 'baz');
assert.strictEqual(response.statusCode, 400);
assert.strictEqual(body.status.message, 'chat-message-editing-disabled');
});
@@ -767,33 +761,21 @@ describe('Messaging Library', () => {
describe('controller', () => {
it('should 404 if chat is disabled', async () => {
meta.config.disableChat = 1;
const response = await request(`${nconf.get('url')}/user/baz/chats`, {
resolveWithFullResponse: true,
simple: false,
});
const { response } = await request.get(`${nconf.get('url')}/user/baz/chats`);
assert.equal(response.statusCode, 404);
});
it('should 401 for guest with not-authorised status code', async () => {
meta.config.disableChat = 0;
const response = await request(`${nconf.get('url')}/api/user/baz/chats`, {
resolveWithFullResponse: true,
simple: false,
json: true,
});
const { body } = response;
const { response, body } = await request.get(`${nconf.get('url')}/api/user/baz/chats`);
assert.equal(response.statusCode, 401);
assert.equal(body.status.code, 'not-authorised');
});
it('should 404 for non-existent user', async () => {
const response = await request(`${nconf.get('url')}/user/doesntexist/chats`, {
resolveWithFullResponse: true,
simple: false,
});
const { response } = await request.get(`${nconf.get('url')}/user/doesntexist/chats`);
assert.equal(response.statusCode, 404);
});
});
@@ -805,13 +787,7 @@ describe('Messaging Library', () => {
});
it('should return chats page data', async () => {
const response = await request(`${nconf.get('url')}/api/user/herp/chats`, {
resolveWithFullResponse: true,
simple: false,
json: true,
jar,
});
const { body } = response;
const { response, body } = await request.get(`${nconf.get('url')}/api/user/herp/chats`, { jar });
assert.equal(response.statusCode, 200);
assert(Array.isArray(body.rooms));
@@ -820,13 +796,7 @@ describe('Messaging Library', () => {
});
it('should return room data', async () => {
const response = await request(`${nconf.get('url')}/api/user/herp/chats/${roomId}`, {
resolveWithFullResponse: true,
simple: false,
json: true,
jar,
});
const { body } = response;
const { response, body } = await request.get(`${nconf.get('url')}/api/user/herp/chats/${roomId}`, { jar });
assert.equal(response.statusCode, 200);
assert.equal(body.roomId, roomId);
@@ -834,27 +804,16 @@ describe('Messaging Library', () => {
});
it('should redirect to chats page', async () => {
const res = await request(`${nconf.get('url')}/api/chats`, {
resolveWithFullResponse: true,
simple: false,
jar,
json: true,
});
const { body } = res;
const { response, body } = await request.get(`${nconf.get('url')}/api/chats`, { jar });
assert.equal(res.statusCode, 200);
assert.equal(res.headers['x-redirect'], '/user/herp/chats');
assert.equal(response.statusCode, 200);
assert.equal(response.headers['x-redirect'], '/user/herp/chats');
assert.equal(body, '/user/herp/chats');
});
it('should return 404 if user is not in room', async () => {
const data = await helpers.loginUser('baz', 'quuxquux');
const response = await request(`${nconf.get('url')}/api/user/baz/chats/${roomId}`, {
resolveWithFullResponse: true,
simple: false,
json: true,
jar: data.jar,
});
const { response } = await request.get(`${nconf.get('url')}/api/user/baz/chats/${roomId}`, { jar: data.jar });
assert.equal(response.statusCode, 404);
});

View File

@@ -2,13 +2,14 @@
const assert = require('assert');
const async = require('async');
const request = require('request');
const nconf = require('nconf');
const db = require('./mocks/databasemock');
const meta = require('../src/meta');
const User = require('../src/user');
const Groups = require('../src/groups');
const request = require('../src/request');
describe('meta', () => {
let fooUid;
@@ -489,117 +490,86 @@ describe('meta', () => {
});
describe('Access-Control-Allow-Origin', () => {
it('Access-Control-Allow-Origin header should be empty', (done) => {
it('Access-Control-Allow-Origin header should be empty', async () => {
const jar = request.jar();
request.get(`${nconf.get('url')}/api/search?term=bug`, {
form: {},
json: true,
const { response } = await request.get(`${nconf.get('url')}/api/search?term=bug`, {
jar: jar,
}, (err, response, body) => {
assert.ifError(err);
assert.equal(response.headers['access-control-allow-origin'], undefined);
done();
});
});
it('should set proper Access-Control-Allow-Origin header', (done) => {
assert.equal(response.headers['access-control-allow-origin'], undefined);
});
it('should set proper Access-Control-Allow-Origin header', async () => {
const jar = request.jar();
const oldValue = meta.config['access-control-allow-origin'];
meta.config['access-control-allow-origin'] = 'test.com, mydomain.com';
request.get(`${nconf.get('url')}/api/search?term=bug`, {
form: {
},
json: true,
const { response } = await request.get(`${nconf.get('url')}/api/search?term=bug`, {
jar: jar,
headers: {
origin: 'mydomain.com',
},
}, (err, response, body) => {
assert.ifError(err);
assert.equal(response.headers['access-control-allow-origin'], 'mydomain.com');
meta.config['access-control-allow-origin'] = oldValue;
done(err);
});
});
it('Access-Control-Allow-Origin header should be empty if origin does not match', (done) => {
assert.equal(response.headers['access-control-allow-origin'], 'mydomain.com');
meta.config['access-control-allow-origin'] = oldValue;
});
it('Access-Control-Allow-Origin header should be empty if origin does not match', async () => {
const jar = request.jar();
const oldValue = meta.config['access-control-allow-origin'];
meta.config['access-control-allow-origin'] = 'test.com, mydomain.com';
request.get(`${nconf.get('url')}/api/search?term=bug`, {
form: {
},
json: true,
const { response } = await request.get(`${nconf.get('url')}/api/search?term=bug`, {
data: {},
jar: jar,
headers: {
origin: 'notallowed.com',
},
}, (err, response, body) => {
assert.ifError(err);
});
assert.equal(response.headers['access-control-allow-origin'], undefined);
meta.config['access-control-allow-origin'] = oldValue;
done(err);
});
});
it('should set proper Access-Control-Allow-Origin header', (done) => {
it('should set proper Access-Control-Allow-Origin header', async () => {
const jar = request.jar();
const oldValue = meta.config['access-control-allow-origin-regex'];
meta.config['access-control-allow-origin-regex'] = 'match\\.this\\..+\\.domain.com, mydomain\\.com';
request.get(`${nconf.get('url')}/api/search?term=bug`, {
form: {
},
json: true,
const { response } = await request.get(`${nconf.get('url')}/api/search?term=bug`, {
jar: jar,
headers: {
origin: 'match.this.anything123.domain.com',
},
}, (err, response, body) => {
assert.ifError(err);
assert.equal(response.headers['access-control-allow-origin'], 'match.this.anything123.domain.com');
meta.config['access-control-allow-origin-regex'] = oldValue;
done(err);
});
});
it('Access-Control-Allow-Origin header should be empty if origin does not match', (done) => {
assert.equal(response.headers['access-control-allow-origin'], 'match.this.anything123.domain.com');
meta.config['access-control-allow-origin-regex'] = oldValue;
});
it('Access-Control-Allow-Origin header should be empty if origin does not match', async () => {
const jar = request.jar();
const oldValue = meta.config['access-control-allow-origin-regex'];
meta.config['access-control-allow-origin-regex'] = 'match\\.this\\..+\\.domain.com, mydomain\\.com';
request.get(`${nconf.get('url')}/api/search?term=bug`, {
form: {
},
json: true,
const { response } = await request.get(`${nconf.get('url')}/api/search?term=bug`, {
jar: jar,
headers: {
origin: 'notallowed.com',
},
}, (err, response, body) => {
assert.ifError(err);
});
assert.equal(response.headers['access-control-allow-origin'], undefined);
meta.config['access-control-allow-origin-regex'] = oldValue;
done(err);
});
});
it('should not error with invalid regexp', (done) => {
it('should not error with invalid regexp', async () => {
const jar = request.jar();
const oldValue = meta.config['access-control-allow-origin-regex'];
meta.config['access-control-allow-origin-regex'] = '[match\\.this\\..+\\.domain.com, mydomain\\.com';
request.get(`${nconf.get('url')}/api/search?term=bug`, {
form: {
},
json: true,
const { response } = await request.get(`${nconf.get('url')}/api/search?term=bug`, {
jar: jar,
headers: {
origin: 'mydomain.com',
},
}, (err, response, body) => {
assert.ifError(err);
});
assert.equal(response.headers['access-control-allow-origin'], 'mydomain.com');
meta.config['access-control-allow-origin-regex'] = oldValue;
done(err);
});
});
});

View File

@@ -2,13 +2,13 @@
const assert = require('assert');
const nconf = require('nconf');
const request = require('request-promise-native');
const db = require('./mocks/databasemock');
const user = require('../src/user');
const groups = require('../src/groups');
const utils = require('../src/utils');
const request = require('../src/request');
const helpers = require('./helpers');
describe('Middlewares', () => {
@@ -116,81 +116,61 @@ describe('Middlewares', () => {
});
it('should be absent on non-existent routes, for guests', async () => {
const res = await request(`${nconf.get('url')}/${utils.generateUUID()}`, {
simple: false,
resolveWithFullResponse: true,
});
const { response } = await request.get(`${nconf.get('url')}/${utils.generateUUID()}`);
assert.strictEqual(res.statusCode, 404);
assert(!Object.keys(res.headers).includes('cache-control'));
assert.strictEqual(response.statusCode, 404);
assert(!Object.keys(response.headers).includes('cache-control'));
});
it('should be set to "private" on non-existent routes, for logged in users', async () => {
const res = await request(`${nconf.get('url')}/${utils.generateUUID()}`, {
simple: false,
resolveWithFullResponse: true,
const { response } = await request.get(`${nconf.get('url')}/${utils.generateUUID()}`, {
jar,
headers: {
accept: 'text/html',
},
});
assert.strictEqual(res.statusCode, 404);
assert(Object.keys(res.headers).includes('cache-control'));
assert.strictEqual(res.headers['cache-control'], 'private');
assert.strictEqual(response.statusCode, 404);
assert(Object.keys(response.headers).includes('cache-control'));
assert.strictEqual(response.headers['cache-control'], 'private');
});
it('should be absent on regular routes, for guests', async () => {
const res = await request(nconf.get('url'), {
simple: false,
resolveWithFullResponse: true,
});
const { response } = await request.get(nconf.get('url'));
assert.strictEqual(res.statusCode, 200);
assert(!Object.keys(res.headers).includes('cache-control'));
assert.strictEqual(response.statusCode, 200);
assert(!Object.keys(response.headers).includes('cache-control'));
});
it('should be absent on api routes, for guests', async () => {
const res = await request(`${nconf.get('url')}/api`, {
simple: false,
resolveWithFullResponse: true,
});
const { response } = await request.get(`${nconf.get('url')}/api`);
assert.strictEqual(res.statusCode, 200);
assert(!Object.keys(res.headers).includes('cache-control'));
assert.strictEqual(response.statusCode, 200);
assert(!Object.keys(response.headers).includes('cache-control'));
});
it('should be set to "private" on regular routes, for logged-in users', async () => {
const res = await request(nconf.get('url'), {
simple: false,
resolveWithFullResponse: true,
jar,
});
const { response } = await request.get(nconf.get('url'), { jar });
assert.strictEqual(res.statusCode, 200);
assert(Object.keys(res.headers).includes('cache-control'));
assert.strictEqual(res.headers['cache-control'], 'private');
assert.strictEqual(response.statusCode, 200);
assert(Object.keys(response.headers).includes('cache-control'));
assert.strictEqual(response.headers['cache-control'], 'private');
});
it('should be set to "private" on api routes, for logged-in users', async () => {
const res = await request(`${nconf.get('url')}/api`, {
simple: false,
resolveWithFullResponse: true,
jar,
});
const { response } = await request.get(`${nconf.get('url')}/api`, { jar });
assert.strictEqual(res.statusCode, 200);
assert(Object.keys(res.headers).includes('cache-control'));
assert.strictEqual(res.headers['cache-control'], 'private');
assert.strictEqual(response.statusCode, 200);
assert(Object.keys(response.headers).includes('cache-control'));
assert.strictEqual(response.headers['cache-control'], 'private');
});
it('should be set to "private" on apiv3 routes, for logged-in users', async () => {
const res = await request(`${nconf.get('url')}/api/v3/users/${uid}`, {
simple: false,
resolveWithFullResponse: true,
jar,
});
const { response } = await request.get(`${nconf.get('url')}/api/v3/users/${uid}`, { jar });
assert.strictEqual(res.statusCode, 200);
assert(Object.keys(res.headers).includes('cache-control'));
assert.strictEqual(res.headers['cache-control'], 'private');
assert.strictEqual(response.statusCode, 200);
assert(Object.keys(response.headers).includes('cache-control'));
assert.strictEqual(response.headers['cache-control'], 'private');
});
});
});

View File

@@ -3,11 +3,12 @@
const assert = require('assert');
const path = require('path');
const nconf = require('nconf');
const request = require('request');
const fs = require('fs');
const db = require('./mocks/databasemock');
const plugins = require('../src/plugins');
const request = require('../src/request');
describe('Plugins', () => {
it('should load plugin data', (done) => {
@@ -290,33 +291,24 @@ describe('Plugins', () => {
});
describe('static assets', () => {
it('should 404 if resource does not exist', (done) => {
request.get(`${nconf.get('url')}/plugins/doesnotexist/should404.tpl`, (err, res, body) => {
assert.ifError(err);
assert.equal(res.statusCode, 404);
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);
done();
});
});
it('should 404 if resource does not exist', (done) => {
it('should 404 if resource does not exist', async () => {
const url = `${nconf.get('url')}/plugins/nodebb-plugin-dbsearch/dbsearch/templates/admin/plugins/should404.tpl`;
request.get(url, (err, res, body) => {
assert.ifError(err);
assert.equal(res.statusCode, 404);
const { response, body } = await request.get(url);
assert.equal(response.statusCode, 404);
assert(body);
done();
});
});
it('should get resource', (done) => {
it('should get resource', async () => {
const url = `${nconf.get('url')}/assets/templates/admin/plugins/dbsearch.tpl`;
request.get(url, (err, res, body) => {
assert.ifError(err);
assert.equal(res.statusCode, 200);
const { response, body } = await request.get(url);
assert.equal(response.statusCode, 200);
assert(body);
done();
});
});
});
@@ -371,7 +363,6 @@ describe('Plugins', () => {
assert.ifError(err);
assert(Array.isArray(data));
data.forEach((pluginData) => {
console.log(pluginData);
assert(activePlugins.includes(pluginData));
});
done();

View File

@@ -2,8 +2,7 @@
const assert = require('assert');
const async = require('async');
const request = require('request-promise-native');
const nconf = require('nconf');
const path = require('path');
const util = require('util');
@@ -24,6 +23,7 @@ const meta = require('../src/meta');
const file = require('../src/file');
const helpers = require('./helpers');
const utils = require('../src/utils');
const request = require('../src/request');
describe('Post\'s', () => {
let voterUid;
@@ -33,52 +33,26 @@ describe('Post\'s', () => {
let topicData;
let cid;
before((done) => {
async.series({
voterUid: function (next) {
user.create({ username: 'upvoter' }, next);
},
voteeUid: function (next) {
user.create({ username: 'upvotee' }, next);
},
globalModUid: function (next) {
user.create({ username: 'globalmod', password: 'globalmodpwd' }, next);
},
category: function (next) {
categories.create({
before(async () => {
voterUid = await user.create({ username: 'upvoter' });
voteeUid = await user.create({ username: 'upvotee' });
globalModUid = await user.create({ username: 'globalmod', password: 'globalmodpwd' });
({ cid } = await categories.create({
name: 'Test Category',
description: 'Test category created by testing script',
}, next);
},
}, (err, results) => {
if (err) {
return done(err);
}
}));
voterUid = results.voterUid;
voteeUid = results.voteeUid;
globalModUid = results.globalModUid;
cid = results.category.cid;
topics.post({
uid: results.voteeUid,
cid: results.category.cid,
({ topicData, postData } = await topics.post({
uid: voteeUid,
cid: cid,
title: 'Test Topic Title',
content: 'The content of test topic',
}, (err, data) => {
if (err) {
return done(err);
}
postData = data.postData;
topicData = data.topicData;
groups.join('Global Moderators', globalModUid, done);
});
});
}));
await groups.join('Global Moderators', globalModUid);
});
it('should update category teaser properly', async () => {
const getCategoriesAsync = async () => await request(`${nconf.get('url')}/api/categories`, { json: true });
const getCategoriesAsync = async () => (await request.get(`${nconf.get('url')}/api/categories`, { })).body;
const postResult = await topics.post({ uid: globalModUid, cid: cid, title: 'topic title', content: '123456789' });
let data = await getCategoriesAsync();
@@ -372,24 +346,14 @@ describe('Post\'s', () => {
assert.strictEqual(isDeleted, 1);
});
it('should not see post content if global mod does not have posts:view_deleted privilege', (done) => {
async.waterfall([
function (next) {
user.create({ username: 'global mod', password: '123456' }, next);
},
function (uid, next) {
groups.join('Global Moderators', uid, next);
},
function (next) {
privileges.categories.rescind(['groups:posts:view_deleted'], cid, 'Global Moderators', next);
},
async () => {
it('should not see post content if global mod does not have posts:view_deleted privilege', async () => {
const uid = await user.create({ username: 'global mod', password: '123456' });
await groups.join('Global Moderators', uid);
await privileges.categories.rescind(['groups:posts:view_deleted'], cid, 'Global Moderators');
const { jar } = await helpers.loginUser('global mod', '123456');
const { posts } = await request(`${nconf.get('url')}/api/topic/${tid}`, { jar, json: true });
assert.equal(posts[1].content, '[[topic:post-is-deleted]]');
const { body } = await request.get(`${nconf.get('url')}/api/topic/${tid}`, { jar });
assert.equal(body.posts[1].content, '[[topic:post-is-deleted]]');
await privileges.categories.give(['groups:posts:view_deleted'], cid, 'Global Moderators');
},
], done);
});
it('should restore a post', async () => {
@@ -1013,7 +977,8 @@ describe('Post\'s', () => {
it('should load queued posts', async () => {
({ jar } = await helpers.loginUser('globalmod', 'globalmodpwd'));
const { posts } = await request(`${nconf.get('url')}/api/post-queue`, { jar: jar, json: true });
const { body } = await request.get(`${nconf.get('url')}/api/post-queue`, { jar });
const { posts } = body;
assert.equal(posts[0].type, 'topic');
assert.equal(posts[0].data.content, 'queued topic content');
assert.equal(posts[1].type, 'reply');
@@ -1029,21 +994,24 @@ describe('Post\'s', () => {
it('should edit post in queue', async () => {
await socketPosts.editQueuedContent({ uid: globalModUid }, { id: queueId, content: 'newContent' });
const { posts } = await request(`${nconf.get('url')}/api/post-queue`, { jar: jar, json: true });
const { body } = await request.get(`${nconf.get('url')}/api/post-queue`, { jar });
const { posts } = body;
assert.equal(posts[1].type, 'reply');
assert.equal(posts[1].data.content, 'newContent');
});
it('should edit topic title in queue', async () => {
await socketPosts.editQueuedContent({ uid: globalModUid }, { id: topicQueueId, title: 'new topic title' });
const { posts } = await request(`${nconf.get('url')}/api/post-queue`, { jar: jar, json: true });
const { body } = await request.get(`${nconf.get('url')}/api/post-queue`, { jar });
const { posts } = body;
assert.equal(posts[0].type, 'topic');
assert.equal(posts[0].data.title, 'new topic title');
});
it('should edit topic category in queue', async () => {
await socketPosts.editQueuedContent({ uid: globalModUid }, { id: topicQueueId, cid: 2 });
const { posts } = await request(`${nconf.get('url')}/api/post-queue`, { jar: jar, json: true });
const { body } = await request.get(`${nconf.get('url')}/api/post-queue`, { jar });
const { posts } = body;
assert.equal(posts[0].type, 'topic');
assert.equal(posts[0].data.cid, 2);
await socketPosts.editQueuedContent({ uid: globalModUid }, { id: topicQueueId, cid: cid });
@@ -1063,20 +1031,10 @@ describe('Post\'s', () => {
});
});
it('should accept queued posts and submit', (done) => {
let ids;
async.waterfall([
function (next) {
db.getSortedSetRange('post:queue', 0, -1, next);
},
function (_ids, next) {
ids = _ids;
socketPosts.accept({ uid: globalModUid }, { id: ids[0] }, next);
},
function (next) {
socketPosts.accept({ uid: globalModUid }, { id: ids[1] }, next);
},
], done);
it('should accept queued posts and submit', async () => {
const ids = await db.getSortedSetRange('post:queue', 0, -1);
await socketPosts.accept({ uid: globalModUid }, { id: ids[0] });
await socketPosts.accept({ uid: globalModUid }, { id: ids[1] });
});
it('should not crash if id does not exist', (done) => {

View File

@@ -6,7 +6,6 @@ const path = require('path');
const os = require('os');
const nconf = require('nconf');
const async = require('async');
const crypto = require('crypto');
const db = require('../mocks/databasemock');
@@ -75,24 +74,15 @@ describe('upload methods', () => {
});
});
it('should remove an image if it is edited out of the post', (done) => {
async.series([
function (next) {
posts.edit({
it('should remove an image if it is edited out of the post', async () => {
await posts.edit({
pid: pid,
uid,
content: 'here is an image [alt text](/assets/uploads/files/abracadabra.png)... AND NO MORE!',
}, next);
},
async.apply(posts.uploads.sync, pid),
], (err) => {
assert.ifError(err);
db.sortedSetCard(`post:${pid}:uploads`, (err, length) => {
assert.ifError(err);
});
await posts.uploads.sync(pid);
const length = await db.sortedSetCard(`post:${pid}:uploads`);
assert.strictEqual(1, length);
done();
});
});
});
});
@@ -127,85 +117,52 @@ describe('upload methods', () => {
});
describe('.associate()', () => {
it('should add an image to the post\'s maintained list of uploads', (done) => {
async.waterfall([
async.apply(posts.uploads.associate, pid, 'files/whoa.gif'),
async.apply(posts.uploads.list, pid),
], (err, uploads) => {
assert.ifError(err);
it('should add an image to the post\'s maintained list of uploads', async () => {
await posts.uploads.associate(pid, 'files/whoa.gif');
const uploads = await posts.uploads.list(pid);
assert.strictEqual(2, uploads.length);
assert.strictEqual(true, uploads.includes('files/whoa.gif'));
done();
});
});
it('should allow arrays to be passed in', (done) => {
async.waterfall([
async.apply(posts.uploads.associate, pid, ['files/amazeballs.jpg', 'files/wut.txt']),
async.apply(posts.uploads.list, pid),
], (err, uploads) => {
assert.ifError(err);
it('should allow arrays to be passed in', async () => {
await posts.uploads.associate(pid, ['files/amazeballs.jpg', 'files/wut.txt']);
const uploads = await posts.uploads.list(pid);
assert.strictEqual(4, uploads.length);
assert.strictEqual(true, uploads.includes('files/amazeballs.jpg'));
assert.strictEqual(true, uploads.includes('files/wut.txt'));
done();
});
});
it('should save a reverse association of md5sum to pid', (done) => {
it('should save a reverse association of md5sum to pid', async () => {
const md5 = filename => crypto.createHash('md5').update(filename).digest('hex');
async.waterfall([
async.apply(posts.uploads.associate, pid, ['files/test.bmp']),
function (next) {
db.getSortedSetRange(`upload:${md5('files/test.bmp')}:pids`, 0, -1, next);
},
], (err, pids) => {
assert.ifError(err);
await posts.uploads.associate(pid, ['files/test.bmp']);
const pids = await db.getSortedSetRange(`upload:${md5('files/test.bmp')}:pids`, 0, -1);
assert.strictEqual(true, Array.isArray(pids));
assert.strictEqual(true, pids.length > 0);
assert.equal(pid, pids[0]);
done();
});
});
it('should not associate a file that does not exist on the local disk', (done) => {
async.waterfall([
async.apply(posts.uploads.associate, pid, ['files/nonexistant.xls']),
async.apply(posts.uploads.list, pid),
], (err, uploads) => {
assert.ifError(err);
it('should not associate a file that does not exist on the local disk', async () => {
await posts.uploads.associate(pid, ['files/nonexistant.xls']);
const uploads = await posts.uploads.list(pid);
assert.strictEqual(uploads.length, 5);
assert.strictEqual(false, uploads.includes('files/nonexistant.xls'));
done();
});
});
});
describe('.dissociate()', () => {
it('should remove an image from the post\'s maintained list of uploads', (done) => {
async.waterfall([
async.apply(posts.uploads.dissociate, pid, 'files/whoa.gif'),
async.apply(posts.uploads.list, pid),
], (err, uploads) => {
assert.ifError(err);
it('should remove an image from the post\'s maintained list of uploads', async () => {
await posts.uploads.dissociate(pid, 'files/whoa.gif');
const uploads = await posts.uploads.list(pid);
assert.strictEqual(4, uploads.length);
assert.strictEqual(false, uploads.includes('files/whoa.gif'));
done();
});
});
it('should allow arrays to be passed in', (done) => {
async.waterfall([
async.apply(posts.uploads.dissociate, pid, ['files/amazeballs.jpg', 'files/wut.txt']),
async.apply(posts.uploads.list, pid),
], (err, uploads) => {
assert.ifError(err);
it('should allow arrays to be passed in', async () => {
await posts.uploads.dissociate(pid, ['files/amazeballs.jpg', 'files/wut.txt']);
const uploads = await posts.uploads.list(pid);
assert.strictEqual(2, uploads.length);
assert.strictEqual(false, uploads.includes('files/amazeballs.jpg'));
assert.strictEqual(false, uploads.includes('files/wut.txt'));
done();
});
});
it('should remove the image\'s user association, if present', async () => {
@@ -397,21 +354,14 @@ describe('post uploads management', () => {
});
});
it('should automatically sync uploads on post edit', (done) => {
async.waterfall([
async.apply(posts.edit, {
it('should automatically sync uploads on post edit', async () => {
await posts.edit({
pid: reply.pid,
uid,
content: 'no uploads',
}),
function (postData, next) {
posts.uploads.list(reply.pid, next);
},
], (err, uploads) => {
assert.ifError(err);
});
const uploads = await posts.uploads.list(reply.pid);
assert.strictEqual(true, Array.isArray(uploads));
assert.strictEqual(0, uploads.length);
done();
});
});
});

View File

@@ -2,8 +2,6 @@
const assert = require('assert');
const async = require('async');
const request = require('request');
const nconf = require('nconf');
const db = require('./mocks/databasemock');
@@ -12,6 +10,7 @@ const categories = require('../src/categories');
const user = require('../src/user');
const search = require('../src/search');
const privileges = require('../src/privileges');
const request = require('../src/request');
describe('Search', () => {
let phoebeUid;
@@ -26,103 +25,60 @@ describe('Search', () => {
let cid2;
let cid3;
before((done) => {
async.waterfall([
function (next) {
async.series({
phoebe: function (next) {
user.create({ username: 'phoebe' }, next);
},
ginger: function (next) {
user.create({ username: 'ginger' }, next);
},
category1: function (next) {
categories.create({
before(async () => {
phoebeUid = await user.create({ username: 'phoebe' });
gingerUid = await user.create({ username: 'ginger' });
cid1 = (await categories.create({
name: 'Test Category',
description: 'Test category created by testing script',
}, next);
},
category2: function (next) {
categories.create({
name: 'Test Category',
description: 'Test category created by testing script',
}, next);
},
}, next);
},
function (results, next) {
phoebeUid = results.phoebe;
gingerUid = results.ginger;
cid1 = results.category1.cid;
cid2 = results.category2.cid;
})).cid;
async.waterfall([
function (next) {
categories.create({
cid2 = (await categories.create({
name: 'Test Category',
description: 'Test category created by testing script',
})).cid;
cid3 = (await categories.create({
name: 'Child Test Category',
description: 'Test category created by testing script',
parentCid: cid2,
}, next);
},
function (category, next) {
cid3 = category.cid;
topics.post({
})).cid;
({ topicData: topic1Data, postData: post1Data } = await topics.post({
uid: phoebeUid,
cid: cid1,
title: 'nodebb mongodb bugs',
content: 'avocado cucumber apple orange fox',
tags: ['nodebb', 'bug', 'plugin', 'nodebb-plugin', 'jquery'],
}, next);
},
function (results, next) {
topic1Data = results.topicData;
post1Data = results.postData;
}));
topics.post({
({ topicData: topic2Data, postData: post2Data } = await topics.post({
uid: gingerUid,
cid: cid2,
title: 'java mongodb redis',
content: 'avocado cucumber carrot armadillo',
tags: ['nodebb', 'bug', 'plugin', 'nodebb-plugin', 'javascript'],
}, next);
},
function (results, next) {
topic2Data = results.topicData;
post2Data = results.postData;
topics.reply({
}));
post3Data = await topics.reply({
uid: phoebeUid,
content: 'reply post apple',
tid: topic2Data.tid,
}, next);
},
function (_post3Data, next) {
post3Data = _post3Data;
setTimeout(next, 500);
},
], next);
},
], done);
});
});
it('should search term in titles and posts', (done) => {
it('should search term in titles and posts', async () => {
const meta = require('../src/meta');
const qs = `/api/search?term=cucumber&in=titlesposts&categories[]=${cid1}&by=phoebe&replies=1&repliesFilter=atleast&sortBy=timestamp&sortDirection=desc&showAs=posts`;
privileges.global.give(['groups:search:content'], 'guests', (err) => {
assert.ifError(err);
request({
url: nconf.get('url') + qs,
json: true,
}, (err, response, body) => {
assert.ifError(err);
await privileges.global.give(['groups:search:content'], 'guests');
const { body } = await request.get(nconf.get('url') + qs);
assert(body);
assert.equal(body.matchCount, 1);
assert.equal(body.posts.length, 1);
assert.equal(body.posts[0].pid, post1Data.pid);
assert.equal(body.posts[0].uid, phoebeUid);
privileges.global.rescind(['groups:search:content'], 'guests', done);
});
});
await privileges.global.rescind(['groups:search:content'], 'guests');
});
it('should search for a user', (done) => {
@@ -225,44 +181,31 @@ describe('Search', () => {
});
});
it('should search child categories', (done) => {
async.waterfall([
function (next) {
topics.post({
it('should search child categories', async () => {
await topics.post({
uid: gingerUid,
cid: cid3,
title: 'child category topic',
content: 'avocado cucumber carrot armadillo',
}, next);
},
function (result, next) {
search.search({
});
const result = await search.search({
query: 'avocado',
searchIn: 'titlesposts',
categories: [cid2],
searchChildren: true,
sortBy: 'topic.timestamp',
sortDirection: 'desc',
}, next);
},
function (result, next) {
});
assert(result.posts.length, 2);
assert(result.posts[0].topic.title === 'child category topic');
assert(result.posts[1].topic.title === 'java mongodb redis');
next();
},
], done);
});
it('should return json search data with no categories', (done) => {
it('should return json search data with no categories', async () => {
const qs = '/api/search?term=cucumber&in=titlesposts&searchOnly=1';
privileges.global.give(['groups:search:content'], 'guests', (err) => {
assert.ifError(err);
request({
url: nconf.get('url') + qs,
json: true,
}, (err, response, body) => {
assert.ifError(err);
await privileges.global.give(['groups:search:content'], 'guests');
const { body } = await request.get(nconf.get('url') + qs);
assert(body);
assert(body.hasOwnProperty('matchCount'));
assert(body.hasOwnProperty('pagination'));
@@ -270,24 +213,15 @@ describe('Search', () => {
assert(body.hasOwnProperty('posts'));
assert(!body.hasOwnProperty('categories'));
privileges.global.rescind(['groups:search:content'], 'guests', done);
});
});
await privileges.global.rescind(['groups:search:content'], 'guests');
});
it('should not crash without a search term', (done) => {
it('should not crash without a search term', async () => {
const qs = '/api/search';
privileges.global.give(['groups:search:content'], 'guests', (err) => {
assert.ifError(err);
request({
url: nconf.get('url') + qs,
json: true,
}, (err, response, body) => {
assert.ifError(err);
await privileges.global.give(['groups:search:content'], 'guests');
const { response, body } = await request.get(nconf.get('url') + qs);
assert(body);
assert.strictEqual(response.statusCode, 200);
privileges.global.rescind(['groups:search:content'], 'guests', done);
});
});
await privileges.global.rescind(['groups:search:content'], 'guests');
});
});

View File

@@ -9,11 +9,7 @@ const util = require('util');
const sleep = util.promisify(setTimeout);
const assert = require('assert');
const async = require('async');
const nconf = require('nconf');
const request = require('request');
const cookies = request.jar();
const db = require('./mocks/databasemock');
const user = require('../src/user');
@@ -52,35 +48,11 @@ describe('socket.io', () => {
});
it('should connect and auth properly', (done) => {
request.get({
url: `${nconf.get('url')}/api/config`,
jar: cookies,
json: true,
}, (err, res, body) => {
assert.ifError(err);
request.post(`${nconf.get('url')}/login`, {
jar: cookies,
form: {
username: 'admin',
password: 'adminpwd',
},
headers: {
'x-csrf-token': body.csrf_token,
},
json: true,
}, (err, res) => {
assert.ifError(err);
helpers.connectSocketIO(res, body.csrf_token, (err, _io) => {
io = _io;
assert.ifError(err);
done();
});
});
});
it('should connect and auth properly', async () => {
const { response, csrf_token } = await helpers.loginUser('admin', 'adminpwd');
io = await helpers.connectSocketIO(response, csrf_token);
assert(io);
assert(io.emit);
});
it('should return error for unknown event', (done) => {
@@ -459,6 +431,7 @@ describe('socket.io', () => {
});
});
describe('install/upgrade plugin', () => {
it('should toggle plugin install', function (done) {
this.timeout(0);
const oldValue = process.env.NODE_ENV;
@@ -476,6 +449,23 @@ describe('socket.io', () => {
});
});
it('should upgrade plugin', function (done) {
this.timeout(0);
const oldValue = process.env.NODE_ENV;
process.env.NODE_ENV = 'development';
socketAdmin.plugins.upgrade({
uid: adminUid,
}, {
id: 'nodebb-plugin-location-to-map',
version: 'latest',
}, (err) => {
assert.ifError(err);
process.env.NODE_ENV = oldValue;
done();
});
});
});
it('should get list of active plugins', (done) => {
socketAdmin.plugins.getActive({ uid: adminUid }, {}, (err, data) => {
assert.ifError(err);
@@ -501,22 +491,6 @@ describe('socket.io', () => {
});
});
it('should upgrade plugin', function (done) {
this.timeout(0);
const oldValue = process.env.NODE_ENV;
process.env.NODE_ENV = 'development';
socketAdmin.plugins.upgrade({
uid: adminUid,
}, {
id: 'nodebb-plugin-location-to-map',
version: 'latest',
}, (err) => {
assert.ifError(err);
process.env.NODE_ENV = oldValue;
done();
});
});
it('should error with invalid data', (done) => {
socketAdmin.widgets.set({ uid: adminUid }, null, (err) => {
assert.equal(err.message, '[[error:invalid-data]]');
@@ -709,60 +683,43 @@ describe('socket.io', () => {
assert(pwExpiry > then && pwExpiry < Date.now());
});
it('should not error on valid email', (done) => {
socketUser.reset.send({ uid: 0 }, 'regular@test.com', (err) => {
assert.ifError(err);
async.parallel({
count: async.apply(db.sortedSetCount.bind(db), 'reset:issueDate', 0, Date.now()),
event: async.apply(events.getEvents, '', 0, 0),
}, (err, data) => {
assert.ifError(err);
assert.strictEqual(data.count, 2);
it('should not error on valid email', async () => {
await socketUser.reset.send({ uid: 0 }, 'regular@test.com');
const [count, eventsData] = await Promise.all([
db.sortedSetCount('reset:issueDate', 0, Date.now()),
events.getEvents('', 0, 0),
]);
assert.strictEqual(count, 2);
// Event validity
assert.strictEqual(data.event.length, 1);
const event = data.event[0];
assert.strictEqual(eventsData.length, 1);
const event = eventsData[0];
assert.strictEqual(event.type, 'password-reset');
assert.strictEqual(event.text, '[[success:success]]');
done();
});
});
});
it('should not generate code if rate limited', (done) => {
socketUser.reset.send({ uid: 0 }, 'regular@test.com', (err) => {
assert(err);
async.parallel({
count: async.apply(db.sortedSetCount.bind(db), 'reset:issueDate', 0, Date.now()),
event: async.apply(events.getEvents, '', 0, 0),
}, (err, data) => {
assert.ifError(err);
assert.strictEqual(data.count, 2);
it('should not generate code if rate limited', async () => {
await assert.rejects(
socketUser.reset.send({ uid: 0 }, 'regular@test.com'),
{ message: '[[error:reset-rate-limited]]' },
);
const [count, eventsData] = await Promise.all([
db.sortedSetCount('reset:issueDate', 0, Date.now()),
events.getEvents('', 0, 0),
]);
assert.strictEqual(count, 2);
// Event validity
assert.strictEqual(data.event.length, 1);
const event = data.event[0];
assert.strictEqual(eventsData.length, 1);
const event = eventsData[0];
assert.strictEqual(event.type, 'password-reset');
assert.strictEqual(event.text, '[[error:reset-rate-limited]]');
done();
});
});
});
it('should not error on invalid email (but not generate reset code)', (done) => {
socketUser.reset.send({ uid: 0 }, 'irregular@test.com', (err) => {
assert.ifError(err);
db.sortedSetCount('reset:issueDate', 0, Date.now(), (err, count) => {
assert.ifError(err);
it('should not error on invalid email (but not generate reset code)', async () => {
await socketUser.reset.send({ uid: 0 }, 'irregular@test.com');
const count = await db.sortedSetCount('reset:issueDate', 0, Date.now());
assert.strictEqual(count, 2);
done();
});
});
});
it('should error on no email', (done) => {

File diff suppressed because it is too large Load Diff

View File

@@ -324,20 +324,14 @@ describe('Topic thumbs', () => {
createFiles();
});
it('should succeed with a valid tid', (done) => {
helpers.uploadFile(`${nconf.get('url')}/api/v3/topics/1/thumbs`, path.join(__dirname, '../files/test.png'), {}, adminJar, adminCSRF, (err, res, body) => {
assert.ifError(err);
assert.strictEqual(res.statusCode, 200);
done();
});
it('should succeed with a valid tid', async () => {
const { response } = await helpers.uploadFile(`${nconf.get('url')}/api/v3/topics/1/thumbs`, path.join(__dirname, '../files/test.png'), {}, adminJar, adminCSRF);
assert.strictEqual(response.statusCode, 200);
});
it('should succeed with a uuid', (done) => {
helpers.uploadFile(`${nconf.get('url')}/api/v3/topics/${uuid}/thumbs`, path.join(__dirname, '../files/test.png'), {}, adminJar, adminCSRF, (err, res, body) => {
assert.ifError(err);
assert.strictEqual(res.statusCode, 200);
done();
});
it('should succeed with a uuid', async () => {
const { response } = await helpers.uploadFile(`${nconf.get('url')}/api/v3/topics/${uuid}/thumbs`, path.join(__dirname, '../files/test.png'), {}, adminJar, adminCSRF);
assert.strictEqual(response.statusCode, 200);
});
it('should succeed with uploader plugins', async () => {
@@ -350,63 +344,49 @@ describe('Topic thumbs', () => {
method: hookMethod,
});
await new Promise((resolve) => {
helpers.uploadFile(`${nconf.get('url')}/api/v3/topics/${uuid}/thumbs`, path.join(__dirname, '../files/test.png'), {}, adminJar, adminCSRF, (err, res, body) => {
assert.ifError(err);
assert.strictEqual(res.statusCode, 200);
resolve();
});
});
const { response } = await helpers.uploadFile(
`${nconf.get('url')}/api/v3/topics/${uuid}/thumbs`,
path.join(__dirname, '../files/test.png'),
{},
adminJar,
adminCSRF
);
assert.strictEqual(response.statusCode, 200);
await plugins.hooks.unregister('test', 'filter:uploadFile', hookMethod);
});
it('should fail with a non-existant tid', (done) => {
helpers.uploadFile(`${nconf.get('url')}/api/v3/topics/4/thumbs`, path.join(__dirname, '../files/test.png'), {}, adminJar, adminCSRF, (err, res, body) => {
assert.ifError(err);
assert.strictEqual(res.statusCode, 404);
done();
});
it('should fail with a non-existant tid', async () => {
const { response } = await helpers.uploadFile(`${nconf.get('url')}/api/v3/topics/4/thumbs`, path.join(__dirname, '../files/test.png'), {}, adminJar, adminCSRF);
assert.strictEqual(response.statusCode, 404);
});
it('should fail when garbage is passed in', (done) => {
helpers.uploadFile(`${nconf.get('url')}/api/v3/topics/abracadabra/thumbs`, path.join(__dirname, '../files/test.png'), {}, adminJar, adminCSRF, (err, res, body) => {
assert.ifError(err);
assert.strictEqual(res.statusCode, 404);
done();
});
it('should fail when garbage is passed in', async () => {
const { response } = await helpers.uploadFile(`${nconf.get('url')}/api/v3/topics/abracadabra/thumbs`, path.join(__dirname, '../files/test.png'), {}, adminJar, adminCSRF);
assert.strictEqual(response.statusCode, 404);
});
it('should fail when calling user cannot edit the tid', (done) => {
helpers.uploadFile(`${nconf.get('url')}/api/v3/topics/2/thumbs`, path.join(__dirname, '../files/test.png'), {}, fooJar, fooCSRF, (err, res, body) => {
assert.ifError(err);
assert.strictEqual(res.statusCode, 403);
done();
});
it('should fail when calling user cannot edit the tid', async () => {
const { response } = await helpers.uploadFile(`${nconf.get('url')}/api/v3/topics/2/thumbs`, path.join(__dirname, '../files/test.png'), {}, fooJar, fooCSRF);
assert.strictEqual(response.statusCode, 403);
});
it('should fail if thumbnails are not enabled', (done) => {
it('should fail if thumbnails are not enabled', async () => {
meta.config.allowTopicsThumbnail = 0;
helpers.uploadFile(`${nconf.get('url')}/api/v3/topics/${uuid}/thumbs`, path.join(__dirname, '../files/test.png'), {}, adminJar, adminCSRF, (err, res, body) => {
assert.ifError(err);
assert.strictEqual(res.statusCode, 503);
const { response, body } = await helpers.uploadFile(`${nconf.get('url')}/api/v3/topics/${uuid}/thumbs`, path.join(__dirname, '../files/test.png'), {}, adminJar, adminCSRF);
assert.strictEqual(response.statusCode, 503);
assert(body && body.status);
assert.strictEqual(body.status.message, 'Topic thumbnails are disabled.');
done();
});
});
it('should fail if file is not image', (done) => {
it('should fail if file is not image', async () => {
meta.config.allowTopicsThumbnail = 1;
helpers.uploadFile(`${nconf.get('url')}/api/v3/topics/${uuid}/thumbs`, path.join(__dirname, '../files/503.html'), {}, adminJar, adminCSRF, (err, res, body) => {
assert.ifError(err);
assert.strictEqual(res.statusCode, 500);
const { response, body } = await helpers.uploadFile(`${nconf.get('url')}/api/v3/topics/${uuid}/thumbs`, path.join(__dirname, '../files/503.html'), {}, adminJar, adminCSRF);
assert.strictEqual(response.statusCode, 500);
assert(body && body.status);
assert.strictEqual(body.status.message, 'Invalid File');
done();
});
});
});

View File

@@ -5,9 +5,6 @@ const assert = require('assert');
const nconf = require('nconf');
const path = require('path');
const fs = require('fs').promises;
const request = require('request');
const requestAsync = require('request-promise-native');
const util = require('util');
const db = require('./mocks/databasemock');
const categories = require('../src/categories');
@@ -21,6 +18,7 @@ const socketUser = require('../src/socket.io/user');
const helpers = require('./helpers');
const file = require('../src/file');
const image = require('../src/image');
const request = require('../src/request');
const emptyUploadsFolder = async () => {
const files = await fs.readdir(`${nconf.get('upload_path')}/files`);
@@ -83,31 +81,25 @@ describe('Upload Controllers', () => {
await privileges.global.give(['groups:upload:post:file'], 'registered-users');
});
it('should fail if the user exceeds the upload rate limit threshold', (done) => {
it('should fail if the user exceeds the upload rate limit threshold', async () => {
const oldValue = meta.config.allowedFileExtensions;
meta.config.allowedFileExtensions = 'png,jpg,bmp,html';
require('../src/middleware/uploads').clearCache();
const times = meta.config.uploadRateLimitThreshold + 1;
async.timesSeries(times, (i, next) => {
helpers.uploadFile(`${nconf.get('url')}/api/post/upload`, path.join(__dirname, '../test/files/503.html'), {}, jar, csrf_token, (err, res, body) => {
for (let i = 0; i < times; i++) {
// eslint-disable-next-line no-await-in-loop
const { response, body } = await helpers.uploadFile(`${nconf.get('url')}/api/post/upload`, path.join(__dirname, '../test/files/503.html'), {}, jar, csrf_token);
if (i + 1 >= times) {
assert.strictEqual(res.statusCode, 500);
assert.strictEqual(response.statusCode, 500);
assert.strictEqual(body.error, '[[error:upload-ratelimit-reached]]');
} else {
assert.ifError(err);
assert.strictEqual(res.statusCode, 200);
assert.strictEqual(response.statusCode, 200);
assert(body && body.status && body.response && body.response.images);
assert(Array.isArray(body.response.images));
assert(body.response.images[0].url);
}
next(err);
});
}, (err) => {
}
meta.config.allowedFileExtensions = oldValue;
assert.ifError(err);
done();
});
});
});
@@ -121,34 +113,26 @@ describe('Upload Controllers', () => {
await privileges.global.give(['groups:upload:post:file'], 'registered-users');
});
it('should upload an image to a post', (done) => {
helpers.uploadFile(`${nconf.get('url')}/api/post/upload`, path.join(__dirname, '../test/files/test.png'), {}, jar, csrf_token, (err, res, body) => {
assert.ifError(err);
assert.equal(res.statusCode, 200);
it('should upload an image to a post', async () => {
const { response, body } = await helpers.uploadFile(`${nconf.get('url')}/api/post/upload`, path.join(__dirname, '../test/files/test.png'), {}, jar, csrf_token);
assert.equal(response.statusCode, 200);
assert(body && body.status && body.response && body.response.images);
assert(Array.isArray(body.response.images));
assert(body.response.images[0].url);
done();
});
});
it('should upload an image to a post and then delete the upload', (done) => {
helpers.uploadFile(`${nconf.get('url')}/api/post/upload`, path.join(__dirname, '../test/files/test.png'), {}, jar, csrf_token, (err, res, body) => {
assert.ifError(err);
assert.strictEqual(res.statusCode, 200);
it('should upload an image to a post and then delete the upload', async () => {
const { response, body } = await helpers.uploadFile(`${nconf.get('url')}/api/post/upload`, path.join(__dirname, '../test/files/test.png'), {}, jar, csrf_token);
assert.strictEqual(response.statusCode, 200);
assert(body && body.status && body.response && body.response.images);
assert(Array.isArray(body.response.images));
assert(body.response.images[0].url);
const name = body.response.images[0].url.replace(`${nconf.get('relative_path') + nconf.get('upload_url')}/`, '');
socketUser.deleteUpload({ uid: regularUid }, { uid: regularUid, name: name }, (err) => {
assert.ifError(err);
db.getSortedSetRange(`uid:${regularUid}:uploads`, 0, -1, (err, uploads) => {
assert.ifError(err);
await socketUser.deleteUpload({ uid: regularUid }, { uid: regularUid, name: name });
const uploads = await db.getSortedSetRange(`uid:${regularUid}:uploads`, 0, -1);
assert.equal(uploads.includes(name), false);
done();
});
});
});
});
it('should not allow deleting if path is not correct', (done) => {
@@ -165,55 +149,45 @@ describe('Upload Controllers', () => {
});
});
it('should resize and upload an image to a post', (done) => {
it('should resize and upload an image to a post', async () => {
const oldValue = meta.config.resizeImageWidth;
meta.config.resizeImageWidth = 10;
meta.config.resizeImageWidthThreshold = 10;
helpers.uploadFile(`${nconf.get('url')}/api/post/upload`, path.join(__dirname, '../test/files/test.png'), {}, jar, csrf_token, (err, res, body) => {
assert.ifError(err);
assert.equal(res.statusCode, 200);
const { response, body } = await helpers.uploadFile(`${nconf.get('url')}/api/post/upload`, path.join(__dirname, '../test/files/test.png'), {}, jar, csrf_token);
assert.equal(response.statusCode, 200);
assert(body && body.status && body.response && body.response.images);
assert(Array.isArray(body.response.images));
assert(body.response.images[0].url);
assert(body.response.images[0].url.match(/\/assets\/uploads\/files\/\d+-test-resized\.png/));
meta.config.resizeImageWidth = oldValue;
meta.config.resizeImageWidthThreshold = 1520;
done();
});
});
it('should upload a file to a post', (done) => {
it('should upload a file to a post', async () => {
const oldValue = meta.config.allowedFileExtensions;
meta.config.allowedFileExtensions = 'png,jpg,bmp,html';
helpers.uploadFile(`${nconf.get('url')}/api/post/upload`, path.join(__dirname, '../test/files/503.html'), {}, jar, csrf_token, (err, res, body) => {
const { response, body } = await helpers.uploadFile(`${nconf.get('url')}/api/post/upload`, path.join(__dirname, '../test/files/503.html'), {}, jar, csrf_token);
meta.config.allowedFileExtensions = oldValue;
assert.ifError(err);
assert.strictEqual(res.statusCode, 200);
assert.strictEqual(response.statusCode, 200);
assert(body && body.status && body.response && body.response.images);
assert(Array.isArray(body.response.images));
assert(body.response.images[0].url);
done();
});
});
it('should fail to upload image to post if image dimensions are too big', (done) => {
helpers.uploadFile(`${nconf.get('url')}/api/post/upload`, path.join(__dirname, '../test/files/toobig.png'), {}, jar, csrf_token, (err, res, body) => {
assert.ifError(err);
assert.strictEqual(res.statusCode, 500);
it('should fail to upload image to post if image dimensions are too big', async () => {
const { response, body } = await helpers.uploadFile(`${nconf.get('url')}/api/post/upload`, path.join(__dirname, '../test/files/toobig.png'), {}, jar, csrf_token);
assert.strictEqual(response.statusCode, 500);
assert(body && body.status && body.status.message);
assert.strictEqual(body.status.message, 'Image dimensions are too big');
done();
});
});
it('should fail to upload image to post if image is broken', (done) => {
helpers.uploadFile(`${nconf.get('url')}/api/post/upload`, path.join(__dirname, '../test/files/brokenimage.png'), {}, jar, csrf_token, (err, res, body) => {
assert.ifError(err);
assert.strictEqual(res.statusCode, 500);
it('should fail to upload image to post if image is broken', async () => {
const { response, body } = await helpers.uploadFile(`${nconf.get('url')}/api/post/upload`, path.join(__dirname, '../test/files/brokenimage.png'), {}, jar, csrf_token);
assert.strictEqual(response.statusCode, 500);
assert(body && body.status && body.status.message);
assert.strictEqual(body.status.message, 'Input file contains unsupported image format');
done();
});
});
it('should fail if file is not an image', (done) => {
@@ -286,39 +260,22 @@ describe('Upload Controllers', () => {
});
});
it('should delete users uploads if account is deleted', (done) => {
let uid;
let url;
it('should delete users uploads if account is deleted', async () => {
const uid = await user.create({ username: 'uploader', password: 'barbar' });
const file = require('../src/file');
const data = await helpers.loginUser('uploader', 'barbar');
const { body } = await helpers.uploadFile(`${nconf.get('url')}/api/post/upload`, path.join(__dirname, '../test/files/test.png'), {}, data.jar, data.csrf_token);
async.waterfall([
function (next) {
user.create({ username: 'uploader', password: 'barbar' }, next);
},
function (_uid, next) {
uid = _uid;
helpers.loginUser('uploader', 'barbar', next);
},
function (data, next) {
helpers.uploadFile(`${nconf.get('url')}/api/post/upload`, path.join(__dirname, '../test/files/test.png'), {}, data.jar, data.csrf_token, next);
},
function (res, body, next) {
assert(body && body.status && body.response && body.response.images);
assert(Array.isArray(body.response.images));
assert(body.response.images[0].url);
url = body.response.images[0].url;
const { url } = body.response.images[0];
await user.delete(1, uid);
user.delete(1, uid, next);
},
function (userData, next) {
const filePath = path.join(nconf.get('upload_path'), url.replace('/assets/uploads', ''));
file.exists(filePath, next);
},
function (exists, next) {
const exists = await file.exists(filePath);
assert(!exists);
done();
},
], done);
});
after(emptyUploadsFolder);
@@ -337,173 +294,147 @@ describe('Upload Controllers', () => {
regular_csrf_token = regularLogin.csrf_token;
});
it('should upload site logo', (done) => {
helpers.uploadFile(`${nconf.get('url')}/api/admin/uploadlogo`, path.join(__dirname, '../test/files/test.png'), {}, jar, csrf_token, (err, res, body) => {
assert.ifError(err);
assert.strictEqual(res.statusCode, 200);
it('should upload site logo', async () => {
const { response, body } = await helpers.uploadFile(`${nconf.get('url')}/api/admin/uploadlogo`, path.join(__dirname, '../test/files/test.png'), {}, jar, csrf_token);
assert.strictEqual(response.statusCode, 200);
assert(Array.isArray(body));
assert.equal(body[0].url, `${nconf.get('relative_path')}/assets/uploads/system/site-logo.png`);
done();
});
});
it('should fail to upload invalid file type', (done) => {
helpers.uploadFile(`${nconf.get('url')}/api/admin/category/uploadpicture`, path.join(__dirname, '../test/files/503.html'), { params: JSON.stringify({ cid: cid }) }, jar, csrf_token, (err, res, body) => {
assert.ifError(err);
assert.strictEqual(res.statusCode, 500);
it('should fail to upload invalid file type', async () => {
const { response, body } = await helpers.uploadFile(`${nconf.get('url')}/api/admin/category/uploadpicture`, path.join(__dirname, '../test/files/503.html'), { params: JSON.stringify({ cid: cid }) }, jar, csrf_token);
assert.strictEqual(response.statusCode, 500);
assert.equal(body.error, '[[error:invalid-image-type, image&#x2F;png&amp;#44; image&#x2F;jpeg&amp;#44; image&#x2F;pjpeg&amp;#44; image&#x2F;jpg&amp;#44; image&#x2F;gif&amp;#44; image&#x2F;svg+xml]]');
done();
});
});
it('should fail to upload category image with invalid json params', (done) => {
helpers.uploadFile(`${nconf.get('url')}/api/admin/category/uploadpicture`, path.join(__dirname, '../test/files/test.png'), { params: 'invalid json' }, jar, csrf_token, (err, res, body) => {
assert.ifError(err);
assert.strictEqual(res.statusCode, 500);
it('should fail to upload category image with invalid json params', async () => {
const { response, body } = await helpers.uploadFile(`${nconf.get('url')}/api/admin/category/uploadpicture`, path.join(__dirname, '../test/files/test.png'), { params: 'invalid json' }, jar, csrf_token);
assert.strictEqual(response.statusCode, 500);
assert.equal(body.error, '[[error:invalid-json]]');
done();
});
});
it('should upload category image', (done) => {
helpers.uploadFile(`${nconf.get('url')}/api/admin/category/uploadpicture`, path.join(__dirname, '../test/files/test.png'), { params: JSON.stringify({ cid: cid }) }, jar, csrf_token, (err, res, body) => {
assert.ifError(err);
assert.equal(res.statusCode, 200);
it('should upload category image', async () => {
const { response, body } = await helpers.uploadFile(`${nconf.get('url')}/api/admin/category/uploadpicture`, path.join(__dirname, '../test/files/test.png'), { params: JSON.stringify({ cid: cid }) }, jar, csrf_token);
assert.equal(response.statusCode, 200);
assert(Array.isArray(body));
assert.equal(body[0].url, `${nconf.get('relative_path')}/assets/uploads/category/category-1.png`);
done();
});
});
it('should upload default avatar', (done) => {
helpers.uploadFile(`${nconf.get('url')}/api/admin/uploadDefaultAvatar`, path.join(__dirname, '../test/files/test.png'), { }, jar, csrf_token, (err, res, body) => {
assert.ifError(err);
assert.equal(res.statusCode, 200);
it('should upload default avatar', async () => {
const { response, body } = await helpers.uploadFile(`${nconf.get('url')}/api/admin/uploadDefaultAvatar`, path.join(__dirname, '../test/files/test.png'), { }, jar, csrf_token);
assert.equal(response.statusCode, 200);
assert.equal(body[0].url, `${nconf.get('relative_path')}/assets/uploads/system/avatar-default.png`);
done();
});
});
it('should upload og image', (done) => {
helpers.uploadFile(`${nconf.get('url')}/api/admin/uploadOgImage`, path.join(__dirname, '../test/files/test.png'), { }, jar, csrf_token, (err, res, body) => {
assert.ifError(err);
assert.equal(res.statusCode, 200);
it('should upload og image', async () => {
const { response, body } = await helpers.uploadFile(`${nconf.get('url')}/api/admin/uploadOgImage`, path.join(__dirname, '../test/files/test.png'), { }, jar, csrf_token);
assert.equal(response.statusCode, 200);
assert.equal(body[0].url, `${nconf.get('relative_path')}/assets/uploads/system/og-image.png`);
done();
});
});
it('should upload favicon', (done) => {
helpers.uploadFile(`${nconf.get('url')}/api/admin/uploadfavicon`, path.join(__dirname, '../test/files/favicon.ico'), {}, jar, csrf_token, (err, res, body) => {
assert.ifError(err);
assert.equal(res.statusCode, 200);
it('should upload favicon', async () => {
const { response, body } = await helpers.uploadFile(`${nconf.get('url')}/api/admin/uploadfavicon`, path.join(__dirname, '../test/files/favicon.ico'), {}, jar, csrf_token);
assert.equal(response.statusCode, 200);
assert(Array.isArray(body));
assert.equal(body[0].url, '/assets/uploads/system/favicon.ico');
done();
});
});
it('should upload touch icon', (done) => {
it('should upload touch icon', async () => {
const touchiconAssetPath = '/assets/uploads/system/touchicon-orig.png';
helpers.uploadFile(`${nconf.get('url')}/api/admin/uploadTouchIcon`, path.join(__dirname, '../test/files/test.png'), {}, jar, csrf_token, (err, res, body) => {
assert.ifError(err);
assert.equal(res.statusCode, 200);
const { response, body } = await helpers.uploadFile(
`${nconf.get('url')}/api/admin/uploadTouchIcon`,
path.join(__dirname, '../test/files/test.png'),
{},
jar,
csrf_token
);
assert.equal(response.statusCode, 200);
assert(Array.isArray(body));
assert.equal(body[0].url, touchiconAssetPath);
meta.config['brand:touchIcon'] = touchiconAssetPath;
request(`${nconf.get('url')}/apple-touch-icon`, (err, res, body) => {
assert.ifError(err);
assert.equal(res.statusCode, 200);
assert(body);
done();
});
});
const { response: res1, body: body1 } = await request.get(`${nconf.get('url')}/apple-touch-icon`);
assert.equal(res1.statusCode, 200);
assert(body1);
});
it('should upload regular file', (done) => {
helpers.uploadFile(`${nconf.get('url')}/api/admin/upload/file`, path.join(__dirname, '../test/files/test.png'), {
it('should upload regular file', async () => {
const { response, body } = await helpers.uploadFile(`${nconf.get('url')}/api/admin/upload/file`, path.join(__dirname, '../test/files/test.png'), {
params: JSON.stringify({
folder: 'system',
}),
}, jar, csrf_token, (err, res, body) => {
assert.ifError(err);
assert.equal(res.statusCode, 200);
}, jar, csrf_token);
assert.equal(response.statusCode, 200);
assert(Array.isArray(body));
assert.equal(body[0].url, '/assets/uploads/system/test.png');
assert(file.existsSync(path.join(nconf.get('upload_path'), 'system', 'test.png')));
done();
});
});
it('should fail to upload regular file in wrong directory', (done) => {
helpers.uploadFile(`${nconf.get('url')}/api/admin/upload/file`, path.join(__dirname, '../test/files/test.png'), {
it('should fail to upload regular file in wrong directory', async () => {
const { response, body } = await helpers.uploadFile(`${nconf.get('url')}/api/admin/upload/file`, path.join(__dirname, '../test/files/test.png'), {
params: JSON.stringify({
folder: '../../system',
}),
}, jar, csrf_token, (err, res, body) => {
assert.ifError(err);
assert.equal(res.statusCode, 500);
}, jar, csrf_token);
assert.equal(response.statusCode, 500);
assert.strictEqual(body.error, '[[error:invalid-path]]');
done();
});
});
describe('ACP uploads screen', () => {
it('should create a folder', async () => {
const res = await helpers.createFolder('', 'myfolder', jar, csrf_token);
assert.strictEqual(res.statusCode, 200);
const { response } = await helpers.createFolder('', 'myfolder', jar, csrf_token);
assert.strictEqual(response.statusCode, 200);
assert(file.existsSync(path.join(nconf.get('upload_path'), 'myfolder')));
});
it('should fail to create a folder if it already exists', async () => {
const res = await helpers.createFolder('', 'myfolder', jar, csrf_token);
assert.strictEqual(res.statusCode, 403);
assert.deepStrictEqual(res.body.status, {
const { response, body } = await helpers.createFolder('', 'myfolder', jar, csrf_token);
assert.strictEqual(response.statusCode, 403);
assert.deepStrictEqual(body.status, {
code: 'forbidden',
message: 'Folder exists',
});
});
it('should fail to create a folder as a non-admin', async () => {
const res = await helpers.createFolder('', 'hisfolder', regularJar, regular_csrf_token);
assert.strictEqual(res.statusCode, 403);
assert.deepStrictEqual(res.body.status, {
const { response, body } = await helpers.createFolder('', 'hisfolder', regularJar, regular_csrf_token);
assert.strictEqual(response.statusCode, 403);
assert.deepStrictEqual(body.status, {
code: 'forbidden',
message: 'You are not authorised to make this call',
});
});
it('should fail to create a folder in wrong directory', async () => {
const res = await helpers.createFolder('../traversing', 'unexpectedfolder', jar, csrf_token);
assert.strictEqual(res.statusCode, 403);
assert.deepStrictEqual(res.body.status, {
const { response, body } = await helpers.createFolder('../traversing', 'unexpectedfolder', jar, csrf_token);
assert.strictEqual(response.statusCode, 403);
assert.deepStrictEqual(body.status, {
code: 'forbidden',
message: 'Invalid path',
});
});
it('should use basename of given folderName to create new folder', async () => {
const res = await helpers.createFolder('/myfolder', '../another folder', jar, csrf_token);
assert.strictEqual(res.statusCode, 200);
const { response } = await helpers.createFolder('/myfolder', '../another folder', jar, csrf_token);
assert.strictEqual(response.statusCode, 200);
const slugifiedName = 'another-folder';
assert(file.existsSync(path.join(nconf.get('upload_path'), 'myfolder', slugifiedName)));
});
it('should fail to delete a file as a non-admin', async () => {
const res = await requestAsync.delete(`${nconf.get('url')}/api/v3/files`, {
const { response, body } = await request.delete(`${nconf.get('url')}/api/v3/files`, {
body: {
path: '/system/test.png',
},
jar: regularJar,
json: true,
headers: {
'x-csrf-token': regular_csrf_token,
},
simple: false,
resolveWithFullResponse: true,
});
assert.strictEqual(res.statusCode, 403);
assert.deepStrictEqual(res.body.status, {
assert.strictEqual(response.statusCode, 403);
assert.deepStrictEqual(body.status, {
code: 'forbidden',
message: 'You are not authorised to make this call',
});

View File

@@ -1,14 +1,12 @@
'use strict';
const assert = require('assert');
const async = require('async');
const fs = require('fs');
const path = require('path');
const nconf = require('nconf');
const validator = require('validator');
const request = require('request');
const requestAsync = require('request-promise-native');
const jwt = require('jsonwebtoken');
const { setTimeout } = require('node:timers/promises');
const db = require('./mocks/databasemock');
const User = require('../src/user');
@@ -24,6 +22,7 @@ const socketUser = require('../src/socket.io/user');
const apiUser = require('../src/api/users');
const utils = require('../src/utils');
const privileges = require('../src/privileges');
const request = require('../src/request');
describe('User', () => {
let userData;
@@ -174,7 +173,7 @@ describe('User', () => {
});
describe('.uniqueUsername()', () => {
it('should deal with collisions', (done) => {
it('should deal with collisions', async () => {
const users = [];
for (let i = 0; i < 10; i += 1) {
users.push({
@@ -182,25 +181,16 @@ describe('User', () => {
email: `jane.doe${i}@example.com`,
});
}
for (const user of users) {
// eslint-disable-next-line no-await-in-loop
await User.create(user);
}
async.series([
function (next) {
async.eachSeries(users, (user, next) => {
User.create(user, next);
}, next);
},
function (next) {
User.uniqueUsername({
const username = await User.uniqueUsername({
username: 'Jane Doe',
userslug: 'jane-doe',
}, (err, username) => {
assert.ifError(err);
assert.strictEqual(username, 'Jane Doe 9');
next();
});
},
], done);
assert.strictEqual(username, 'Jane Doe 9');
});
});
@@ -252,12 +242,10 @@ describe('User', () => {
});
describe('.getModeratorUids()', () => {
before((done) => {
async.series([
async.apply(groups.create, { name: 'testGroup' }),
async.apply(groups.join, 'cid:1:privileges:groups:moderate', 'testGroup'),
async.apply(groups.join, 'testGroup', 1),
], done);
before(async () => {
await groups.create({ name: 'testGroup' });
await groups.join('cid:1:privileges:groups:moderate', 'testGroup');
await groups.join('testGroup', 1);
});
it('should retrieve all users with moderator bit in category privilege', (done) => {
@@ -269,38 +257,13 @@ describe('User', () => {
});
});
after((done) => {
async.series([
async.apply(groups.leave, 'cid:1:privileges:groups:moderate', 'testGroup'),
async.apply(groups.destroy, 'testGroup'),
], done);
after(async () => {
groups.leave('cid:1:privileges:groups:moderate', 'testGroup');
groups.destroy('testGroup');
});
});
describe('.isReadyToPost()', () => {
it('should error when a user makes two posts in quick succession', (done) => {
meta.config = meta.config || {};
meta.config.postDelay = '10';
async.series([
async.apply(Topics.post, {
uid: testUid,
title: 'Topic 1',
content: 'lorem ipsum',
cid: testCid,
}),
async.apply(Topics.post, {
uid: testUid,
title: 'Topic 2',
content: 'lorem ipsum',
cid: testCid,
}),
], (err) => {
assert(err);
done();
});
});
it('should allow a post if the last post time is > 10 seconds', (done) => {
User.setUserField(testUid, 'lastposttime', +new Date() - (11 * 1000), () => {
Topics.post({
@@ -355,13 +318,12 @@ describe('User', () => {
const titles = new Array(10).fill('topic title');
const res = await Promise.allSettled(titles.map(async (title) => {
const { body } = await helpers.request('post', '/api/v3/topics', {
form: {
body: {
cid: testCid,
title: title,
content: 'the content',
},
jar: jar,
json: true,
});
return body.status;
}));
@@ -486,32 +448,19 @@ describe('User', () => {
assert.equal(data.users[0].username, 'ipsearch_filter');
});
it('should sort results by username', (done) => {
async.waterfall([
function (next) {
User.create({ username: 'brian' }, next);
},
function (uid, next) {
User.create({ username: 'baris' }, next);
},
function (uid, next) {
User.create({ username: 'bzari' }, next);
},
function (uid, next) {
User.search({
it('should sort results by username', async () => {
await User.create({ username: 'brian' });
await User.create({ username: 'baris' });
await User.create({ username: 'bzari' });
const data = await User.search({
uid: testUid,
query: 'b',
sortBy: 'username',
paginate: false,
}, next);
},
], (err, data) => {
assert.ifError(err);
});
assert.equal(data.users[0].username, 'baris');
assert.equal(data.users[1].username, 'brian');
assert.equal(data.users[2].username, 'bzari');
done();
});
});
});
@@ -991,10 +940,8 @@ describe('User', () => {
it('should let you set an external image', async () => {
const token = await helpers.getCsrfToken(jar);
const body = await requestAsync(`${nconf.get('url')}/api/v3/users/${uid}/picture`, {
const { body } = await request.put(`${nconf.get('url')}/api/v3/users/${uid}/picture`, {
jar,
method: 'put',
json: true,
headers: {
'x-csrf-token': token,
},
@@ -1193,46 +1140,34 @@ describe('User', () => {
});
});
it('should load profile page', (done) => {
request(`${nconf.get('url')}/api/user/updatedagain`, { jar: jar, json: true }, (err, res, body) => {
assert.ifError(err);
assert.equal(res.statusCode, 200);
it('should load profile page', async () => {
const { response, body } = await request.get(`${nconf.get('url')}/api/user/updatedagain`, { jar });
assert.equal(response.statusCode, 200);
assert(body);
done();
});
});
it('should load settings page', (done) => {
request(`${nconf.get('url')}/api/user/updatedagain/settings`, { jar: jar, json: true }, (err, res, body) => {
assert.ifError(err);
assert.equal(res.statusCode, 200);
it('should load settings page', async () => {
const { response, body } = await request.get(`${nconf.get('url')}/api/user/updatedagain/settings`, { jar });
assert.equal(response.statusCode, 200);
assert(body.settings);
assert(body.languages);
assert(body.homePageRoutes);
done();
});
});
it('should load edit page', (done) => {
request(`${nconf.get('url')}/api/user/updatedagain/edit`, { jar: jar, json: true }, (err, res, body) => {
assert.ifError(err);
assert.equal(res.statusCode, 200);
it('should load edit page', async () => {
const { response, body } = await request.get(`${nconf.get('url')}/api/user/updatedagain/edit`, { jar });
assert.equal(response.statusCode, 200);
assert(body);
done();
});
});
it('should load edit/email page', async () => {
const res = await requestAsync(`${nconf.get('url')}/api/user/updatedagain/edit/email`, { jar: jar, json: true, resolveWithFullResponse: true });
assert.strictEqual(res.statusCode, 200);
assert(res.body);
const { response, body } = await request.get(`${nconf.get('url')}/api/user/updatedagain/edit/email`, { jar });
assert.strictEqual(response.statusCode, 200);
assert(body);
// Accessing this page will mark the user's account as needing an updated email, below code undo's.
await requestAsync({
uri: `${nconf.get('url')}/register/abort`,
await request.post(`${nconf.get('url')}/register/abort`, {
jar,
method: 'POST',
simple: false,
headers: {
'x-csrf-token': csrf_token,
},
@@ -1246,7 +1181,7 @@ describe('User', () => {
});
await groups.join('Test', uid);
const body = await requestAsync(`${nconf.get('url')}/api/user/updatedagain/groups`, { jar: jar, json: true });
const { body } = await request.get(`${nconf.get('url')}/api/user/updatedagain/groups`, { jar });
assert(Array.isArray(body.groups));
assert.equal(body.groups[0].name, 'Test');
@@ -1279,30 +1214,12 @@ describe('User', () => {
assert.equal(data[0].timestamp, now);
});
it('should return the correct ban reason', (done) => {
async.series([
function (next) {
User.bans.ban(testUserUid, 0, '', (err) => {
assert.ifError(err);
next(err);
});
},
function (next) {
User.getModerationHistory(testUserUid, (err, data) => {
assert.ifError(err);
it('should return the correct ban reason', async () => {
await User.bans.ban(testUserUid, 0, '');
const data = await User.getModerationHistory(testUserUid);
assert.equal(data.bans.length, 1, 'one ban');
assert.equal(data.bans[0].reason, '[[user:info.banned-no-reason]]', 'no ban reason');
next(err);
});
},
], (err) => {
assert.ifError(err);
User.bans.unban(testUserUid, (err) => {
assert.ifError(err);
done();
});
});
await User.bans.unban(testUserUid);
});
it('should ban user permanently', (done) => {
@@ -1316,22 +1233,14 @@ describe('User', () => {
});
});
it('should ban user temporarily', (done) => {
User.bans.ban(testUserUid, Date.now() + 2000, (err) => {
assert.ifError(err);
User.bans.isBanned(testUserUid, (err, isBanned) => {
assert.ifError(err);
it('should ban user temporarily', async () => {
await User.bans.ban(testUserUid, Date.now() + 2000);
let isBanned = await User.bans.isBanned(testUserUid);
assert.equal(isBanned, true);
setTimeout(() => {
User.bans.isBanned(testUserUid, (err, isBanned) => {
assert.ifError(err);
await setTimeout(3000);
isBanned = await User.bans.isBanned(testUserUid);
assert.equal(isBanned, false);
User.bans.unban(testUserUid, done);
});
}, 3000);
});
});
await User.bans.unban(testUserUid);
});
it('should error if until is NaN', (done) => {
@@ -1409,26 +1318,19 @@ describe('User', () => {
describe('Digest.getSubscribers', () => {
const uidIndex = {};
before((done) => {
before(async () => {
const testUsers = ['daysub', 'offsub', 'nullsub', 'weeksub'];
async.each(testUsers, (username, next) => {
async.waterfall([
async.apply(User.create, { username: username, email: `${username}@example.com` }),
function (uid, next) {
await Promise.all(testUsers.map(async (username) => {
const uid = await User.create({ username, email: `${username}@example.com` });
if (username === 'nullsub') {
return setImmediate(next);
return;
}
uidIndex[username] = uid;
const sub = username.slice(0, -3);
async.parallel([
async.apply(User.updateDigestSetting, uid, sub),
async.apply(User.setSetting, uid, 'dailyDigestFreq', sub),
], next);
},
], next);
}, done);
await User.updateDigestSetting(uid, sub);
await User.setSetting(uid, 'dailyDigestFreq', sub);
}));
});
it('should accurately build digest list given ACP default "null" (not set)', (done) => {
@@ -1440,71 +1342,38 @@ describe('User', () => {
});
});
it('should accurately build digest list given ACP default "day"', (done) => {
async.series([
async.apply(meta.configs.set, 'dailyDigestFreq', 'day'),
function (next) {
User.digest.getSubscribers('day', (err, subs) => {
assert.ifError(err);
it('should accurately build digest list given ACP default "day"', async () => {
await meta.configs.set('dailyDigestFreq', 'day');
const subs = await User.digest.getSubscribers('day');
assert.strictEqual(subs.includes(uidIndex.daysub.toString()), true); // daysub does get emailed
assert.strictEqual(subs.includes(uidIndex.weeksub.toString()), false); // weeksub does not get emailed
assert.strictEqual(subs.includes(uidIndex.offsub.toString()), false); // offsub doesn't get emailed
next();
});
},
], done);
});
it('should accurately build digest list given ACP default "week"', (done) => {
async.series([
async.apply(meta.configs.set, 'dailyDigestFreq', 'week'),
function (next) {
User.digest.getSubscribers('week', (err, subs) => {
assert.ifError(err);
it('should accurately build digest list given ACP default "week"', async () => {
await meta.configs.set('dailyDigestFreq', 'week');
const subs = await User.digest.getSubscribers('week');
assert.strictEqual(subs.includes(uidIndex.weeksub.toString()), true); // weeksub gets emailed
assert.strictEqual(subs.includes(uidIndex.daysub.toString()), false); // daysub gets emailed
assert.strictEqual(subs.includes(uidIndex.offsub.toString()), false); // offsub does not get emailed
next();
});
},
], done);
});
it('should accurately build digest list given ACP default "off"', (done) => {
async.series([
async.apply(meta.configs.set, 'dailyDigestFreq', 'off'),
function (next) {
User.digest.getSubscribers('day', (err, subs) => {
assert.ifError(err);
it('should accurately build digest list given ACP default "off"', async () => {
await meta.configs.set('dailyDigestFreq', 'off');
const subs = await User.digest.getSubscribers('day');
assert.strictEqual(subs.length, 1);
next();
});
},
], done);
});
});
describe('digests', () => {
let uid;
before((done) => {
async.waterfall([
function (next) {
User.create({ username: 'digestuser', email: 'test@example.com' }, next);
},
function (_uid, next) {
uid = _uid;
User.updateDigestSetting(uid, 'day', next);
},
function (next) {
User.setSetting(uid, 'dailyDigestFreq', 'day', next);
},
function (next) {
User.setSetting(uid, 'notificationType_test', 'notificationemail', next);
},
], done);
before(async () => {
uid = await User.create({ username: 'digestuser', email: 'test@example.com' });
await User.updateDigestSetting(uid, 'day');
await User.setSetting(uid, 'dailyDigestFreq', 'day');
await User.setSetting(uid, 'notificationType_test', 'notificationemail');
});
it('should send digests', async () => {
@@ -1549,106 +1418,65 @@ describe('User', () => {
});
describe('unsubscribe via POST', () => {
it('should unsubscribe from digest if one-click unsubscribe is POSTed', (done) => {
it('should unsubscribe from digest if one-click unsubscribe is POSTed', async () => {
const token = jwt.sign({
template: 'digest',
uid: uid,
}, nconf.get('secret'));
request({
method: 'post',
url: `${nconf.get('url')}/email/unsubscribe/${token}`,
}, (err, res) => {
assert.ifError(err);
assert.strictEqual(res.statusCode, 200);
db.getObjectField(`user:${uid}:settings`, 'dailyDigestFreq', (err, value) => {
assert.ifError(err);
const { response } = await request.post(`${nconf.get('url')}/email/unsubscribe/${token}`);
assert.strictEqual(response.statusCode, 200);
const value = await db.getObjectField(`user:${uid}:settings`, 'dailyDigestFreq');
assert.strictEqual(value, 'off');
done();
});
});
});
it('should unsubscribe from notifications if one-click unsubscribe is POSTed', (done) => {
it('should unsubscribe from notifications if one-click unsubscribe is POSTed', async () => {
const token = jwt.sign({
template: 'notification',
type: 'test',
uid: uid,
}, nconf.get('secret'));
request({
method: 'post',
url: `${nconf.get('url')}/email/unsubscribe/${token}`,
}, (err, res) => {
assert.ifError(err);
assert.strictEqual(res.statusCode, 200);
const { response } = await request.post(`${nconf.get('url')}/email/unsubscribe/${token}`);
assert.strictEqual(response.statusCode, 200);
db.getObjectField(`user:${uid}:settings`, 'notificationType_test', (err, value) => {
assert.ifError(err);
const value = await db.getObjectField(`user:${uid}:settings`, 'notificationType_test');
assert.strictEqual(value, 'notification');
done();
});
});
});
it('should return errors on missing template in token', (done) => {
it('should return errors on missing template in token', async () => {
const token = jwt.sign({
uid: uid,
}, nconf.get('secret'));
request({
method: 'post',
url: `${nconf.get('url')}/email/unsubscribe/${token}`,
}, (err, res) => {
assert.ifError(err);
assert.strictEqual(res.statusCode, 404);
done();
});
const { response } = await request.post(`${nconf.get('url')}/email/unsubscribe/${token}`);
assert.strictEqual(response.statusCode, 404);
});
it('should return errors on wrong template in token', (done) => {
it('should return errors on wrong template in token', async () => {
const token = jwt.sign({
template: 'user',
uid: uid,
}, nconf.get('secret'));
request({
method: 'post',
url: `${nconf.get('url')}/email/unsubscribe/${token}`,
}, (err, res) => {
assert.ifError(err);
assert.strictEqual(res.statusCode, 404);
done();
});
const { response } = await request.post(`${nconf.get('url')}/email/unsubscribe/${token}`);
assert.strictEqual(response.statusCode, 404);
});
it('should return errors on missing token', (done) => {
request({
method: 'post',
url: `${nconf.get('url')}/email/unsubscribe/`,
}, (err, res) => {
assert.ifError(err);
assert.strictEqual(res.statusCode, 404);
done();
});
it('should return errors on missing token', async () => {
const { response } = await request.post(`${nconf.get('url')}/email/unsubscribe/`);
assert.strictEqual(response.statusCode, 404);
});
it('should return errors on token signed with wrong secret (verify-failure)', (done) => {
it('should return errors on token signed with wrong secret (verify-failure)', async () => {
const token = jwt.sign({
template: 'notification',
type: 'test',
uid: uid,
}, `${nconf.get('secret')}aababacaba`);
request({
method: 'post',
url: `${nconf.get('url')}/email/unsubscribe/${token}`,
}, (err, res) => {
assert.ifError(err);
assert.strictEqual(res.statusCode, 403);
done();
});
const { response } = await request.post(`${nconf.get('url')}/email/unsubscribe/${token}`);
assert.strictEqual(response.statusCode, 403);
});
});
});
@@ -1848,36 +1676,17 @@ describe('User', () => {
}
});
it('should set moderation note', (done) => {
let adminUid;
async.waterfall([
function (next) {
User.create({ username: 'noteadmin' }, next);
},
function (_adminUid, next) {
adminUid = _adminUid;
groups.join('administrators', adminUid, next);
},
function (next) {
socketUser.setModerationNote({ uid: adminUid }, { uid: testUid, note: 'this is a test user' }, next);
},
function (next) {
setTimeout(next, 50);
},
function (next) {
socketUser.setModerationNote({ uid: adminUid }, { uid: testUid, note: '<svg/onload=alert(document.location);//' }, next);
},
function (next) {
User.getModerationNotes(testUid, 0, -1, next);
},
], (err, notes) => {
assert.ifError(err);
it('should set moderation note', async () => {
const adminUid = await User.create({ username: 'noteadmin' });
await groups.join('administrators', adminUid);
await socketUser.setModerationNote({ uid: adminUid }, { uid: testUid, note: 'this is a test user' });
await setTimeout(50);
await socketUser.setModerationNote({ uid: adminUid }, { uid: testUid, note: '<svg/onload=alert(document.location);//' });
const notes = await User.getModerationNotes(testUid, 0, -1);
assert.equal(notes[0].note, '&lt;svg&#x2F;onload=alert(document.location);&#x2F;&#x2F;');
assert.equal(notes[0].uid, adminUid);
assert.equal(notes[1].note, 'this is a test user');
assert(notes[0].timestamp);
done();
});
});
it('should get unread count 0 for guest', async () => {
@@ -1974,89 +1783,66 @@ describe('User', () => {
gdpr_consent: true,
});
const { jar } = await helpers.loginUser('admin', '123456');
const { users } = await requestAsync(`${nconf.get('url')}/api/admin/manage/registration`, { jar, json: true });
const { body: { users } } = await request.get(`${nconf.get('url')}/api/admin/manage/registration`, { jar });
assert.equal(users[0].username, 'rejectme');
assert.equal(users[0].email, '&lt;script&gt;alert(&quot;ok&quot;)&lt;script&gt;reject@me.com');
});
it('should fail to add user to queue if username is taken', (done) => {
helpers.registerUser({
it('should fail to add user to queue if username is taken', async () => {
const { body } = await helpers.registerUser({
username: 'rejectme',
password: '123456',
'password-confirm': '123456',
email: '<script>alert("ok")<script>reject@me.com',
gdpr_consent: true,
}, (err, jar, res, body) => {
assert.ifError(err);
assert.equal(body, '[[error:username-taken]]');
done();
});
assert.equal(body, '[[error:username-taken]]');
});
it('should fail to add user to queue if email is taken', (done) => {
helpers.registerUser({
it('should fail to add user to queue if email is taken', async () => {
const { body } = await helpers.registerUser({
username: 'rejectmenew',
password: '123456',
'password-confirm': '123456',
email: '<script>alert("ok")<script>reject@me.com',
gdpr_consent: true,
}, (err, jar, res, body) => {
assert.ifError(err);
});
assert.equal(body, '[[error:email-taken]]');
done();
});
});
it('should reject user registration', (done) => {
socketUser.rejectRegistration({ uid: adminUid }, { username: 'rejectme' }, (err) => {
assert.ifError(err);
User.getRegistrationQueue(0, -1, (err, users) => {
assert.ifError(err);
it('should reject user registration', async () => {
await socketUser.rejectRegistration({ uid: adminUid }, { username: 'rejectme' });
const users = await User.getRegistrationQueue(0, -1);
assert.equal(users.length, 0);
done();
});
});
});
it('should accept user registration', (done) => {
helpers.registerUser({
it('should accept user registration', async () => {
await helpers.registerUser({
username: 'acceptme',
password: '123456',
'password-confirm': '123456',
email: 'accept@me.com',
gdpr_consent: true,
}, (err) => {
assert.ifError(err);
socketUser.acceptRegistration({ uid: adminUid }, { username: 'acceptme' }, (err, uid) => {
assert.ifError(err);
User.exists(uid, (err, exists) => {
assert.ifError(err);
assert(exists);
User.getRegistrationQueue(0, -1, (err, users) => {
assert.ifError(err);
assert.equal(users.length, 0);
done();
});
});
});
});
});
it('should trim username and add user to registration queue', (done) => {
helpers.registerUser({
const uid = await socketUser.acceptRegistration({ uid: adminUid }, { username: 'acceptme' });
const exists = await User.exists(uid);
assert(exists);
const users = await User.getRegistrationQueue(0, -1);
assert.equal(users.length, 0);
});
it('should trim username and add user to registration queue', async () => {
await helpers.registerUser({
username: 'invalidname\r\n',
password: '123456',
'password-confirm': '123456',
email: 'invalidtest@test.com',
gdpr_consent: true,
}, (err) => {
assert.ifError(err);
db.getSortedSetRange('registration:queue', 0, -1, (err, data) => {
assert.ifError(err);
assert.equal(data[0], 'invalidname');
done();
});
});
const users = await db.getSortedSetRange('registration:queue', 0, -1);
assert.equal(users[0], 'invalidname');
});
});
@@ -2104,16 +1890,16 @@ describe('User', () => {
});
it('should error if user does not have invite privilege', async () => {
const { res } = await helpers.invite({ emails: 'invite1@test.com', groupsToJoin: [] }, notAnInviterUid, jar, csrf_token);
assert.strictEqual(res.statusCode, 403);
assert.strictEqual(res.body.status.message, 'You do not have enough privileges for this action.');
const { response, body } = await helpers.invite({ emails: 'invite1@test.com', groupsToJoin: [] }, notAnInviterUid, jar, csrf_token);
assert.strictEqual(response.statusCode, 403);
assert.strictEqual(body.status.message, 'You do not have enough privileges for this action.');
});
it('should error out if user tries to use an inviter\'s uid via the API', async () => {
const { res } = await helpers.invite({ emails: 'invite1@test.com', groupsToJoin: [] }, inviterUid, jar, csrf_token);
const { response, body } = await helpers.invite({ emails: 'invite1@test.com', groupsToJoin: [] }, inviterUid, jar, csrf_token);
const numInvites = await User.getInvitesNumber(inviterUid);
assert.strictEqual(res.statusCode, 403);
assert.strictEqual(res.body.status.message, 'You do not have enough privileges for this action.');
assert.strictEqual(response.statusCode, 403);
assert.strictEqual(body.status.message, 'You do not have enough privileges for this action.');
assert.strictEqual(numInvites, 0);
});
});
@@ -2127,82 +1913,82 @@ describe('User', () => {
});
it('should error with invalid data', async () => {
const { res } = await helpers.invite({}, inviterUid, jar, csrf_token);
assert.strictEqual(res.statusCode, 400);
assert.strictEqual(res.body.status.message, 'Invalid Data');
const { response, body } = await helpers.invite({}, inviterUid, jar, csrf_token);
assert.strictEqual(response.statusCode, 400);
assert.strictEqual(body.status.message, 'Invalid Data');
});
it('should error if user is not admin and type is admin-invite-only', async () => {
meta.config.registrationType = 'admin-invite-only';
const { res } = await helpers.invite({ emails: 'invite1@test.com', groupsToJoin: [] }, inviterUid, jar, csrf_token);
assert.strictEqual(res.statusCode, 403);
assert.strictEqual(res.body.status.message, 'You do not have enough privileges for this action.');
const { response, body } = await helpers.invite({ emails: 'invite1@test.com', groupsToJoin: [] }, inviterUid, jar, csrf_token);
assert.strictEqual(response.statusCode, 403);
assert.strictEqual(body.status.message, 'You do not have enough privileges for this action.');
});
it('should send invitation email (without groups to be joined)', async () => {
meta.config.registrationType = 'normal';
const { res } = await helpers.invite({ emails: 'invite1@test.com', groupsToJoin: [] }, inviterUid, jar, csrf_token);
assert.strictEqual(res.statusCode, 200);
const { response } = await helpers.invite({ emails: 'invite1@test.com', groupsToJoin: [] }, inviterUid, jar, csrf_token);
assert.strictEqual(response.statusCode, 200);
});
it('should send multiple invitation emails (with a public group to be joined)', async () => {
const { res } = await helpers.invite({ emails: 'invite2@test.com,invite3@test.com', groupsToJoin: [PUBLIC_GROUP] }, inviterUid, jar, csrf_token);
assert.strictEqual(res.statusCode, 200);
const { response, body } = await helpers.invite({ emails: 'invite2@test.com,invite3@test.com', groupsToJoin: [PUBLIC_GROUP] }, inviterUid, jar, csrf_token);
assert.strictEqual(response.statusCode, 200);
});
it('should error if the user has not permission to invite to the group', async () => {
const { res } = await helpers.invite({ emails: 'invite4@test.com', groupsToJoin: [PRIVATE_GROUP] }, inviterUid, jar, csrf_token);
assert.strictEqual(res.statusCode, 403);
assert.strictEqual(res.body.status.message, 'You do not have enough privileges for this action.');
const { response, body } = await helpers.invite({ emails: 'invite4@test.com', groupsToJoin: [PRIVATE_GROUP] }, inviterUid, jar, csrf_token);
assert.strictEqual(response.statusCode, 403);
assert.strictEqual(body.status.message, 'You do not have enough privileges for this action.');
});
it('should error if a non-admin tries to invite to the administrators group', async () => {
const { res } = await helpers.invite({ emails: 'invite4@test.com', groupsToJoin: ['administrators'] }, inviterUid, jar, csrf_token);
assert.strictEqual(res.statusCode, 403);
assert.strictEqual(res.body.status.message, 'You do not have enough privileges for this action.');
const { response, body } = await helpers.invite({ emails: 'invite4@test.com', groupsToJoin: ['administrators'] }, inviterUid, jar, csrf_token);
assert.strictEqual(response.statusCode, 403);
assert.strictEqual(body.status.message, 'You do not have enough privileges for this action.');
});
it('should to invite to own private group', async () => {
const { res } = await helpers.invite({ emails: 'invite4@test.com', groupsToJoin: [OWN_PRIVATE_GROUP] }, inviterUid, jar, csrf_token);
assert.strictEqual(res.statusCode, 200);
const { response } = await helpers.invite({ emails: 'invite4@test.com', groupsToJoin: [OWN_PRIVATE_GROUP] }, inviterUid, jar, csrf_token);
assert.strictEqual(response.statusCode, 200);
});
it('should to invite to multiple groups', async () => {
const { res } = await helpers.invite({ emails: 'invite5@test.com', groupsToJoin: [PUBLIC_GROUP, OWN_PRIVATE_GROUP] }, inviterUid, jar, csrf_token);
assert.strictEqual(res.statusCode, 200);
const { response } = await helpers.invite({ emails: 'invite5@test.com', groupsToJoin: [PUBLIC_GROUP, OWN_PRIVATE_GROUP] }, inviterUid, jar, csrf_token);
assert.strictEqual(response.statusCode, 200);
});
it('should error if tries to invite to hidden group', async () => {
const { res } = await helpers.invite({ emails: 'invite6@test.com', groupsToJoin: [HIDDEN_GROUP] }, inviterUid, jar, csrf_token);
assert.strictEqual(res.statusCode, 403);
const { response } = await helpers.invite({ emails: 'invite6@test.com', groupsToJoin: [HIDDEN_GROUP] }, inviterUid, jar, csrf_token);
assert.strictEqual(response.statusCode, 403);
});
it('should error if out of invitations', async () => {
meta.config.maximumInvites = 1;
const { res } = await helpers.invite({ emails: 'invite6@test.com', groupsToJoin: [] }, inviterUid, jar, csrf_token);
assert.strictEqual(res.statusCode, 403);
assert.strictEqual(res.body.status.message, `You have invited the maximum amount of people (${5} out of ${1}).`);
const { response, body } = await helpers.invite({ emails: 'invite6@test.com', groupsToJoin: [] }, inviterUid, jar, csrf_token);
assert.strictEqual(response.statusCode, 403);
assert.strictEqual(body.status.message, `You have invited the maximum amount of people (${5} out of ${1}).`);
meta.config.maximumInvites = 10;
});
it('should send invitation email after maximumInvites increased', async () => {
const { res } = await helpers.invite({ emails: 'invite6@test.com', groupsToJoin: [] }, inviterUid, jar, csrf_token);
assert.strictEqual(res.statusCode, 200);
const { response } = await helpers.invite({ emails: 'invite6@test.com', groupsToJoin: [] }, inviterUid, jar, csrf_token);
assert.strictEqual(response.statusCode, 200);
});
it('should error if invite is sent via API with a different UID', async () => {
const { res } = await helpers.invite({ emails: 'inviter@nodebb.org', groupsToJoin: [] }, adminUid, jar, csrf_token);
const { response, body } = await helpers.invite({ emails: 'inviter@nodebb.org', groupsToJoin: [] }, adminUid, jar, csrf_token);
const numInvites = await User.getInvitesNumber(adminUid);
assert.strictEqual(res.statusCode, 403);
assert.strictEqual(res.body.status.message, 'You do not have enough privileges for this action.');
assert.strictEqual(response.statusCode, 403);
assert.strictEqual(body.status.message, 'You do not have enough privileges for this action.');
assert.strictEqual(numInvites, 0);
});
it('should succeed if email exists but not actually send an invite', async () => {
const { res } = await helpers.invite({ emails: 'inviter@nodebb.org', groupsToJoin: [] }, inviterUid, jar, csrf_token);
const { response } = await helpers.invite({ emails: 'inviter@nodebb.org', groupsToJoin: [] }, inviterUid, jar, csrf_token);
const numInvites = await User.getInvitesNumber(adminUid);
assert.strictEqual(res.statusCode, 200);
assert.strictEqual(response.statusCode, 200);
assert.strictEqual(numInvites, 0);
});
});
@@ -2223,8 +2009,8 @@ describe('User', () => {
});
it('should invite to the administrators group if inviter is an admin', async () => {
const { res } = await helpers.invite({ emails: 'invite99@test.com', groupsToJoin: ['administrators'] }, adminUid, jar, csrf_token);
assert.strictEqual(res.statusCode, 200);
const { response } = await helpers.invite({ emails: 'invite99@test.com', groupsToJoin: ['administrators'] }, adminUid, jar, csrf_token);
assert.strictEqual(response.statusCode, 200);
});
});
@@ -2319,29 +2105,18 @@ describe('User', () => {
const groupsToJoin = [PUBLIC_GROUP, OWN_PRIVATE_GROUP];
const token = await db.get(`invitation:uid:${inviterUid}:invited:${email}`);
await new Promise((resolve, reject) => {
helpers.registerUser({
const { body } = await helpers.registerUser({
username: 'invite5',
password: '123456',
'password-confirm': '123456',
email: email,
gdpr_consent: true,
token: token,
}, async (err, jar, response, body) => {
if (err) {
reject(err);
}
});
const memberships = await groups.isMemberOfGroups(body.uid, groupsToJoin);
const joinedToAll = memberships.filter(Boolean);
if (joinedToAll.length !== groupsToJoin.length) {
reject(new Error('Not joined to the groups'));
}
resolve();
});
});
assert.strictEqual(joinedToAll.length, groupsToJoin.length, 'Not joined to the groups');
});
});
@@ -2354,9 +2129,7 @@ describe('User', () => {
});
it('should show a list of groups for adding to an invite', async () => {
const body = await requestAsync({
url: `${nconf.get('url')}/api/v3/users/${inviterUid}/invites/groups`,
json: true,
const { body } = await helpers.request('get', `/api/v3/users/${inviterUid}/invites/groups`, {
jar,
});
@@ -2366,15 +2139,11 @@ describe('User', () => {
});
it('should error out if you request invite groups for another uid', async () => {
const res = await requestAsync({
url: `${nconf.get('url')}/api/v3/users/${adminUid}/invites/groups`,
json: true,
const { response } = await helpers.request('get', `/api/v3/users/${adminUid}/invites/groups`, {
jar,
simple: false,
resolveWithFullResponse: true,
});
assert.strictEqual(res.statusCode, 403);
assert.strictEqual(response.statusCode, 403);
});
});
});
@@ -2515,8 +2284,8 @@ describe('User', () => {
async function assertPrivacy({ expectVisible, jar, v3Api, emailOnly }) {
const path = v3Api ? `v3/users/${hidingUser.uid}` : `user/${hidingUser.username}`;
const response = await requestAsync(`${nconf.get('url')}/api/${path}`, { json: true, jar });
const { response: userData } = v3Api ? response : { response };
const { body } = await request.get(`${nconf.get('url')}/api/${path}`, { jar });
const userData = v3Api ? body.response : body;
assert.strictEqual(userData.email, expectVisible ? hidingUser.email : '');
if (!emailOnly) {
@@ -2657,24 +2426,19 @@ describe('User', () => {
assert.strictEqual(userData[1].email, '');
});
it('should hide fullname in topic list and topic', (done) => {
Topics.post({
it('should hide fullname in topic list and topic', async () => {
await Topics.post({
uid: hidingUser.uid,
title: 'Topic hidden',
content: 'lorem ipsum',
cid: testCid,
}, (err) => {
assert.ifError(err);
request(`${nconf.get('url')}/api/recent`, { json: true }, (err, res, body) => {
assert.ifError(err);
assert(!body.topics[0].user.hasOwnProperty('fullname'));
request(`${nconf.get('url')}/api/topic/${body.topics[0].slug}`, { json: true }, (err, res, body) => {
assert.ifError(err);
assert(!body.posts[0].user.hasOwnProperty('fullname'));
done();
});
});
});
const { body: body1 } = await request.get(`${nconf.get('url')}/api/recent`);
assert(!body1.topics[0].user.hasOwnProperty('fullname'));
const { body: body2 } = await request.get(`${nconf.get('url')}/api/topic/${body1.topics[0].slug}`);
assert(!body2.posts[0].user.hasOwnProperty('fullname'));
});
});

View File

@@ -141,26 +141,16 @@ describe('email confirmation (library methods)', () => {
describe('email confirmation (v3 api)', () => {
let userObj;
let jar;
const register = data => new Promise((resolve, reject) => {
helpers.registerUser(data, (err, jar, response, body) => {
if (err) {
return reject(err);
}
resolve({ jar, response, body });
});
});
before(async () => {
// If you're running this file directly, uncomment these lines
await register({
await helpers.registerUser({
username: 'fake-user',
password: 'derpioansdosa',
email: 'b@c.com',
gdpr_consent: true,
});
({ body: userObj, jar } = await register({
({ body: userObj, jar } = await helpers.registerUser({
username: 'email-test',
password: 'abcdef',
email: 'test@example.org',
@@ -174,43 +164,40 @@ describe('email confirmation (v3 api)', () => {
});
it('should not list their email', async () => {
const { res, body } = await helpers.request('get', `/api/v3/users/${userObj.uid}/emails`, {
const { response, body } = await helpers.request('get', `/api/v3/users/${userObj.uid}/emails`, {
jar,
json: true,
});
assert.strictEqual(res.statusCode, 200);
assert.strictEqual(response.statusCode, 200);
assert.deepStrictEqual(body, JSON.parse('{"status":{"code":"ok","message":"OK"},"response":{"emails":[]}}'));
});
it('should not allow confirmation if they are not an admin', async () => {
const { res } = await helpers.request('post', `/api/v3/users/${userObj.uid}/emails/${encodeURIComponent('test@example.org')}/confirm`, {
const { response } = await helpers.request('post', `/api/v3/users/${userObj.uid}/emails/${encodeURIComponent('test@example.org')}/confirm`, {
jar,
json: true,
});
assert.strictEqual(res.statusCode, 403);
assert.strictEqual(response.statusCode, 403);
});
it('should not confirm an email that is not pending or set', async () => {
await groups.join('administrators', userObj.uid);
const { res, body } = await helpers.request('post', `/api/v3/users/${userObj.uid}/emails/${encodeURIComponent('fake@example.org')}/confirm`, {
const { response } = await helpers.request('post', `/api/v3/users/${userObj.uid}/emails/${encodeURIComponent('fake@example.org')}/confirm`, {
jar,
json: true,
});
assert.strictEqual(res.statusCode, 404);
assert.strictEqual(response.statusCode, 404);
await groups.leave('administrators', userObj.uid);
});
it('should confirm their email (using the pending validation)', async () => {
await groups.join('administrators', userObj.uid);
const { res, body } = await helpers.request('post', `/api/v3/users/${userObj.uid}/emails/${encodeURIComponent('test@example.org')}/confirm`, {
const { response, body } = await helpers.request('post', `/api/v3/users/${userObj.uid}/emails/${encodeURIComponent('test@example.org')}/confirm`, {
jar,
json: true,
});
assert.strictEqual(res.statusCode, 200);
assert.strictEqual(response.statusCode, 200);
assert.deepStrictEqual(body, JSON.parse('{"status":{"code":"ok","message":"OK"},"response":{}}'));
await groups.leave('administrators', userObj.uid);
});
@@ -221,12 +208,12 @@ describe('email confirmation (v3 api)', () => {
({ jar } = await helpers.loginUser('email-test', 'abcdef')); // email removal logs out everybody
await groups.join('administrators', userObj.uid);
const { res, body } = await helpers.request('post', `/api/v3/users/${userObj.uid}/emails/${encodeURIComponent('test@example.org')}/confirm`, {
const { response, body } = await helpers.request('post', `/api/v3/users/${userObj.uid}/emails/${encodeURIComponent('test@example.org')}/confirm`, {
jar,
json: true,
});
assert.strictEqual(res.statusCode, 200);
assert.strictEqual(response.statusCode, 200);
assert.deepStrictEqual(body, JSON.parse('{"status":{"code":"ok","message":"OK"},"response":{}}'));
await groups.leave('administrators', userObj.uid);
});