From add163a42db9b103e8c85d4118ed367071fdccba Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 31 Dec 2025 10:54:57 -0500 Subject: [PATCH] test: ensure auto-cat and cat sync logic properly integrates with crossposts --- src/activitypub/actors.js | 9 +++++ test/topics/crossposts.js | 85 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+) diff --git a/src/activitypub/actors.js b/src/activitypub/actors.js index c6894b7c3b..15128e82f0 100644 --- a/src/activitypub/actors.js +++ b/src/activitypub/actors.js @@ -445,10 +445,19 @@ Actors.getLocalFollowers = async (id) => { } }); } else if (isCategory) { + // Internally, users are different, they follow via watch state instead + // Possibly refactor to store in followersRemote:${id} too?? const members = await db.getSortedSetRangeByScore(`cid:${id}:uid:watch:state`, 0, -1, categories.watchStates.tracking, categories.watchStates.watching); members.forEach((uid) => { response.uids.add(uid); }); + + const cids = await db.getSortedSetMembers(`followersRemote:${id}`); + cids.forEach((id) => { + if (id.startsWith('cid|') && utils.isNumber(id.slice(4))) { + response.cids.add(parseInt(id.slice(4), 10)); + } + }); } return response; diff --git a/test/topics/crossposts.js b/test/topics/crossposts.js index 542e5b22a7..3becd9868d 100644 --- a/test/topics/crossposts.js +++ b/test/topics/crossposts.js @@ -348,6 +348,91 @@ describe('Crossposting (& related logic)', () => { }); }); + describe('category sync; integration with', () => { + let cid; + let remoteCid; + let pid; + let post; + + const helpers = require('../activitypub/helpers'); + + before(async () => { + ({ cid } = await categories.create({ name: utils.generateUUID().slice(0, 8) })); + ({ id: remoteCid } = helpers.mocks.group()); + ({ id: pid, note: post } = helpers.mocks.note({ + audience: [remoteCid], + })); + + // Mock a group follow/accept + const timestamp = Date.now(); + console.log('saving', remoteCid); + await Promise.all([ + db.sortedSetAdd(`cid:${cid}:following`, timestamp, remoteCid), + db.sortedSetAdd(`followersRemote:${remoteCid}`, timestamp, `cid|${cid}`), + ]); + }); + + it('should automatically cross-post the topic when the remote category announces', async () => { + const { activity: body } = helpers.mocks.announce({ + actor: remoteCid, + object: post, + }); + + await activitypub.inbox.announce({ body }); + + const tid = await posts.getPostField(pid, 'tid'); + const crossposts = await topics.crossposts.get(tid); + + assert.strictEqual(crossposts.length, 1); + assert.partialDeepStrictEqual(crossposts[0], { + uid: '0', + tid, + cid: String(cid), + }); + }); + }); + + describe('auto-categorization; integration with', () => { + let cid; + let remoteCid; + let pid; + let post; + + const helpers = require('../activitypub/helpers'); + + before(async () => { + const preferredUsername = utils.generateUUID().slice(0, 8); + ({ cid } = await categories.create({ name: utils.generateUUID().slice(0, 8) })); + ({ id: remoteCid } = helpers.mocks.group({ + preferredUsername, + })); + ({ id: pid, note: post } = helpers.mocks.note({ + audience: [remoteCid], + tag: [ + { + type: 'Hashtag', + name: `#${preferredUsername}`, + }, + ], + })); + + await activitypub.rules.add('hashtag', preferredUsername, cid); + }); + + it('note assertion should automatically cross-post', async () => { + await activitypub.notes.assert(0, pid, { skipChecks: true }); + + const tid = await posts.getPostField(pid, 'tid'); + const crossposts = await topics.crossposts.get(tid); + assert.strictEqual(crossposts.length, 1); + assert.partialDeepStrictEqual(crossposts[0], { + uid: '0', + tid, + cid: String(cid), + }); + }); + }); + describe('ActivityPub effects (or lack thereof)', () => { describe('local canonical category', () => { let tid;