mirror of
https://github.com/NodeBB/NodeBB.git
synced 2026-07-07 19:22:09 +02:00
Merge branch 'master' into develop
This commit is contained in:
62
CHANGELOG.md
62
CHANGELOG.md
@@ -1,3 +1,65 @@
|
||||
#### v4.0.6 (2025-02-27)
|
||||
|
||||
##### Chores
|
||||
|
||||
* up dbsearch (9e1a0a13)
|
||||
* add sourceContent to schema (b2cdd5fd)
|
||||
* up harmony (f94cf5ce)
|
||||
* up deps (a9563d75)
|
||||
* up harmony (08014e7c)
|
||||
* up harmony (8385d4ae)
|
||||
* up harmony (0a9d28d5)
|
||||
* up harmony (c920836a)
|
||||
* up esbuild (2ab6a368)
|
||||
* up widgets (02a8d9b6)
|
||||
* up harmony (05bbefd1)
|
||||
* incrementing version number - v4.0.5 (1792a62b)
|
||||
* update changelog for v4.0.5 (f84b9fc7)
|
||||
* incrementing version number - v4.0.4 (b1125cce)
|
||||
* incrementing version number - v4.0.3 (2b65c735)
|
||||
* incrementing version number - v4.0.2 (73fe5fcf)
|
||||
* incrementing version number - v4.0.1 (a461b758)
|
||||
* incrementing version number - v4.0.0 (c1eaee45)
|
||||
|
||||
##### New Features
|
||||
|
||||
* match events and parent style (e38d1531)
|
||||
* merge consecutive share events (9153f8cf)
|
||||
* **config:** add `acpPluginInstallDisabled` option (#13189) (577eee2f)
|
||||
|
||||
##### Bug Fixes
|
||||
|
||||
* add missing file (04d5edbb)
|
||||
* #13202, private note assertion failure when cc property is missing (c65e1ebb)
|
||||
* tests for public and private note assertion, failing test for private note assertion with missing cc prop (efb27ce0)
|
||||
* closes #13206, truncate long usernames (51872d54)
|
||||
* closes #13207, add localComments (13a13e1d)
|
||||
* handle multiple types in remote actor payload (d91b80d2)
|
||||
* don't send validation email for pending emails (07957e82)
|
||||
* missing db (52b23313)
|
||||
* #13198, use email from confirmObj (b4dfc48b)
|
||||
* summary post parse (e9d4c7b9)
|
||||
* add sourceContent to getPostSummaryByPids (ad92e931)
|
||||
* #13193, add lang strings (2c830567)
|
||||
* #13194, dont notify about cid=-1 (b8c8ae09)
|
||||
* old upgrade script so it doesn't create settings objects (fe980688)
|
||||
* escape confirm email in acp manage users (9bfa8853)
|
||||
* add missing await on filter:user.logout (c6b8256f)
|
||||
* relaxing strict allowedTags configuration for incoming AP content (allowing picture, source, and additional attributes for img) (2ad48f17)
|
||||
|
||||
##### Other Changes
|
||||
|
||||
* fix (ec11b0c2)
|
||||
|
||||
##### Refactors
|
||||
|
||||
* use sortedSetRemoveBulk (349084d8)
|
||||
* don't make db request if there is no code (53a2be9d)
|
||||
|
||||
##### Tests
|
||||
|
||||
* remove extra .only, add basic tests for public note assertion (64267f7d)
|
||||
|
||||
#### v4.0.5 (2025-02-20)
|
||||
|
||||
##### Chores
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "nodebb",
|
||||
"license": "GPL-3.0",
|
||||
"description": "NodeBB Forum",
|
||||
"version": "4.0.5",
|
||||
"version": "4.0.6",
|
||||
"homepage": "https://www.nodebb.org",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -55,12 +55,12 @@ Actors.userBySlug = async function (req, res) {
|
||||
Actors.user(req, res);
|
||||
};
|
||||
|
||||
Actors.note = async function (req, res) {
|
||||
Actors.note = async function (req, res, next) {
|
||||
// technically a note isn't an actor, but it is here purely for organizational purposes.
|
||||
// but also, wouldn't it be wild if you could follow a note? lol.
|
||||
const allowed = await privileges.posts.can('topics:read', req.params.pid, activitypub._constants.uid);
|
||||
if (!allowed) {
|
||||
return res.sendStatus(404);
|
||||
return next();
|
||||
}
|
||||
|
||||
// Handle requests for remote content
|
||||
@@ -72,8 +72,8 @@ Actors.note = async function (req, res) {
|
||||
parse: false,
|
||||
extraFields: ['edited'],
|
||||
})).pop();
|
||||
if (!post) {
|
||||
return res.sendStatus(404);
|
||||
if (!post || post.timestamp > Date.now()) {
|
||||
return next();
|
||||
}
|
||||
|
||||
const payload = await activitypub.mocks.notes.public(post);
|
||||
@@ -120,8 +120,12 @@ Actors.topic = async function (req, res, next) {
|
||||
|
||||
const page = parseInt(req.query.page, 10) || undefined;
|
||||
const perPage = meta.config.postsPerPage;
|
||||
const { cid, titleRaw: name, mainPid, slug } = await topics.getTopicFields(req.params.tid, ['cid', 'title', 'mainPid', 'slug']);
|
||||
const { cid, titleRaw: name, mainPid, slug, timestamp } = await topics.getTopicFields(req.params.tid, ['cid', 'title', 'mainPid', 'slug', 'timestamp']);
|
||||
try {
|
||||
if (timestamp > Date.now()) { // Scheduled topic, no response
|
||||
return next();
|
||||
}
|
||||
|
||||
let [collection, pids] = await Promise.all([
|
||||
activitypub.helpers.generateCollection({
|
||||
set: `tid:${req.params.tid}:posts`,
|
||||
|
||||
@@ -308,168 +308,6 @@ describe('ActivityPub integration', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('User Actor endpoint', () => {
|
||||
let uid;
|
||||
let slug;
|
||||
|
||||
beforeEach(async () => {
|
||||
slug = slugify(utils.generateUUID().slice(0, 8));
|
||||
uid = await user.create({ username: slug });
|
||||
});
|
||||
|
||||
it('should return a valid ActivityPub Actor JSON-LD payload', async () => {
|
||||
const { response, body } = await request.get(`${nconf.get('url')}/uid/${uid}`, {
|
||||
headers: {
|
||||
Accept: 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
|
||||
},
|
||||
});
|
||||
|
||||
assert(response);
|
||||
assert.strictEqual(response.statusCode, 200);
|
||||
assert(body.hasOwnProperty('@context'));
|
||||
assert(body['@context'].includes('https://www.w3.org/ns/activitystreams'));
|
||||
|
||||
['id', 'url', 'followers', 'following', 'inbox', 'outbox'].forEach((prop) => {
|
||||
assert(body.hasOwnProperty(prop));
|
||||
assert(body[prop]);
|
||||
});
|
||||
|
||||
assert.strictEqual(body.id, `${nconf.get('url')}/uid/${uid}`);
|
||||
assert.strictEqual(body.type, 'Person');
|
||||
});
|
||||
|
||||
it('should contain a `publicKey` property with a public key', async () => {
|
||||
const { response, body } = await request.get(`${nconf.get('url')}/uid/${uid}`, {
|
||||
headers: {
|
||||
Accept: 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
|
||||
},
|
||||
});
|
||||
|
||||
assert(body.hasOwnProperty('publicKey'));
|
||||
assert(['id', 'owner', 'publicKeyPem'].every(prop => body.publicKey.hasOwnProperty(prop)));
|
||||
});
|
||||
});
|
||||
|
||||
describe('Category Actor endpoint', () => {
|
||||
let cid;
|
||||
let slug;
|
||||
let description;
|
||||
|
||||
beforeEach(async () => {
|
||||
slug = slugify(utils.generateUUID().slice(0, 8));
|
||||
description = utils.generateUUID();
|
||||
({ cid } = await categories.create({
|
||||
name: slug,
|
||||
description,
|
||||
}));
|
||||
});
|
||||
|
||||
it('should return a valid ActivityPub Actor JSON-LD payload', async () => {
|
||||
const { response, body } = await request.get(`${nconf.get('url')}/category/${cid}`, {
|
||||
headers: {
|
||||
Accept: 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
|
||||
},
|
||||
});
|
||||
|
||||
assert(response);
|
||||
assert.strictEqual(response.statusCode, 200);
|
||||
assert(body.hasOwnProperty('@context'));
|
||||
assert(body['@context'].includes('https://www.w3.org/ns/activitystreams'));
|
||||
|
||||
['id', 'url', /* 'followers', 'following', */ 'inbox', 'outbox'].forEach((prop) => {
|
||||
assert(body.hasOwnProperty(prop));
|
||||
assert(body[prop]);
|
||||
});
|
||||
|
||||
assert.strictEqual(body.id, `${nconf.get('url')}/category/${cid}`);
|
||||
assert.strictEqual(body.type, 'Group');
|
||||
assert.strictEqual(body.summary, description);
|
||||
assert.deepStrictEqual(body.icon, {
|
||||
type: 'Image',
|
||||
mediaType: 'image/png',
|
||||
url: `${nconf.get('url')}/assets/uploads/category/category-${cid}-icon.png`,
|
||||
});
|
||||
});
|
||||
|
||||
it('should contain a `publicKey` property with a public key', async () => {
|
||||
const { body } = await request.get(`${nconf.get('url')}/category/${cid}`, {
|
||||
headers: {
|
||||
Accept: 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
|
||||
},
|
||||
});
|
||||
|
||||
assert(body.hasOwnProperty('publicKey'));
|
||||
assert(['id', 'owner', 'publicKeyPem'].every(prop => body.publicKey.hasOwnProperty(prop)));
|
||||
});
|
||||
|
||||
it('should serve the the backgroundImage in `icon` if set', async () => {
|
||||
const payload = {};
|
||||
payload[cid] = {
|
||||
backgroundImage: `/assets/uploads/files/test.png`,
|
||||
};
|
||||
await categories.update(payload);
|
||||
|
||||
const { body } = await request.get(`${nconf.get('url')}/category/${cid}`, {
|
||||
headers: {
|
||||
Accept: 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
|
||||
},
|
||||
});
|
||||
|
||||
assert.deepStrictEqual(body.icon, {
|
||||
type: 'Image',
|
||||
mediaType: 'image/png',
|
||||
url: `${nconf.get('url')}/assets/uploads/files/test.png`,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Instance Actor endpoint', () => {
|
||||
let response;
|
||||
let body;
|
||||
|
||||
before(async () => {
|
||||
({ response, body } = await request.get(`${nconf.get('url')}/actor`, {
|
||||
headers: {
|
||||
Accept: 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
|
||||
},
|
||||
}));
|
||||
});
|
||||
|
||||
it('should respond properly', async () => {
|
||||
assert(response);
|
||||
assert.strictEqual(response.statusCode, 200);
|
||||
});
|
||||
|
||||
it('should return a valid ActivityPub Actor JSON-LD payload', async () => {
|
||||
assert(body.hasOwnProperty('@context'));
|
||||
assert(body['@context'].includes('https://www.w3.org/ns/activitystreams'));
|
||||
|
||||
['id', 'url', 'inbox', 'outbox', 'name', 'preferredUsername'].forEach((prop) => {
|
||||
assert(body.hasOwnProperty(prop));
|
||||
assert(body[prop]);
|
||||
});
|
||||
|
||||
assert.strictEqual(body.id, body.url);
|
||||
assert.strictEqual(body.type, 'Application');
|
||||
assert.strictEqual(body.name, meta.config.site_title || 'NodeBB');
|
||||
assert.strictEqual(body.preferredUsername, nconf.get('url_parsed').hostname);
|
||||
});
|
||||
|
||||
it('should contain a `publicKey` property with a public key', async () => {
|
||||
assert(body.hasOwnProperty('publicKey'));
|
||||
assert(['id', 'owner', 'publicKeyPem'].every(prop => body.publicKey.hasOwnProperty(prop)));
|
||||
});
|
||||
|
||||
it('should also have a valid WebFinger response tied to `preferredUsername`', async () => {
|
||||
const { response, body: body2 } = await request.get(`${nconf.get('url')}/.well-known/webfinger?resource=acct%3a${body.preferredUsername}@${nconf.get('url_parsed').host}`);
|
||||
|
||||
assert.strictEqual(response.statusCode, 200);
|
||||
assert(body2 && body2.aliases && body2.links);
|
||||
assert(body2.aliases.includes(nconf.get('url')));
|
||||
assert(body2.links.some(item => item.rel === 'self' && item.type === 'application/activity+json' && item.href === `${nconf.get('url')}/actor`));
|
||||
});
|
||||
});
|
||||
|
||||
describe('Receipt of ActivityPub events to inboxes (federating IN)', () => {
|
||||
describe('Create', () => {
|
||||
describe('Note', () => {
|
||||
@@ -648,83 +486,6 @@ describe('ActivityPub integration', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('Actor asserton', () => {
|
||||
describe('happy path', () => {
|
||||
let uid;
|
||||
let actorUri;
|
||||
|
||||
before(async () => {
|
||||
uid = utils.generateUUID().slice(0, 8);
|
||||
actorUri = `https://example.org/user/${uid}`;
|
||||
activitypub._cache.set(`0;${actorUri}`, {
|
||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||
id: actorUri,
|
||||
url: actorUri,
|
||||
|
||||
type: 'Person',
|
||||
name: 'example',
|
||||
preferredUsername: 'example',
|
||||
inbox: `https://example.org/user/${uid}/inbox`,
|
||||
outbox: `https://example.org/user/${uid}/outbox`,
|
||||
|
||||
publicKey: {
|
||||
id: `${actorUri}#key`,
|
||||
owner: actorUri,
|
||||
publicKeyPem: 'somekey',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should return true if successfully asserted', async () => {
|
||||
const result = await activitypub.actors.assert([actorUri]);
|
||||
assert(result && result.length);
|
||||
});
|
||||
|
||||
it('should contain a representation of that remote user in the database', async () => {
|
||||
const exists = await db.exists(`userRemote:${actorUri}`);
|
||||
assert(exists);
|
||||
|
||||
const userData = await user.getUserData(actorUri);
|
||||
assert(userData);
|
||||
assert.strictEqual(userData.uid, actorUri);
|
||||
});
|
||||
|
||||
it('should save the actor\'s publicly accessible URL in the hash as well', async () => {
|
||||
const url = await user.getUserField(actorUri, 'url');
|
||||
assert.strictEqual(url, actorUri);
|
||||
});
|
||||
});
|
||||
|
||||
describe('edge case: loopback handles and uris', () => {
|
||||
let uid;
|
||||
const userslug = utils.generateUUID().slice(0, 8);
|
||||
before(async () => {
|
||||
uid = await user.create({ username: userslug });
|
||||
});
|
||||
|
||||
it('should return true but not actually assert the handle into the database', async () => {
|
||||
const handle = `${userslug}@${nconf.get('url_parsed').host}`;
|
||||
const result = await activitypub.actors.assert([handle]);
|
||||
assert(result);
|
||||
|
||||
const handleExists = await db.isObjectField('handle:uid', handle);
|
||||
assert.strictEqual(handleExists, false);
|
||||
|
||||
const userRemoteHashExists = await db.exists(`userRemote:${nconf.get('url')}/uid/${uid}`);
|
||||
assert.strictEqual(userRemoteHashExists, false);
|
||||
});
|
||||
|
||||
it('should return true but not actually assert the uri into the database', async () => {
|
||||
const uri = `${nconf.get('url')}/uid/${uid}`;
|
||||
const result = await activitypub.actors.assert([uri]);
|
||||
assert(result);
|
||||
|
||||
const userRemoteHashExists = await db.exists(`userRemote:${uri}`);
|
||||
assert.strictEqual(userRemoteHashExists, false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('ActivityPub', async () => {
|
||||
let files;
|
||||
|
||||
|
||||
395
test/activitypub/actors.js
Normal file
395
test/activitypub/actors.js
Normal file
@@ -0,0 +1,395 @@
|
||||
'use strict';
|
||||
|
||||
const assert = require('assert');
|
||||
const nconf = require('nconf');
|
||||
|
||||
const db = require('../mocks/databasemock');
|
||||
const meta = require('../../src/meta');
|
||||
const categories = require('../../src/categories');
|
||||
const user = require('../../src/user');
|
||||
const topics = require('../../src/topics');
|
||||
const activitypub = require('../../src/activitypub');
|
||||
const utils = require('../../src/utils');
|
||||
const request = require('../../src/request');
|
||||
const slugify = require('../../src/slugify');
|
||||
|
||||
describe('Actor asserton', () => {
|
||||
describe('happy path', () => {
|
||||
let uid;
|
||||
let actorUri;
|
||||
|
||||
before(async () => {
|
||||
uid = utils.generateUUID().slice(0, 8);
|
||||
actorUri = `https://example.org/user/${uid}`;
|
||||
activitypub._cache.set(`0;${actorUri}`, {
|
||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||
id: actorUri,
|
||||
url: actorUri,
|
||||
|
||||
type: 'Person',
|
||||
name: 'example',
|
||||
preferredUsername: 'example',
|
||||
inbox: `https://example.org/user/${uid}/inbox`,
|
||||
outbox: `https://example.org/user/${uid}/outbox`,
|
||||
|
||||
publicKey: {
|
||||
id: `${actorUri}#key`,
|
||||
owner: actorUri,
|
||||
publicKeyPem: 'somekey',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should return true if successfully asserted', async () => {
|
||||
const result = await activitypub.actors.assert([actorUri]);
|
||||
assert(result && result.length);
|
||||
});
|
||||
|
||||
it('should contain a representation of that remote user in the database', async () => {
|
||||
const exists = await db.exists(`userRemote:${actorUri}`);
|
||||
assert(exists);
|
||||
|
||||
const userData = await user.getUserData(actorUri);
|
||||
assert(userData);
|
||||
assert.strictEqual(userData.uid, actorUri);
|
||||
});
|
||||
|
||||
it('should save the actor\'s publicly accessible URL in the hash as well', async () => {
|
||||
const url = await user.getUserField(actorUri, 'url');
|
||||
assert.strictEqual(url, actorUri);
|
||||
});
|
||||
});
|
||||
|
||||
describe('edge case: loopback handles and uris', () => {
|
||||
let uid;
|
||||
const userslug = utils.generateUUID().slice(0, 8);
|
||||
before(async () => {
|
||||
uid = await user.create({ username: userslug });
|
||||
});
|
||||
|
||||
it('should return true but not actually assert the handle into the database', async () => {
|
||||
const handle = `${userslug}@${nconf.get('url_parsed').host}`;
|
||||
const result = await activitypub.actors.assert([handle]);
|
||||
assert(result);
|
||||
|
||||
const handleExists = await db.isObjectField('handle:uid', handle);
|
||||
assert.strictEqual(handleExists, false);
|
||||
|
||||
const userRemoteHashExists = await db.exists(`userRemote:${nconf.get('url')}/uid/${uid}`);
|
||||
assert.strictEqual(userRemoteHashExists, false);
|
||||
});
|
||||
|
||||
it('should return true but not actually assert the uri into the database', async () => {
|
||||
const uri = `${nconf.get('url')}/uid/${uid}`;
|
||||
const result = await activitypub.actors.assert([uri]);
|
||||
assert(result);
|
||||
|
||||
const userRemoteHashExists = await db.exists(`userRemote:${uri}`);
|
||||
assert.strictEqual(userRemoteHashExists, false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Controllers', () => {
|
||||
describe('User Actor endpoint', () => {
|
||||
let uid;
|
||||
let slug;
|
||||
|
||||
beforeEach(async () => {
|
||||
slug = slugify(utils.generateUUID().slice(0, 8));
|
||||
uid = await user.create({ username: slug });
|
||||
});
|
||||
|
||||
it('should return a valid ActivityPub Actor JSON-LD payload', async () => {
|
||||
const { response, body } = await request.get(`${nconf.get('url')}/uid/${uid}`, {
|
||||
headers: {
|
||||
Accept: 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
|
||||
},
|
||||
});
|
||||
|
||||
assert(response);
|
||||
assert.strictEqual(response.statusCode, 200);
|
||||
assert(body.hasOwnProperty('@context'));
|
||||
assert(body['@context'].includes('https://www.w3.org/ns/activitystreams'));
|
||||
|
||||
['id', 'url', 'followers', 'following', 'inbox', 'outbox'].forEach((prop) => {
|
||||
assert(body.hasOwnProperty(prop));
|
||||
assert(body[prop]);
|
||||
});
|
||||
|
||||
assert.strictEqual(body.id, `${nconf.get('url')}/uid/${uid}`);
|
||||
assert.strictEqual(body.type, 'Person');
|
||||
});
|
||||
|
||||
it('should contain a `publicKey` property with a public key', async () => {
|
||||
const { response, body } = await request.get(`${nconf.get('url')}/uid/${uid}`, {
|
||||
headers: {
|
||||
Accept: 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
|
||||
},
|
||||
});
|
||||
|
||||
assert(body.hasOwnProperty('publicKey'));
|
||||
assert(['id', 'owner', 'publicKeyPem'].every(prop => body.publicKey.hasOwnProperty(prop)));
|
||||
});
|
||||
});
|
||||
|
||||
describe('Category Actor endpoint', () => {
|
||||
let cid;
|
||||
let slug;
|
||||
let description;
|
||||
|
||||
beforeEach(async () => {
|
||||
slug = slugify(utils.generateUUID().slice(0, 8));
|
||||
description = utils.generateUUID();
|
||||
({ cid } = await categories.create({
|
||||
name: slug,
|
||||
description,
|
||||
}));
|
||||
});
|
||||
|
||||
it('should return a valid ActivityPub Actor JSON-LD payload', async () => {
|
||||
const { response, body } = await request.get(`${nconf.get('url')}/category/${cid}`, {
|
||||
headers: {
|
||||
Accept: 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
|
||||
},
|
||||
});
|
||||
|
||||
assert(response);
|
||||
assert.strictEqual(response.statusCode, 200);
|
||||
assert(body.hasOwnProperty('@context'));
|
||||
assert(body['@context'].includes('https://www.w3.org/ns/activitystreams'));
|
||||
|
||||
['id', 'url', /* 'followers', 'following', */ 'inbox', 'outbox'].forEach((prop) => {
|
||||
assert(body.hasOwnProperty(prop));
|
||||
assert(body[prop]);
|
||||
});
|
||||
|
||||
assert.strictEqual(body.id, `${nconf.get('url')}/category/${cid}`);
|
||||
assert.strictEqual(body.type, 'Group');
|
||||
assert.strictEqual(body.summary, description);
|
||||
assert.deepStrictEqual(body.icon, {
|
||||
type: 'Image',
|
||||
mediaType: 'image/png',
|
||||
url: `${nconf.get('url')}/assets/uploads/category/category-${cid}-icon.png`,
|
||||
});
|
||||
});
|
||||
|
||||
it('should contain a `publicKey` property with a public key', async () => {
|
||||
const { body } = await request.get(`${nconf.get('url')}/category/${cid}`, {
|
||||
headers: {
|
||||
Accept: 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
|
||||
},
|
||||
});
|
||||
|
||||
assert(body.hasOwnProperty('publicKey'));
|
||||
assert(['id', 'owner', 'publicKeyPem'].every(prop => body.publicKey.hasOwnProperty(prop)));
|
||||
});
|
||||
|
||||
it('should serve the the backgroundImage in `icon` if set', async () => {
|
||||
const payload = {};
|
||||
payload[cid] = {
|
||||
backgroundImage: `/assets/uploads/files/test.png`,
|
||||
};
|
||||
await categories.update(payload);
|
||||
|
||||
const { body } = await request.get(`${nconf.get('url')}/category/${cid}`, {
|
||||
headers: {
|
||||
Accept: 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
|
||||
},
|
||||
});
|
||||
|
||||
assert.deepStrictEqual(body.icon, {
|
||||
type: 'Image',
|
||||
mediaType: 'image/png',
|
||||
url: `${nconf.get('url')}/assets/uploads/files/test.png`,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Instance Actor endpoint', () => {
|
||||
let response;
|
||||
let body;
|
||||
|
||||
before(async () => {
|
||||
({ response, body } = await request.get(`${nconf.get('url')}/actor`, {
|
||||
headers: {
|
||||
Accept: 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
|
||||
},
|
||||
}));
|
||||
});
|
||||
|
||||
it('should respond properly', async () => {
|
||||
assert(response);
|
||||
assert.strictEqual(response.statusCode, 200);
|
||||
});
|
||||
|
||||
it('should return a valid ActivityPub Actor JSON-LD payload', async () => {
|
||||
assert(body.hasOwnProperty('@context'));
|
||||
assert(body['@context'].includes('https://www.w3.org/ns/activitystreams'));
|
||||
|
||||
['id', 'url', 'inbox', 'outbox', 'name', 'preferredUsername'].forEach((prop) => {
|
||||
assert(body.hasOwnProperty(prop));
|
||||
assert(body[prop]);
|
||||
});
|
||||
|
||||
assert.strictEqual(body.id, body.url);
|
||||
assert.strictEqual(body.type, 'Application');
|
||||
assert.strictEqual(body.name, meta.config.site_title || 'NodeBB');
|
||||
assert.strictEqual(body.preferredUsername, nconf.get('url_parsed').hostname);
|
||||
});
|
||||
|
||||
it('should contain a `publicKey` property with a public key', async () => {
|
||||
assert(body.hasOwnProperty('publicKey'));
|
||||
assert(['id', 'owner', 'publicKeyPem'].every(prop => body.publicKey.hasOwnProperty(prop)));
|
||||
});
|
||||
|
||||
it('should also have a valid WebFinger response tied to `preferredUsername`', async () => {
|
||||
const { response, body: body2 } = await request.get(`${nconf.get('url')}/.well-known/webfinger?resource=acct%3a${body.preferredUsername}@${nconf.get('url_parsed').host}`);
|
||||
|
||||
assert.strictEqual(response.statusCode, 200);
|
||||
assert(body2 && body2.aliases && body2.links);
|
||||
assert(body2.aliases.includes(nconf.get('url')));
|
||||
assert(body2.links.some(item => item.rel === 'self' && item.type === 'application/activity+json' && item.href === `${nconf.get('url')}/actor`));
|
||||
});
|
||||
});
|
||||
|
||||
describe('Topic Collection endpoint', () => {
|
||||
let cid;
|
||||
let uid;
|
||||
|
||||
before(async () => {
|
||||
({ cid } = await categories.create({ name: utils.generateUUID().slice(0, 8) }));
|
||||
const slug = slugify(utils.generateUUID().slice(0, 8));
|
||||
uid = await user.create({ username: slug });
|
||||
});
|
||||
|
||||
describe('Live', () => {
|
||||
let topicData;
|
||||
let response;
|
||||
let body;
|
||||
|
||||
before(async () => {
|
||||
({ topicData } = await topics.post({
|
||||
uid,
|
||||
cid,
|
||||
title: 'Lorem "Lipsum" Ipsum',
|
||||
content: 'Lorem ipsum dolor sit amet',
|
||||
}));
|
||||
|
||||
({ response, body } = await request.get(`${nconf.get('url')}/topic/${topicData.slug}`, {
|
||||
headers: {
|
||||
Accept: 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
|
||||
},
|
||||
}));
|
||||
});
|
||||
|
||||
it('should respond properly', async () => {
|
||||
assert(response);
|
||||
assert.strictEqual(response.statusCode, 200);
|
||||
});
|
||||
|
||||
it('should return an OrderedCollection with one item', () => {
|
||||
assert.strictEqual(body.type, 'OrderedCollection');
|
||||
assert.strictEqual(body.totalItems, 1);
|
||||
assert(Array.isArray(body.orderedItems));
|
||||
assert.strictEqual(body.orderedItems[0], `${nconf.get('url')}/post/${topicData.mainPid}`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Scheduled', () => {
|
||||
let topicData;
|
||||
let response;
|
||||
let body;
|
||||
|
||||
before(async () => {
|
||||
({ topicData } = await topics.post({
|
||||
uid,
|
||||
cid,
|
||||
title: 'Lorem "Lipsum" Ipsum',
|
||||
content: 'Lorem ipsum dolor sit amet',
|
||||
timestamp: Date.now() + (1000 * 60 * 60), // 1 hour in the future
|
||||
}));
|
||||
|
||||
({ response, body } = await request.get(`${nconf.get('url')}/topic/${topicData.slug}`, {
|
||||
headers: {
|
||||
Accept: 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
|
||||
},
|
||||
}));
|
||||
});
|
||||
|
||||
it('should respond with a 404 Not Found', async () => {
|
||||
assert(response);
|
||||
assert.strictEqual(response.statusCode, 404);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Post Object endpoint', () => {
|
||||
let cid;
|
||||
let uid;
|
||||
|
||||
before(async () => {
|
||||
({ cid } = await categories.create({ name: utils.generateUUID().slice(0, 8) }));
|
||||
const slug = slugify(utils.generateUUID().slice(0, 8));
|
||||
uid = await user.create({ username: slug });
|
||||
});
|
||||
|
||||
describe('Live', () => {
|
||||
let postData;
|
||||
let response;
|
||||
let body;
|
||||
|
||||
before(async () => {
|
||||
({ postData } = await topics.post({
|
||||
uid,
|
||||
cid,
|
||||
title: 'Lorem "Lipsum" Ipsum',
|
||||
content: 'Lorem ipsum dolor sit amet',
|
||||
}));
|
||||
|
||||
({ response, body } = await request.get(`${nconf.get('url')}/post/${postData.pid}`, {
|
||||
headers: {
|
||||
Accept: 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
|
||||
},
|
||||
}));
|
||||
});
|
||||
|
||||
it('should respond properly', async () => {
|
||||
assert(response);
|
||||
assert.strictEqual(response.statusCode, 200);
|
||||
});
|
||||
|
||||
it('should return a Note type object', () => {
|
||||
assert.strictEqual(body.type, 'Note');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Scheduled', () => {
|
||||
let topicData;
|
||||
let postData;
|
||||
let response;
|
||||
let body;
|
||||
|
||||
before(async () => {
|
||||
({ topicData, postData } = await topics.post({
|
||||
uid,
|
||||
cid,
|
||||
title: 'Lorem "Lipsum" Ipsum',
|
||||
content: 'Lorem ipsum dolor sit amet',
|
||||
timestamp: Date.now() + (1000 * 60 * 60), // 1 hour in the future
|
||||
}));
|
||||
|
||||
({ response, body } = await request.get(`${nconf.get('url')}/post/${postData.pid}`, {
|
||||
headers: {
|
||||
Accept: 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
|
||||
},
|
||||
}));
|
||||
});
|
||||
|
||||
it('should respond with a 404 Not Found', async () => {
|
||||
assert(response);
|
||||
assert.strictEqual(response.statusCode, 404);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user