diff --git a/test/activitypub/inbox.js b/test/activitypub/inbox.js new file mode 100644 index 0000000000..91b0c76e46 --- /dev/null +++ b/test/activitypub/inbox.js @@ -0,0 +1,479 @@ +'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 topics = require('../../src/topics'); +const posts = require('../../src/posts'); +const activitypub = require('../../src/activitypub'); +const utils = require('../../src/utils'); + +const helpers = require('./helpers'); + +describe('Inbox', () => { + before(async () => { + meta.config.activitypubEnabled = 1; + await install.giveWorldPrivileges(); + }); + + 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('from a banned account', () => { + before(async function () { + const { id: bannedUid } = helpers.mocks.person(); + this.bannedUid = bannedUid; + + const { note } = helpers.mocks.note({ + attributedTo: bannedUid, + }); + + const uid = await user.create({ username: utils.generateUUID() }); + const { id: boosterUid } = helpers.mocks.person(); + await db.sortedSetAdd(`followersRemote:${boosterUid}`, Date.now(), uid); + const { activity } = helpers.mocks.announce({ + actor: boosterUid, + object: note, + }); + this.activity = activity; + this.pid = note.id; + + await user.bans.ban(bannedUid, 0, 'testing'); + }); + + it('should list the remote user as banned, when queried', async function () { + const isBanned = await user.bans.isBanned(this.bannedUid); + assert.strictEqual(isBanned, true); + }); + + // Can't actually test the middleware because I can't sign a request for a test + + it('should not assert a note if authored by a banned user (boosted by third-party)', async function () { + await activitypub.inbox.announce({ + body: this.activity, + }); + + const exists = await posts.exists(this.pid); + assert.strictEqual(exists, false); + }); + }); + + describe('Create', () => { + let uid; + let cid; + + 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 not append to the tids_read sorted set', 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 }); + + const exists = await db.exists(`uid:${note.attributedTo}:tids_read`); + assert(!exists); + }); + + it('should create a new topic in a remote category if addressed (category same-origin)', 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, remoteCid); + }); + + it('should create a new topic in cid -1 if a non-same origin remote category is addressed', async function () { + const { id: remoteCid } = helpers.mocks.group({ + id: `https://example.com/${utils.generateUUID()}`, + }); + const { note, id } = helpers.mocks.note({ + audience: [remoteCid], + }); + const { activity } = helpers.mocks.create(note); + try { + await activitypub.inbox.create({ body: activity }); + } catch (err) { + assert(false); + } + + assert(await posts.exists(id)); + const cid = await posts.getCidByPid(id); + assert.strictEqual(cid, -1); + }); + }); + + describe('(Like)', () => { + let pid; + let voterUid; + + before(async () => { + ({ cid } = await categories.create({ name: utils.generateUUID() })); + const { postData } = await topics.post({ + uid, + cid, + title: utils.generateUUID(), + content: utils.generateUUID(), + }); + pid = postData.pid; + const object = await activitypub.mocks.notes.public(postData); + const { activity } = helpers.mocks.like({ object }); + voterUid = activity.actor; + await activitypub.inbox.like({ body: activity }); + }); + + it('should increment a like for the post', async () => { + const voted = await posts.hasVoted(pid, voterUid); + const count = await posts.getPostField(pid, 'upvotes'); + assert(voted); + assert.strictEqual(count, 1); + }); + + it('should not append to the uid upvotes zset', async () => { + const exists = await db.exists(`uid:${voterUid}:upvote`); + assert(!exists); + }); + }); + }); + + describe('Announce', () => { + let cid; + + before(async () => { + ({ cid } = await categories.create({ name: utils.generateUUID() })); + }); + + 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); + }); + }); +}); diff --git a/test/activitypub/notes.js b/test/activitypub/notes.js index 470c7c821c..bded9c322f 100644 --- a/test/activitypub/notes.js +++ b/test/activitypub/notes.js @@ -384,462 +384,6 @@ describe('Notes', () => { }); }); - 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('from a banned account', () => { - before(async function () { - const { id: bannedUid } = helpers.mocks.person(); - this.bannedUid = bannedUid; - - const { note } = helpers.mocks.note({ - attributedTo: bannedUid, - }); - - const uid = await user.create({ username: utils.generateUUID() }); - const { id: boosterUid } = helpers.mocks.person(); - await db.sortedSetAdd(`followersRemote:${boosterUid}`, Date.now(), uid); - const { activity } = helpers.mocks.announce({ - actor: boosterUid, - object: note, - }); - this.activity = activity; - this.pid = note.id; - - await user.bans.ban(bannedUid, 0, 'testing'); - }); - - it('should list the remote user as banned, when queried', async function () { - const isBanned = await user.bans.isBanned(this.bannedUid); - assert.strictEqual(isBanned, true); - }); - - // Can't actually test the middleware because I can't sign a request for a test - - it('should not assert a note if authored by a banned user (boosted by third-party)', async function () { - await activitypub.inbox.announce({ - body: this.activity, - }); - - const exists = await posts.exists(this.pid); - assert.strictEqual(exists, false); - }); - }); - - describe('Create', () => { - let uid; - let cid; - - 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 not append to the tids_read sorted set', 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 }); - - const exists = await db.exists(`uid:${note.attributedTo}:tids_read`); - assert(!exists); - }); - - it('should create a new topic in a remote category if addressed (category same-origin)', 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, remoteCid); - }); - - it('should create a new topic in cid -1 if a non-same origin remote category is addressed', async function () { - const { id: remoteCid } = helpers.mocks.group({ - id: `https://example.com/${utils.generateUUID()}`, - }); - const { note, id } = helpers.mocks.note({ - audience: [remoteCid], - }); - const { activity } = helpers.mocks.create(note); - try { - await activitypub.inbox.create({ body: activity }); - } catch (err) { - assert(false); - } - - assert(await posts.exists(id)); - const cid = await posts.getCidByPid(id); - assert.strictEqual(cid, -1); - }); - }); - - describe('(Like)', () => { - let pid; - let voterUid; - - before(async () => { - ({ cid } = await categories.create({ name: utils.generateUUID() })); - const { postData } = await topics.post({ - uid, - cid, - title: utils.generateUUID(), - content: utils.generateUUID(), - }); - pid = postData.pid; - const object = await activitypub.mocks.notes.public(postData); - const { activity } = helpers.mocks.like({ object }); - voterUid = activity.actor; - await activitypub.inbox.like({ body: activity }); - }); - - it('should increment a like for the post', async () => { - const voted = await posts.hasVoted(pid, voterUid); - const count = await posts.getPostField(pid, 'upvotes'); - assert(voted); - assert.strictEqual(count, 1); - }); - - it('should not append to the uid upvotes zset', async () => { - const exists = await db.exists(`uid:${voterUid}:upvote`); - assert(!exists); - }); - }); - }); - - describe('Announce', () => { - let cid; - - before(async () => { - ({ cid } = await categories.create({ name: utils.generateUUID() })); - }); - - 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;