You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
nodebb/test/topicThumbs.js

371 lines
12 KiB
JavaScript

'use strict';
const fs = require('fs');
const path = require('path');
const assert = require('assert');
const nconf = require('nconf');
const request = require('request-promise-native');
const db = require('./mocks/databasemock');
const meta = require('../src/meta');
const user = require('../src/user');
const groups = require('../src/groups');
const topics = require('../src/topics');
const posts = require('../src/posts');
const categories = require('../src/categories');
const plugins = require('../src/plugins');
const file = require('../src/file');
const utils = require('../src/utils');
const helpers = require('./helpers');
describe('Topic thumbs', () => {
let topicObj;
let categoryObj;
let adminUid;
let adminJar;
let adminCSRF;
let fooJar;
let fooCSRF;
let fooUid;
const thumbPaths = [
`${nconf.get('upload_path')}/files/test.png`,
`${nconf.get('upload_path')}/files/test2.png`,
'https://example.org',
];
const relativeThumbPaths = thumbPaths.map(path => path.replace(nconf.get('upload_path'), ''));
const uuid = utils.generateUUID();
function createFiles() {
fs.closeSync(fs.openSync(path.resolve(__dirname, './uploads', thumbPaths[0]), 'w'));
fs.closeSync(fs.openSync(path.resolve(__dirname, './uploads', thumbPaths[1]), 'w'));
}
before(async () => {
meta.config.allowTopicsThumbnail = 1;
adminUid = await user.create({ username: 'admin', password: '123456' });
fooUid = await user.create({ username: 'foo', password: '123456' });
await groups.join('administrators', adminUid);
({ adminJar, adminCSRF } = await new Promise((resolve, reject) => {
helpers.loginUser('admin', '123456', (err, adminJar, adminCSRF) => {
if (err) {
return reject(err);
}
resolve({ adminJar, adminCSRF });
});
}));
({ fooJar, fooCSRF } = await new Promise((resolve, reject) => {
helpers.loginUser('foo', '123456', (err, fooJar, fooCSRF) => {
if (err) {
return reject(err);
}
resolve({ fooJar, fooCSRF });
});
}));
categoryObj = await categories.create({
name: 'Test Category',
description: 'Test category created by testing script',
});
topicObj = await topics.post({
uid: adminUid,
cid: categoryObj.cid,
title: 'Test Topic Title',
content: 'The content of test topic',
});
// Touch a couple files and associate it to a topic
createFiles();
await db.sortedSetAdd(`topic:${topicObj.topicData.tid}:thumbs`, 0, `${relativeThumbPaths[0]}`);
});
it('should return bool for whether a thumb exists', async () => {
const exists = await topics.thumbs.exists(topicObj.topicData.tid, `${relativeThumbPaths[0]}`);
assert.strictEqual(exists, true);
});
describe('.get()', () => {
it('should return an array of thumbs', async () => {
require('../src/cache').del(`topic:${topicObj.topicData.tid}:thumbs`);
const thumbs = await topics.thumbs.get(topicObj.topicData.tid);
assert.deepStrictEqual(thumbs, [{
id: 1,
name: 'test.png',
url: `${nconf.get('relative_path')}${nconf.get('upload_url')}${relativeThumbPaths[0]}`,
}]);
});
it('should return an array of an array of thumbs if multiple tids are passed in', async () => {
const thumbs = await topics.thumbs.get([topicObj.topicData.tid, topicObj.topicData.tid + 1]);
assert.deepStrictEqual(thumbs, [
[{
id: 1,
name: 'test.png',
url: `${nconf.get('relative_path')}${nconf.get('upload_url')}${relativeThumbPaths[0]}`,
}],
[],
]);
});
});
describe('.associate()', () => {
let tid;
let mainPid;
before(async () => {
topicObj = await topics.post({
uid: adminUid,
cid: categoryObj.cid,
title: 'Test Topic Title',
content: 'The content of test topic',
});
tid = topicObj.topicData.tid;
mainPid = topicObj.postData.pid;
});
it('should add an uploaded file to a zset', async () => {
await topics.thumbs.associate({
id: tid,
path: relativeThumbPaths[0],
});
const exists = await db.isSortedSetMember(`topic:2:thumbs`, relativeThumbPaths[0]);
assert(exists);
});
it('should also work with UUIDs', async () => {
await topics.thumbs.associate({
id: uuid,
path: relativeThumbPaths[1],
score: 5,
});
const exists = await db.isSortedSetMember(`draft:${uuid}:thumbs`, relativeThumbPaths[1]);
assert(exists);
});
it('should also work with a URL', async () => {
await topics.thumbs.associate({
id: tid,
path: relativeThumbPaths[2],
});
const exists = await db.isSortedSetMember(`topic:2:thumbs`, relativeThumbPaths[2]);
assert(exists);
});
it('should have a score equal to the number of thumbs prior to addition', async () => {
const scores = await db.sortedSetScores('topic:2:thumbs', [relativeThumbPaths[0], relativeThumbPaths[2]]);
assert.deepStrictEqual(scores, [0, 1]);
});
it('should update the relevant topic hash with the number of thumbnails', async () => {
const numThumbs = await topics.getTopicField(2, 'numThumbs');
assert.strictEqual(parseInt(numThumbs, 10), 2);
});
it('should successfully associate a thumb with a topic even if it already contains that thumbnail (updates score)', async () => {
await topics.thumbs.associate({
id: tid,
path: relativeThumbPaths[0],
});
const score = await db.sortedSetScore(`topic:2:thumbs`, relativeThumbPaths[0]);
assert(isFinite(score)); // exists in set
assert.strictEqual(score, 2);
});
it('should update the score to be passed in as the third argument', async () => {
await topics.thumbs.associate({
id: tid,
path: relativeThumbPaths[0],
score: 0,
});
const score = await db.sortedSetScore(`topic:2:thumbs`, relativeThumbPaths[0]);
assert(isFinite(score)); // exists in set
assert.strictEqual(score, 0);
});
it('should associate the thumbnail with that topic\'s main pid\'s uploads', async () => {
const uploads = await posts.uploads.list(mainPid);
assert(uploads.includes(path.basename(relativeThumbPaths[0])));
});
it('should maintain state in the topic\'s main pid\'s uploads if posts.uploads.sync() is called', async () => {
await posts.uploads.sync(mainPid);
const uploads = await posts.uploads.list(mainPid);
assert(uploads.includes(path.basename(relativeThumbPaths[0])));
});
});
describe('.migrate()', () => {
it('should combine the thumbs uploaded to a UUID zset and combine it with a topic\'s thumb zset', async () => {
await topics.thumbs.migrate(uuid, 2);
const thumbs = await topics.thumbs.get(2);
assert.strictEqual(thumbs.length, 3);
assert.deepStrictEqual(thumbs, [
{
id: 2,
name: 'test.png',
url: `${nconf.get('relative_path')}${nconf.get('upload_url')}${relativeThumbPaths[0]}`,
},
{
id: 2,
name: 'example.org',
url: 'https://example.org',
},
{
id: 2,
name: 'test2.png',
url: `${nconf.get('relative_path')}${nconf.get('upload_url')}${relativeThumbPaths[1]}`,
},
]);
});
});
describe(`.delete()`, () => {
it('should remove a file from sorted set AND disk', async () => {
await topics.thumbs.associate({
id: 1,
path: thumbPaths[0],
});
await topics.thumbs.delete(1, relativeThumbPaths[0]);
assert.strictEqual(await db.isSortedSetMember('topic:1:thumbs', relativeThumbPaths[0]), false);
assert.strictEqual(await file.exists(thumbPaths[0]), false);
});
it('should no longer be associated with that topic\'s main pid\'s uploads', async () => {
const mainPid = (await topics.getMainPids([1]))[0];
const uploads = await posts.uploads.list(mainPid);
assert(!uploads.includes(path.basename(relativeThumbPaths[0])));
});
it('should also work with UUIDs', async () => {
await topics.thumbs.associate({
id: uuid,
path: thumbPaths[1],
});
await topics.thumbs.delete(uuid, relativeThumbPaths[1]);
assert.strictEqual(await db.isSortedSetMember(`draft:${uuid}:thumbs`, relativeThumbPaths[1]), false);
assert.strictEqual(await file.exists(thumbPaths[1]), false);
});
it('should also work with URLs', async () => {
await topics.thumbs.associate({
id: uuid,
path: thumbPaths[2],
});
await topics.thumbs.delete(uuid, relativeThumbPaths[2]);
assert.strictEqual(await db.isSortedSetMember(`draft:${uuid}:thumbs`, relativeThumbPaths[2]), false);
});
it('should not delete the file from disk if not associated with the tid', async () => {
createFiles();
await topics.thumbs.delete(uuid, thumbPaths[0]);
assert.strictEqual(await file.exists(thumbPaths[0]), true);
});
});
describe('HTTP calls to topic thumb routes', () => {
before(() => {
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 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 uploader plugins', async () => {
const hookMethod = async () => ({
name: 'test.png',
url: 'https://example.org',
});
await plugins.hooks.register('test', {
hook: 'filter:uploadFile',
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();
});
});
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/3/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', (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 calling user cannot edit the tid', (done) => {
helpers.uploadFile(`${nconf.get('url')}/api/v3/topics/1/thumbs`, path.join(__dirname, './files/test.png'), {}, fooJar, fooCSRF, (err, res, body) => {
assert.ifError(err);
assert.strictEqual(res.statusCode, 403);
done();
});
});
it('should fail if thumbnails are not enabled', (done) => {
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);
assert(body && body.status);
assert.strictEqual(body.status.message, 'Topic thumbnails are disabled.');
done();
});
});
it('should fail if file is not image', (done) => {
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);
assert(body && body.status);
assert.strictEqual(body.status.message, 'Invalid File');
done();
});
});
});
});