2024-05-09 15:48:58 -04:00
|
|
|
'use strict';
|
|
|
|
|
|
|
|
|
|
const assert = require('assert');
|
2025-02-26 12:29:36 -05:00
|
|
|
const nconf = require('nconf');
|
2024-05-09 15:48:58 -04:00
|
|
|
|
2025-02-28 13:36:54 -05:00
|
|
|
const db = require('../mocks/databasemock');
|
2025-02-25 14:24:56 -05:00
|
|
|
const meta = require('../../src/meta');
|
|
|
|
|
const install = require('../../src/install');
|
2024-05-09 15:48:58 -04:00
|
|
|
const user = require('../../src/user');
|
|
|
|
|
const categories = require('../../src/categories');
|
2025-02-26 12:29:36 -05:00
|
|
|
const posts = require('../../src/posts');
|
2024-05-09 15:48:58 -04:00
|
|
|
const topics = require('../../src/topics');
|
2025-03-17 14:52:52 -04:00
|
|
|
const api = require('../../src/api');
|
2024-05-09 15:48:58 -04:00
|
|
|
const activitypub = require('../../src/activitypub');
|
|
|
|
|
const utils = require('../../src/utils');
|
|
|
|
|
|
2025-02-26 12:29:36 -05:00
|
|
|
const helpers = require('./helpers');
|
|
|
|
|
|
2024-05-09 15:48:58 -04:00
|
|
|
describe('Notes', () => {
|
2025-03-21 14:16:33 -04:00
|
|
|
before(async () => {
|
|
|
|
|
meta.config.activitypubEnabled = 1;
|
|
|
|
|
await install.giveWorldPrivileges();
|
|
|
|
|
});
|
2025-02-25 14:24:56 -05:00
|
|
|
|
2025-03-21 14:16:33 -04:00
|
|
|
describe('Assertion', () => {
|
2025-03-17 14:52:52 -04:00
|
|
|
describe('Public objects', () => {
|
2025-02-26 12:29:36 -05:00
|
|
|
it('should pull a remote root-level object by its id and create a new topic', async () => {
|
|
|
|
|
const { id } = helpers.mocks.note();
|
2025-02-26 13:06:31 -05:00
|
|
|
const assertion = await activitypub.notes.assert(0, id, { skipChecks: true });
|
|
|
|
|
assert(assertion);
|
|
|
|
|
|
|
|
|
|
const { tid, count } = assertion;
|
|
|
|
|
assert(tid);
|
2025-02-26 12:29:36 -05:00
|
|
|
assert.strictEqual(count, 1);
|
|
|
|
|
|
|
|
|
|
const exists = await topics.exists(tid);
|
|
|
|
|
assert(exists);
|
2025-02-25 14:24:56 -05:00
|
|
|
});
|
|
|
|
|
|
2025-02-26 12:29:36 -05:00
|
|
|
it('should assert if the cc property is missing', async () => {
|
|
|
|
|
const { id } = helpers.mocks.note({ cc: 'remove' });
|
2025-02-26 13:06:31 -05:00
|
|
|
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);
|
2025-02-26 12:29:36 -05:00
|
|
|
assert.strictEqual(count, 1);
|
2025-02-25 14:24:56 -05:00
|
|
|
|
2025-02-26 12:29:36 -05:00
|
|
|
const exists = await topics.exists(tid);
|
|
|
|
|
assert(exists);
|
|
|
|
|
});
|
2025-03-17 12:02:43 -04:00
|
|
|
|
2025-03-17 14:52:52 -04:00
|
|
|
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);
|
2025-03-17 12:02:43 -04:00
|
|
|
});
|
|
|
|
|
|
2025-03-20 13:02:30 -04:00
|
|
|
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],
|
|
|
|
|
});
|
2025-05-13 13:59:34 -04:00
|
|
|
const { tid } = await activitypub.notes.assert(0, id, { cid });
|
2025-03-20 13:02:30 -04:00
|
|
|
|
|
|
|
|
const inInbox = await db.isSortedSetMember(`uid:${uid}:inbox`, tid);
|
|
|
|
|
assert(inInbox);
|
|
|
|
|
});
|
2025-03-17 12:02:43 -04:00
|
|
|
});
|
|
|
|
|
|
2025-03-17 14:52:52 -04:00
|
|
|
describe('User-specific behaviours', () => {
|
|
|
|
|
let remoteCid;
|
|
|
|
|
let uid;
|
2025-03-17 12:02:43 -04:00
|
|
|
|
2025-03-17 14:52:52 -04:00
|
|
|
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);
|
2025-03-17 12:02:43 -04:00
|
|
|
});
|
|
|
|
|
|
2025-03-17 14:52:52 -04:00
|
|
|
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);
|
2025-03-17 12:02:43 -04:00
|
|
|
|
2025-03-17 14:52:52 -04:00
|
|
|
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],
|
|
|
|
|
});
|
2025-05-13 13:59:34 -04:00
|
|
|
const assertion = await activitypub.notes.assert(0, id, { cid: remoteCid });
|
2025-03-17 14:52:52 -04:00
|
|
|
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],
|
|
|
|
|
});
|
2025-05-13 13:59:34 -04:00
|
|
|
const assertion = await activitypub.notes.assert(0, id, { cid: remoteCid });
|
2025-03-17 14:52:52 -04:00
|
|
|
assert(assertion);
|
2025-03-17 12:02:43 -04:00
|
|
|
|
2025-03-17 14:52:52 -04:00
|
|
|
const unread = await topics.getTotalUnread(uid);
|
|
|
|
|
assert.strictEqual(unread, 1);
|
2025-03-17 12:02:43 -04:00
|
|
|
|
2025-03-17 14:52:52 -04:00
|
|
|
// 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);
|
2025-03-17 12:02:43 -04:00
|
|
|
|
2025-03-17 14:52:52 -04:00
|
|
|
await topics.markAllRead(uid);
|
2025-03-17 12:02:43 -04:00
|
|
|
});
|
2025-03-25 10:44:39 -04:00
|
|
|
|
|
|
|
|
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],
|
|
|
|
|
});
|
2025-05-13 13:59:34 -04:00
|
|
|
const assertion = await activitypub.notes.assert(0, id, { cid: remoteCid });
|
2025-03-25 10:44:39 -04:00
|
|
|
assert(assertion);
|
|
|
|
|
|
|
|
|
|
const unread = await topics.getTotalUnread(uid);
|
|
|
|
|
assert.strictEqual(unread, 0);
|
2025-04-23 12:47:16 -04:00
|
|
|
});
|
2025-03-17 12:02:43 -04:00
|
|
|
});
|
2025-02-25 14:24:56 -05:00
|
|
|
});
|
|
|
|
|
|
2025-02-26 12:29:36 -05:00
|
|
|
describe('Private objects', () => {
|
|
|
|
|
let recipientUid;
|
|
|
|
|
|
|
|
|
|
before(async () => {
|
|
|
|
|
recipientUid = await user.create({ username: utils.generateUUID().slice(0, 8) });
|
2025-02-25 14:24:56 -05:00
|
|
|
});
|
|
|
|
|
|
2025-02-26 12:29:36 -05:00
|
|
|
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);
|
|
|
|
|
});
|
2025-02-25 14:24:56 -05:00
|
|
|
|
2025-02-26 12:29:36 -05:00
|
|
|
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));
|
|
|
|
|
});
|
2025-02-25 14:24:56 -05:00
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2025-03-21 14:16:33 -04:00
|
|
|
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() }));
|
2025-04-15 10:12:53 -04:00
|
|
|
activitypub._sent.clear();
|
2025-03-21 14:16:33 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
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}`));
|
|
|
|
|
});
|
2025-04-17 16:41:00 -04:00
|
|
|
|
|
|
|
|
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');
|
2025-04-23 12:47:16 -04:00
|
|
|
});
|
2025-03-21 14:16:33 -04:00
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
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));
|
|
|
|
|
});
|
|
|
|
|
});
|
2025-03-21 14:22:22 -04:00
|
|
|
|
|
|
|
|
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));
|
|
|
|
|
});
|
|
|
|
|
});
|
2025-03-21 14:16:33 -04:00
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2025-04-23 13:17:10 -04:00
|
|
|
describe('Inbox handling', () => {
|
2025-04-23 12:47:16 -04:00
|
|
|
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,
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2025-05-13 13:59:34 -04:00
|
|
|
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);
|
|
|
|
|
});
|
|
|
|
|
|
2025-05-16 11:43:26 -04:00
|
|
|
it('should create a new topic in a remote category if addressed (category same-origin)', async () => {
|
2025-05-13 13:59:34 -04:00
|
|
|
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 });
|
2025-05-16 11:43:26 -04:00
|
|
|
|
|
|
|
|
assert(await posts.exists(id));
|
|
|
|
|
|
|
|
|
|
const cid = await posts.getCidByPid(id);
|
|
|
|
|
assert.strictEqual(cid, remoteCid);
|
|
|
|
|
});
|
|
|
|
|
|
2025-07-25 10:57:30 -04:00
|
|
|
it('should create a new topic in cid -1 if a non-same origin remote category is addressed', async function () {
|
|
|
|
|
this.timeout(60000);
|
2025-05-16 11:43:26 -04:00
|
|
|
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);
|
|
|
|
|
|
|
|
|
|
await activitypub.inbox.create({ body: activity });
|
2025-05-13 13:59:34 -04:00
|
|
|
|
|
|
|
|
assert(await posts.exists(id));
|
|
|
|
|
|
|
|
|
|
const cid = await posts.getCidByPid(id);
|
|
|
|
|
assert.strictEqual(cid, -1);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2025-04-23 12:47:16 -04:00
|
|
|
describe('Announce', () => {
|
|
|
|
|
let cid;
|
|
|
|
|
|
|
|
|
|
before(async () => {
|
|
|
|
|
({ cid } = await categories.create({ name: utils.generateUUID().slice(0, 8) }));
|
|
|
|
|
});
|
|
|
|
|
|
2025-05-13 13:59:34 -04:00
|
|
|
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);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2025-05-14 11:49:12 -04:00
|
|
|
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);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2025-04-23 12:47:16 -04:00
|
|
|
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);
|
|
|
|
|
});
|
|
|
|
|
});
|
2025-04-23 13:17:10 -04:00
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
});
|
|
|
|
|
});
|
2025-04-23 12:47:16 -04:00
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2024-05-09 15:48:58 -04:00
|
|
|
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);
|
|
|
|
|
});
|
2024-07-05 12:00:19 -04:00
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
});
|
2024-05-09 15:48:58 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|