feat: remote user to category migration should also migrate local user follows into category watches

This commit is contained in:
Julian Lam
2025-03-24 15:15:48 -04:00
parent 309deb0d7a
commit ac7b7f81b3
2 changed files with 65 additions and 31 deletions

View File

@@ -418,6 +418,13 @@ async function _migratePersonToGroup(categoryObjs) {
}); });
} }
})); }));
const followers = await db.getSortedSetMembersWithScores(`followersRemote:${id}`);
await db.sortedSetAdd(
`cid:${id}:uid:watch:state`,
followers.map(() => categories.watchStates.tracking),
followers.map(({ value }) => value),
);
await user.deleteAccount(id); await user.deleteAccount(id);
})); }));
await categories.onTopicsMoved(ids); await categories.onTopicsMoved(ids);

View File

@@ -76,46 +76,73 @@ describe('Actor asserton', () => {
assert.strictEqual(assertion[0].cid, actor.id); assert.strictEqual(assertion[0].cid, actor.id);
}); });
it('should not migrate a user to a category if .assert is called', async () => { describe('remote user to remote category migration', () => {
// ... because the user isn't due for an update and so is filtered out during qualification it('should not migrate a user to a category if .assert is called', async () => {
const { id } = helpers.mocks.person(); // ... because the user isn't due for an update and so is filtered out during qualification
await activitypub.actors.assert([id]); const { id } = helpers.mocks.person();
await activitypub.actors.assert([id]);
const { actor } = helpers.mocks.group({ id }); const { actor } = helpers.mocks.group({ id });
const assertion = await activitypub.actors.assertGroup([id]); const assertion = await activitypub.actors.assertGroup([id]);
assert(assertion.length, 0); assert(assertion.length, 0);
const exists = await user.exists(id); const exists = await user.exists(id);
assert.strictEqual(exists, false); assert.strictEqual(exists, false);
}); });
it('should migrate a user to a category if on re-assertion it identifies as an as:Group', async () => { it('should migrate a user to a category if on re-assertion it identifies as an as:Group', async () => {
// This is to handle previous behaviour that saved all as:Group actors as NodeBB users. // This is to handle previous behaviour that saved all as:Group actors as NodeBB users.
const { id } = helpers.mocks.person(); const { id } = helpers.mocks.person();
await activitypub.actors.assert([id]); await activitypub.actors.assert([id]);
// Two shares helpers.mocks.group({ id });
for (let x = 0; x < 2; x++) { const assertion = await activitypub.actors.assertGroup([id]);
const { id: pid } = helpers.mocks.note();
// eslint-disable-next-line no-await-in-loop
const { tid } = await activitypub.notes.assert(0, pid, { skipChecks: 1 });
// eslint-disable-next-line no-await-in-loop
await db.sortedSetAdd(`uid:${id}:shares`, Date.now(), tid);
}
const { actor } = helpers.mocks.group({ id }); assert(assertion && Array.isArray(assertion) && assertion.length === 1);
const assertion = await activitypub.actors.assertGroup([id]);
assert(assertion && Array.isArray(assertion) && assertion.length === 1); const exists = await user.exists(id);
assert.strictEqual(exists, false);
});
const { topic_count, post_count } = await categories.getCategoryData(id); it('should migrate any shares by that user, into topics in the category', async () => {
assert.strictEqual(topic_count, 2); const { id } = helpers.mocks.person();
assert.strictEqual(post_count, 2); await activitypub.actors.assert([id]);
const exists = await user.exists(id); // Two shares
assert.strictEqual(exists, false); for (let x = 0; x < 2; x++) {
}); const { id: pid } = helpers.mocks.note();
// eslint-disable-next-line no-await-in-loop
const { tid } = await activitypub.notes.assert(0, pid, { skipChecks: 1 });
// eslint-disable-next-line no-await-in-loop
await db.sortedSetAdd(`uid:${id}:shares`, Date.now(), tid);
}
helpers.mocks.group({ id });
await activitypub.actors.assertGroup([id]);
const { topic_count, post_count } = await categories.getCategoryData(id);
assert.strictEqual(topic_count, 2);
assert.strictEqual(post_count, 2);
});
it('should migrate any local followers into category watches', async () => {
const { id } = helpers.mocks.person();
await activitypub.actors.assert([id]);
const followerUid = await user.create({ username: utils.generateUUID() });
await Promise.all([
db.sortedSetAdd(`followingRemote:${followerUid}`, Date.now(), id),
db.sortedSetAdd(`followersRemote:${id}`, Date.now(), followerUid),
]);
helpers.mocks.group({ id });
await activitypub.actors.assertGroup([id]);
const states = await categories.getWatchState([id], followerUid);
assert.strictEqual(states[0], categories.watchStates.tracking);
})
})
}); });
describe('edge cases: loopback handles and uris', () => { describe('edge cases: loopback handles and uris', () => {