mirror of
				https://github.com/NodeBB/NodeBB.git
				synced 2025-10-26 16:46:12 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			775 lines
		
	
	
		
			23 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			775 lines
		
	
	
		
			23 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', () => {
 | |
| 	before(async () => {
 | |
| 		meta.config.activitypubEnabled = 1;
 | |
| 		await install.giveWorldPrivileges();
 | |
| 	});
 | |
| 
 | |
| 	describe('Assertion', () => {
 | |
| 		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 add a remote category topic to a user\'s inbox if they are following the category', async () => {
 | |
| 					const { id: cid, actor } = helpers.mocks.group();
 | |
| 					await activitypub.actors.assertGroup([cid]);
 | |
| 
 | |
| 					const uid = await user.create({ username: utils.generateUUID() });
 | |
| 					await api.categories.setWatchState({ uid }, { cid, state: categories.watchStates.tracking });
 | |
| 
 | |
| 					const { id } = helpers.mocks.note({
 | |
| 						cc: [cid],
 | |
| 					});
 | |
| 					const { tid } = await activitypub.notes.assert(0, id, { cid });
 | |
| 
 | |
| 					const inInbox = await db.isSortedSetMember(`uid:${uid}:inbox`, tid);
 | |
| 					assert(inInbox);
 | |
| 				});
 | |
| 			});
 | |
| 
 | |
| 			describe('User-specific behaviours', () => {
 | |
| 				let remoteCid;
 | |
| 				let uid;
 | |
| 
 | |
| 				before(async () => {
 | |
| 					// Remote
 | |
| 					const { id, actor } = helpers.mocks.group();
 | |
| 					remoteCid = id;
 | |
| 					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, { cid: remoteCid });
 | |
| 					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, { cid: remoteCid });
 | |
| 					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);
 | |
| 				});
 | |
| 
 | |
| 				it('should not show up in recent/unread if I am ignoring the remote category', async () => {
 | |
| 					await api.categories.setWatchState({ uid }, {
 | |
| 						cid: remoteCid,
 | |
| 						state: categories.watchStates.ignoring,
 | |
| 						uid,
 | |
| 					});
 | |
| 
 | |
| 					const { id, note } = helpers.mocks.note({
 | |
| 						cc: [remoteCid],
 | |
| 					});
 | |
| 					const assertion = await activitypub.notes.assert(0, id, { cid: remoteCid });
 | |
| 					assert(assertion);
 | |
| 
 | |
| 					const unread = await topics.getTotalUnread(uid);
 | |
| 					assert.strictEqual(unread, 0);
 | |
| 				});
 | |
| 			});
 | |
| 		});
 | |
| 
 | |
