Files
NodeBB/test/activitypub/notes.js

321 lines
9.6 KiB
JavaScript

'use strict';
const assert = require('assert');
const nconf = require('nconf');
const db = require('../mocks/databasemock');
const meta = require('../../src/meta');
const install = require('../../src/install');
const user = require('../../src/user');
const categories = require('../../src/categories');
const posts = require('../../src/posts');
const topics = require('../../src/topics');
const api = require('../../src/api');
const activitypub = require('../../src/activitypub');
const utils = require('../../src/utils');
const helpers = require('./helpers');
describe('Notes', () => {
describe('Assertion', () => {
before(async () => {
meta.config.activitypubEnabled = 1;
await install.giveWorldPrivileges();
});
describe('Public objects', () => {
it('should pull a remote root-level object by its id and create a new topic', async () => {
const { id } = helpers.mocks.note();
const assertion = await activitypub.notes.assert(0, id, { skipChecks: true });
assert(assertion);
const { tid, count } = assertion;
assert(tid);
assert.strictEqual(count, 1);
const exists = await topics.exists(tid);
assert(exists);
});
it('should assert if the cc property is missing', async () => {
const { id } = helpers.mocks.note({ cc: 'remove' });
const assertion = await activitypub.notes.assert(0, id, { skipChecks: true });
assert(assertion);
const { tid, count } = assertion;
assert(tid);
assert.strictEqual(count, 1);
const exists = await topics.exists(tid);
assert(exists);
});
it('should assert if the object is of type Video', async () => {
const { id } = helpers.mocks.note({
type: 'Video',
});
const assertion = await activitypub.notes.assert(0, id, { skipChecks: true });
assert(assertion);
const { tid, count } = assertion;
assert(tid);
assert.strictEqual(count, 1);
const exists = await topics.exists(tid);
assert(exists);
});
describe('Category-specific behaviours', () => {
it('should slot newly created topic in local category if addressed', async () => {
const { cid } = await categories.create({ name: utils.generateUUID() });
const { id } = helpers.mocks.note({
cc: [`${nconf.get('url')}/category/${cid}`],
});
const assertion = await activitypub.notes.assert(0, id);
assert(assertion);
const { tid, count } = assertion;
assert(tid);
assert.strictEqual(count, 1);
const topic = await topics.getTopicData(tid);
assert.strictEqual(topic.cid, cid);
});
it('should slot newly created topic in remote category if addressed', async () => {
const { id: cid, actor } = helpers.mocks.group();
activitypub._cache.set(`0;${cid}`, actor);
await activitypub.actors.assertGroup([cid]);
const { id } = helpers.mocks.note({
cc: [cid],
});
const assertion = await activitypub.notes.assert(0, id);
assert(assertion);
const { tid, count } = assertion;
assert(tid);
assert.strictEqual(count, 1);
const topic = await topics.getTopicData(tid);
assert.strictEqual(topic.cid, cid);
const tids = await db.getSortedSetMembers(`cid:${cid}:tids`);
assert(tids.includes(tid));
const category = await categories.getCategoryData(cid);
['topic_count', 'post_count', 'totalPostCount', 'totalTopicCount'].forEach((prop) => {
assert.strictEqual(category[prop], 1);
});
});
});
describe('User-specific behaviours', () => {
let remoteCid;
let uid;
before(async () => {
// Remote
const { id, actor } = helpers.mocks.group();
remoteCid = id;
activitypub._cache.set(`0;${id}`, actor);
await activitypub.actors.assertGroup([id]);
// User
uid = await user.create({ username: utils.generateUUID() });
await topics.markAllRead(uid);
});
it('should not show up in my unread if it is in cid -1', async () => {
const { id } = helpers.mocks.note();
const assertion = await activitypub.notes.assert(0, id, { skipChecks: 1 });
assert(assertion);
const unread = await topics.getTotalUnread(uid);
assert.strictEqual(unread, 0);
});
it('should show up in my recent/unread if I am tracking the remote category', async () => {
await api.categories.setWatchState({ uid }, {
cid: remoteCid,
state: categories.watchStates.tracking,
uid,
});
const { id } = helpers.mocks.note({
cc: [remoteCid],
});
const assertion = await activitypub.notes.assert(0, id);
assert(assertion);
const unread = await topics.getTotalUnread(uid);
assert.strictEqual(unread, 1);
await topics.markAllRead(uid);
});
it('should show up in recent/unread and notify me if I am watching the remote category', async () => {
await api.categories.setWatchState({ uid }, {
cid: remoteCid,
state: categories.watchStates.watching,
uid,
});
const { id, note } = helpers.mocks.note({
cc: [remoteCid],
});
const assertion = await activitypub.notes.assert(0, id);
assert(assertion);
const unread = await topics.getTotalUnread(uid);
assert.strictEqual(unread, 1);
// Notification inbox delivery is async so can't test directly
const exists = await db.exists(`notifications:new_topic:tid:${assertion.tid}:uid:${note.attributedTo}`);
assert(exists);
await topics.markAllRead(uid);
});
});
});
describe('Private objects', () => {
let recipientUid;
before(async () => {
recipientUid = await user.create({ username: utils.generateUUID().slice(0, 8) });
});
it('should NOT create a new topic or post when asserting a private note', async () => {
const { id, note } = helpers.mocks.note({
to: [`${nconf.get('url')}/uid/${recipientUid}`],
cc: [],
});
const { activity } = helpers.mocks.create(note);
const { roomId } = await activitypub.inbox.create({ body: activity });
assert(roomId);
assert(utils.isNumber(roomId));
const exists = await posts.exists(id);
assert(!exists);
});
it('should still assert if the cc property is missing', async () => {
const { id, note } = helpers.mocks.note({
to: [`${nconf.get('url')}/uid/${recipientUid}`],
cc: 'remove',
});
const { activity } = helpers.mocks.create(note);
const { roomId } = await activitypub.inbox.create({ body: activity });
assert(roomId);
assert(utils.isNumber(roomId));
});
});
});
describe('Inbox Synchronization', () => {
let cid;
let uid;
let topicData;
before(async () => {
({ cid } = await categories.create({ name: utils.generateUUID().slice(0, 8) }));
});
beforeEach(async () => {
uid = await user.create({ username: utils.generateUUID().slice(0, 10) });
({ topicData } = await topics.post({
cid,
uid,
title: utils.generateUUID(),
content: utils.generateUUID(),
}));
});
it('should add a topic to a user\'s inbox if user is a recipient in OP', async () => {
await db.setAdd(`post:${topicData.mainPid}:recipients`, [uid]);
await activitypub.notes.syncUserInboxes(topicData.tid);
const inboxed = await db.isSortedSetMember(`uid:${uid}:inbox`, topicData.tid);
assert.strictEqual(inboxed, true);
});
it('should add a topic to a user\'s inbox if a user is a recipient in a reply', async () => {
const uid = await user.create({ username: utils.generateUUID().slice(0, 10) });
const { pid } = await topics.reply({
tid: topicData.tid,
uid,
content: utils.generateUUID(),
});
await db.setAdd(`post:${pid}:recipients`, [uid]);
await activitypub.notes.syncUserInboxes(topicData.tid);
const inboxed = await db.isSortedSetMember(`uid:${uid}:inbox`, topicData.tid);
assert.strictEqual(inboxed, true);
});
it('should maintain a list of recipients at the topic level', async () => {
await db.setAdd(`post:${topicData.mainPid}:recipients`, [uid]);
await activitypub.notes.syncUserInboxes(topicData.tid);
const [isRecipient, count] = await Promise.all([
db.isSetMember(`tid:${topicData.tid}:recipients`, uid),
db.setCount(`tid:${topicData.tid}:recipients`),
]);
assert(isRecipient);
assert.strictEqual(count, 1);
});
it('should add topic to a user\'s inbox if it is explicitly passed in as an argument', async () => {
await activitypub.notes.syncUserInboxes(topicData.tid, uid);
const inboxed = await db.isSortedSetMember(`uid:${uid}:inbox`, topicData.tid);
assert.strictEqual(inboxed, true);
});
it('should remove a topic from a user\'s inbox if that user is no longer a recipient in any contained posts', async () => {
await activitypub.notes.syncUserInboxes(topicData.tid, uid);
await activitypub.notes.syncUserInboxes(topicData.tid);
const inboxed = await db.isSortedSetMember(`uid:${uid}:inbox`, topicData.tid);
assert.strictEqual(inboxed, false);
});
});
describe('Deletion', () => {
let cid;
let uid;
let topicData;
before(async () => {
({ cid } = await categories.create({ name: utils.generateUUID().slice(0, 8) }));
});
beforeEach(async () => {
uid = await user.create({ username: utils.generateUUID().slice(0, 10) });
({ topicData } = await topics.post({
cid,
uid,
title: utils.generateUUID(),
content: utils.generateUUID(),
}));
});
it('should clean up recipient sets for the post', async () => {
const { pid } = await topics.reply({
pid: `https://example.org/${utils.generateUUID().slice(0, 8)}`,
tid: topicData.tid,
uid,
content: utils.generateUUID(),
});
await db.setAdd(`post:${pid}:recipients`, [uid]);
await activitypub.notes.delete([pid]);
const inboxed = await db.isSetMember(`post:${pid}:recipients`, uid);
assert(!inboxed);
});
});
});