| 		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('Creation', () => {
 | |
| 		let uid;
 | |
| 
 | |
| 		before(async () => {
 | |
| 			uid = await user.create({ username: utils.generateUUID() });
 | |
| 		});
 | |
| 
 | |
| 		describe('Local categories', () => {
 | |
| 			let cid;
 | |
| 
 | |
| 			before(async () => {
 | |
| 				({ cid } = await categories.create({ name: utils.generateUUID() }));
 | |
| 				activitypub._sent.clear();
 | |
| 			});
 | |
| 
 | |
| 			afterEach(() => {
 | |
| 				activitypub._sent.clear();
 | |
| 			});
 | |
| 
 | |
| 			describe('new topics', () => {
 | |
| 				let activity;
 | |
| 
 | |
| 				before(async () => {
 | |
| 					const { tid } = await api.topics.create({ uid }, {
 | |
| 						cid,
 | |
| 						title: utils.generateUUID(),
 | |
| 						content: utils.generateUUID(),
 | |
| 					});
 | |
| 
 | |
| 					assert(tid);
 | |
| 					assert.strictEqual(activitypub._sent.size, 1);
 | |
| 					const key = Array.from(activitypub._sent.keys())[0];
 | |
| 					activity = activitypub._sent.get(key);
 | |
| 				});
 | |
| 
 | |
| 				it('should federate out a Create activity', () => {
 | |
| 					assert(activity && activity.to);
 | |
| 					assert.strictEqual(activity.type, 'Create');
 | |
| 				});
 | |
| 
 | |
| 				it('should have the local category addressed', () => {
 | |
| 					const addressees = new Set([
 | |
| 						...(activity.to || []),
 | |
| 						...(activity.cc || []),
 | |
| 						...(activity.bcc || []),
 | |
| 						...(activity.object.to || []),
 | |
| 						...(activity.object.cc || []),
 | |
| 						...(activity.object.bcc || []),
 | |
| 					]);
 | |
| 
 | |
| 					assert(addressees.has(`${nconf.get('url')}/category/${cid}`));
 | |
| 				});
 | |
| 
 | |
| 				it('should federate out an activity with object of type "Article"', () => {
 | |
| 					assert(activity.object && activity.object.type);
 | |
| 					assert.strictEqual(activity.object.type, 'Article');
 | |
| 				});
 | |
| 			});
 | |
| 
 | |
| 			describe('new reply', () => {
 | |
| 				let activity;
 | |
| 
 | |
| 				before(async () => {
 | |
| 					const { tid } = await api.topics.create({ uid }, {
 | |
| 						cid,
 | |
| 						title: utils.generateUUID(),
 | |
| 						content: utils.generateUUID(),
 | |
| 					});
 | |
| 					activitypub._sent.clear();
 | |
| 
 | |
| 					const { pid } = await api.topics.reply({ uid }, {
 | |
| 						tid,
 | |
| 						content: utils.generateUUID(),
 | |
| 					});
 | |
| 
 | |
| 					const key = Array.from(activitypub._sent.keys())[0];
 | |
| 					activity = activitypub._sent.get(key);
 | |
| 				});
 | |
| 
 | |
| 				it('should federate out an activity with object of type "Note"', () => {
 | |
| 					assert(activity.object && activity.object.type);
 | |
| 					assert.strictEqual(activity.object.type, 'Note');
 | |
| 				});
 | |
| 			});
 | |
| 		});
 | |
| 
 | |
| 		describe('Remote Categories', () => {
 | |
| 			let cid;
 | |
| 
 | |
| 			before(async () => {
 | |
| 				({ id: cid } = helpers.mocks.group());
 | |
| 				await activitypub.actors.assert([cid]);
 | |
| 			});
 | |
| 
 | |
| 			afterEach(() => {
 | |
| 				activitypub._sent.clear();
 | |
| 			});
 | |
| 
 | |
| 			describe('new topics', () => {
 | |
| 				it('should federate out a Create activity with the remote community addressed', async () => {
 | |
| 					const { tid } = await api.topics.create({ uid }, {
 | |
| 						cid,
 | |
| 						title: utils.generateUUID(),
 | |
| 						content: utils.generateUUID(),
 | |
| 					});
 | |
| 
 | |
| 					assert(tid);
 | |
| 					assert.strictEqual(activitypub._sent.size, 1);
 | |
| 
 | |
| 					const key = Array.from(activitypub._sent.keys())[0];
 | |
| 					const activity = activitypub._sent.get(key);
 | |
| 					assert(activity && activity.to);
 | |
| 					assert.strictEqual(activity.type, 'Create');
 | |
| 
 | |
| 					const addressees = new Set([
 | |
| 						...(activity.to || []),
 | |
| 						...(activity.cc || []),
 | |
| 						...(activity.bcc || []),
 | |
| 						...(activity.object.to || []),
 | |
| 						...(activity.object.cc || []),
 | |
| 						...(activity.object.bcc || []),
 | |
| 					]);
 | |
| 
 | |
| 					assert(addressees.has(cid));
 | |
| 				});
 | |
| 			});
 | |
| 
 | |
| 			describe('replies', () => {
 | |
| 				it('should federate out a Create activity with the remote community addressed', async () => {
 | |
| 					const { tid } = await api.topics.create({ uid }, {
 | |
| 						cid,
 | |
| 						title: utils.generateUUID(),
 | |
| 						content: utils.generateUUID(),
 | |
| 					});
 | |
| 
 | |
| 					activitypub._sent.clear();
 | |
| 
 | |
| 					const postData = await api.topics.reply({ uid }, {
 | |
| 						tid,
 | |
| 						content: utils.generateUUID(),
 | |
| 					});
 | |
| 
 | |
| 					assert(postData);
 | |
| 					assert.strictEqual(activitypub._sent.size, 1);
 | |
| 
 | |
| 					const key = Array.from(activitypub._sent.keys())[0];
 | |
| 					const activity = activitypub._sent.get(key);
 | |
| 					assert(activity && activity.to);
 | |
| 					assert.strictEqual(activity.type, 'Create');
 | |
| 
 | |
| 					const addressees = new Set([
 | |
| 						...(activity.to || []),
 | |
| 						...(activity.cc || []),
 | |
| 						...(activity.bcc || []),
 | |
| 						...(activity.object.to || []),
 | |
| 						...(activity.object.cc || []),
 | |
| 						...(activity.object.bcc || []),
 | |
| 					]);
 | |
| 
 | |
| 					assert(addressees.has(cid));
 | |
| 				});
 | |
| 			});
 | |
| 		});
 | |
| 	});
 | |
| 
 | |
| 	describe('Inbox handling', () => {
 | |
| 		describe('helper self-check', () => {
 | |
| 			it('should generate a Like activity', () => {
 | |
| 				const object = utils.generateUUID();
 | |
| 				const { id: actor } = helpers.mocks.person();
 | |
| 				const { activity } = helpers.mocks.like({
 | |
| 					object,
 | |
| 					actor,
 | |
| 				});
 | |
| 
 | |
| 				assert.deepStrictEqual(activity, {
 | |
| 					'@context': 'https://www.w3.org/ns/activitystreams',
 | |
| 					id: `${helpers.mocks._baseUrl}/like/${encodeURIComponent(object)}`,
 | |
| 					type: 'Like',
 | |
| 					actor,
 | |
| 					object,
 | |
| 				});
 | |
| 			});
 | |
| 
 | |
| 			it('should generate an Announce activity wrapping a Like activity', () => {
 | |
| 				const object = utils.generateUUID();
 | |
| 				const { id: actor } = helpers.mocks.person();
 | |
| 				const { activity: like } = helpers.mocks.like({
 | |
| 					object,
 | |
| 					actor,
 | |
| 				});
 | |
| 				const { id: gActor } = helpers.mocks.group();
 | |
| 				const { activity } = helpers.mocks.announce({
 | |
| 					actor: gActor,
 | |
| 					object: like,
 | |
| 				});
 | |
| 
 | |
| 				assert.deepStrictEqual(activity, {
 | |
| 					'@context': 'https://www.w3.org/ns/activitystreams',
 | |
| 					id: `${helpers.mocks._baseUrl}/announce/${encodeURIComponent(like.id)}`,
 | |
| 					type: 'Announce',
 | |
| 					to: [ 'https://www.w3.org/ns/activitystreams#Public' ],
 | |
| 					cc: [
 | |
| 						`${gActor}/followers`,
 | |
| 					],
 | |
| 					actor: gActor,
 | |
| 					object: like,
 | |
| 				});
 | |
| 			});
 | |
| 		});
 | |
| 
 | |
| 		describe('Create', () => {
 | |
| 			let uid;
 | |
| 
 | |
| 			before(async () => {
 | |
| 				uid = await user.create({ username: utils.generateUUID() });
 | |
| 			});
 | |
| 
 | |
| 			describe('(Note)', () => {
 | |
| 				it('should create a new topic in cid -1', async () => {
 | |
| 					const { note, id } = helpers.mocks.note();
 | |
| 					const { activity } = helpers.mocks.create(note);
 | |
| 
 | |
| 					await db.sortedSetAdd(`followersRemote:${note.attributedTo}`, Date.now(), uid);
 | |
| 					await activitypub.inbox.create({ body: activity });
 | |
| 
 | |
| 					assert(await posts.exists(id));
 | |
| 
 | |
| 					const cid = await posts.getCidByPid(id);
 | |
| 					assert.strictEqual(cid, -1);
 | |
| 				});
 | |
| 
 | |
| 				it('should create a new topic in cid -1 even if a remote category is addressed', async () => {
 | |
| 					const { id: remoteCid } = helpers.mocks.group();
 | |
| 					const { note, id } = helpers.mocks.note({
 | |
| 						audience: [remoteCid],
 | |
| 					});
 | |
| 					const { activity } = helpers.mocks.create(note);
 | |
| 
 | |
| 					await activitypub.inbox.create({ body: activity });
 | |
| 
 | |
| 					assert(await posts.exists(id));
 | |
| 
 | |
| 					const cid = await posts.getCidByPid(id);
 | |
| 					assert.strictEqual(cid, -1);
 | |
| 				});
 | |
| 			});
 | |
| 		});
 | |
| 
 | |
| 		describe('Announce', () => {
 | |
| 			let cid;
 | |
| 
 | |
| 			before(async () => {
 | |
| 				({ cid } = await categories.create({ name: utils.generateUUID().slice(0, 8) }));
 | |
| 			});
 | |
| 
 | |
| 			describe('(Create)', () => {
 | |
| 				it('should create a new topic in a remote category if addressed', async () => {
 | |
| 					const { id: remoteCid } = helpers.mocks.group();
 | |
| 					const { id, note } = helpers.mocks.note({
 | |
| 						audience: [remoteCid],
 | |
| 					});
 | |
| 					let { activity } = helpers.mocks.create(note);
 | |
| 					({ activity } = helpers.mocks.announce({ actor: remoteCid, object: activity }));
 | |
| 
 | |
| 					await activitypub.inbox.announce({ body: activity });
 | |
| 
 | |
| 					assert(await posts.exists(id));
 | |
| 
 | |
| 					const cid = await posts.getCidByPid(id);
 | |
| 					assert.strictEqual(cid, remoteCid);
 | |
| 				});
 | |
| 			});
 | |
| 
 | |
| 			describe('(Create) or (Note) referencing local post', () => {
 | |
| 				let uid;
 | |
| 				let topicData;
 | |
| 				let postData;
 | |
| 				let localNote;
 | |
| 				let announces = 0;
 | |
| 
 | |
| 				before(async () => {
 | |
| 					uid = await user.create({ username: utils.generateUUID().slice(0, 10) });
 | |
| 					({ topicData, postData } = await topics.post({
 | |
| 						cid,
 | |
| 						uid,
 | |
| 						title: utils.generateUUID(),
 | |
| 						content: utils.generateUUID(),
 | |
| 					}));
 | |
| 					localNote = await activitypub.mocks.notes.public(postData);
 | |
| 				});
 | |
| 
 | |
| 				it('should increment announces counter when a remote user shares', async () => {
 | |
| 					const { id } = helpers.mocks.person();
 | |
| 					const { activity } = helpers.mocks.announce({
 | |
| 						actor: id,
 | |
| 						object: localNote,
 | |
| 						cc: [`${nconf.get('url')}/uid/${topicData.uid}`],
 | |
| 					});
 | |
| 
 | |
| 					await activitypub.inbox.announce({ body: activity });
 | |
| 					announces += 1;
 | |
| 
 | |
| 					const count = await posts.getPostField(topicData.mainPid, 'announces');
 | |
| 					assert.strictEqual(count, announces);
 | |
| 				});
 | |
| 
 | |
| 				it('should contain the remote user announcer id in the post announces zset', async () => {
 | |
| 					const { id } = helpers.mocks.person();
 | |
| 					const { activity } = helpers.mocks.announce({
 | |
| 						actor: id,
 | |
| 						object: localNote,
 | |
| 						cc: [`${nconf.get('url')}/uid/${topicData.uid}`],
 | |
| 					});
 | |
| 
 | |
| 					await activitypub.inbox.announce({ body: activity });
 | |
| 					announces += 1;
 | |
| 
 | |
| 					const exists = await db.isSortedSetMember(`pid:${topicData.mainPid}:announces`, id);
 | |
| 					assert(exists);
 | |
| 				});
 | |
| 
 | |
| 				it('should NOT increment announces counter when a remote category shares', async () => {
 | |
| 					const { id } = helpers.mocks.group();
 | |
| 					const { activity } = helpers.mocks.announce({
 | |
| 						actor: id,
 | |
| 						object: localNote,
 | |
| 						cc: [`${nconf.get('url')}/uid/${topicData.uid}`],
 | |
| 					});
 | |
| 
 | |
| 					await activitypub.inbox.announce({ body: activity });
 | |
| 
 | |
| 					const count = await posts.getPostField(topicData.mainPid, 'announces');
 | |
| 					assert.strictEqual(count, announces);
 | |
| 				});
 | |
| 
 | |
| 				it('should NOT contain the remote category announcer id in the post announces zset', async () => {
 | |
| 					const { id } = helpers.mocks.group();
 | |
| 					const { activity } = helpers.mocks.announce({
 | |
| 						actor: id,
 | |
| 						object: localNote,
 | |
| 						cc: [`${nconf.get('url')}/uid/${topicData.uid}`],
 | |
| 					});
 | |
| 
 | |
| 					await activitypub.inbox.announce({ body: activity });
 | |
| 
 | |
| 					const exists = await db.isSortedSetMember(`pid:${topicData.mainPid}:announces`, id);
 | |
| 					assert(!exists);
 | |
| 				});
 | |
| 			});
 | |
| 
 | |
| 			describe('(Note)', () => {
 | |
| 				it('should create a new topic in cid -1 if category not addressed', async () => {
 | |
| 					const { note } = helpers.mocks.note();
 | |
| 					await activitypub.actors.assert([note.attributedTo]);
 | |
| 					const { activity } = helpers.mocks.announce({
 | |
| 						object: note,
 | |
| 					});
 | |
| 					const uid = await user.create({ username: utils.generateUUID().slice(0, 10) });
 | |
| 					await db.sortedSetAdd(`followersRemote:${activity.actor}`, Date.now(), uid);
 | |
| 
 | |
| 					const beforeCount = await db.sortedSetCard(`cid:-1:tids`);
 | |
| 					await activitypub.inbox.announce({ body: activity });
 | |
| 					const count = await db.sortedSetCard(`cid:-1:tids`);
 | |
| 
 | |
| 					assert.strictEqual(count, beforeCount + 1);
 | |
| 				});
 | |
| 
 | |
| 				it('should create a new topic in local category', async () => {
 | |
| 					const { note } = helpers.mocks.note({
 | |
| 						cc: [`${nconf.get('url')}/category/${cid}`],
 | |
| 					});
 | |
| 					await activitypub.actors.assert([note.attributedTo]);
 | |
| 					const { activity } = helpers.mocks.announce({
 | |
| 						object: note,
 | |
| 					});
 | |
| 					const uid = await user.create({ username: utils.generateUUID().slice(0, 10) });
 | |
| 					await db.sortedSetAdd(`followersRemote:${activity.actor}`, Date.now(), uid);
 | |
| 
 | |
| 					const beforeCount = await db.sortedSetCard(`cid:${cid}:tids`);
 | |
| 					await activitypub.inbox.announce({ body: activity });
 | |
| 					const count = await db.sortedSetCard(`cid:${cid}:tids`);
 | |
| 
 | |
| 					assert.strictEqual(count, beforeCount + 1);
 | |
| 				});
 | |
| 			});
 | |
| 
 | |
| 			describe('(Like)', () => {
 | |
| 				it('should upvote a local post', async () => {
 | |
| 					const uid = await user.create({ username: utils.generateUUID().slice(0, 10) });
 | |
| 					const { postData } = await topics.post({
 | |
| 						cid,
 | |
| 						uid,
 | |
| 						title: utils.generateUUID(),
 | |
| 						content: utils.generateUUID(),
 | |
| 					});
 | |
| 
 | |
| 					const { activity: like } = helpers.mocks.like({
 | |
| 						object: `${nconf.get('url')}/post/${postData.pid}`,
 | |
| 					});
 | |
| 					const { activity } = helpers.mocks.announce({
 | |
| 						object: like,
 | |
| 					});
 | |
| 
 | |
| 					let { upvotes } = await posts.getPostFields(postData.pid, 'upvotes');
 | |
| 					assert.strictEqual(upvotes, 0);
 | |
| 
 | |
| 					await activitypub.inbox.announce({ body: activity });
 | |
| 					({ upvotes } = await posts.getPostFields(postData.pid, 'upvotes'));
 | |
| 					assert.strictEqual(upvotes, 1);
 | |
| 				});
 | |
| 
 | |
| 				it('should upvote an asserted remote post', async () => {
 | |
| 					const { id } = helpers.mocks.note();
 | |
| 					await activitypub.notes.assert(0, [id], { skipChecks: true });
 | |
| 					const { activity: like } = helpers.mocks.like({
 | |
| 						object: id,
 | |
| 					});
 | |
| 					const { activity } = helpers.mocks.announce({
 | |
| 						object: like,
 | |
| 					});
 | |
| 
 | |
| 					let { upvotes } = await posts.getPostFields(id, 'upvotes');
 | |
| 					assert.strictEqual(upvotes, 0);
 | |
| 
 | |
| 					await activitypub.inbox.announce({ body: activity });
 | |
| 
 | |
| 					({ upvotes } = await posts.getPostFields(id, 'upvotes'));
 | |
| 					assert.strictEqual(upvotes, 1);
 | |
| 				});
 | |
| 			});
 | |
| 
 | |
| 			describe('(Update)', () => {
 | |
| 				it('should update a note\'s content', async () => {
 | |
| 					const { id: actor } = helpers.mocks.person();
 | |
| 					const { id, note } = helpers.mocks.note({ attributedTo: actor });
 | |
| 					await activitypub.notes.assert(0, [id], { skipChecks: true });
 | |
| 					note.content = utils.generateUUID();
 | |
| 					const { activity: update } = helpers.mocks.update({ object: note });
 | |
| 					const { activity } = helpers.mocks.announce({ object: update });
 | |
| 
 | |
| 					await activitypub.inbox.announce({ body: activity });
 | |
| 
 | |
| 					const content = await posts.getPostField(id, 'content');
 | |
| 					assert.strictEqual(content, note.content);
 | |
| 				});
 | |
| 			});
 | |
| 		});
 | |
| 	});
 | |
| 
 | |
| 	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);
 | |
| 		});
 | |
| 	});
 | |
| });
 |