feat: refactor out.announce.topic to allow user announces, refactor tests to accommodate

This commit is contained in:
Julian Lam
2026-01-07 10:39:03 -05:00
parent e717f00edd
commit 874ffd7b26
2 changed files with 118 additions and 55 deletions

View File

@@ -305,12 +305,19 @@ Out.dislike.note = enabledCheck(async (uid, pid) => {
Out.announce = {}; Out.announce = {};
Out.announce.topic = enabledCheck(async (tid) => { Out.announce.topic = enabledCheck(async (tid, uid) => {
const { mainPid: pid, cid } = await topics.getTopicFields(tid, ['mainPid', 'cid']); const { mainPid: pid, cid } = await topics.getTopicFields(tid, ['mainPid', 'cid']);
// Only local categories can announce if (uid) {
if (!utils.isNumber(cid) || parseInt(cid, 10) < 1) { const exists = await user.exists(uid);
return; if (!exists || !utils.isNumber(cid)) {
return;
}
} else {
// Only local categories can announce
if (!utils.isNumber(cid) || parseInt(cid, 10) < 1) {
return;
}
} }
const authorUid = await posts.getPostField(pid, 'uid'); // author const authorUid = await posts.getPostField(pid, 'uid'); // author
@@ -323,16 +330,23 @@ Out.announce.topic = enabledCheck(async (tid) => {
const { to, cc, targets } = await activitypub.buildRecipients({ const { to, cc, targets } = await activitypub.buildRecipients({
id: pid, id: pid,
to: [activitypub._constants.publicAddress], to: [activitypub._constants.publicAddress],
}, { cid }); }, uid ? { uid } : { cid });
if (!utils.isNumber(authorUid)) { if (!utils.isNumber(authorUid)) {
cc.push(authorUid); cc.push(authorUid);
targets.add(authorUid); targets.add(authorUid);
} }
await activitypub.send('cid', cid, Array.from(targets), { const payload = uid ? {
id: `${nconf.get('url')}/post/${encodeURIComponent(pid)}#activity/announce/uid/${uid}`,
type: 'Announce',
actor: `${nconf.get('url')}/uid/${uid}`,
} : {
id: `${nconf.get('url')}/post/${encodeURIComponent(pid)}#activity/announce/cid/${cid}`, id: `${nconf.get('url')}/post/${encodeURIComponent(pid)}#activity/announce/cid/${cid}`,
type: 'Announce', type: 'Announce',
actor: `${nconf.get('url')}/category/${cid}`, actor: `${nconf.get('url')}/category/${cid}`,
};
await activitypub.send(uid ? 'uid' : 'cid', uid || cid, Array.from(targets), {
...payload,
to, to,
cc, cc,
object: utils.isNumber(pid) ? `${nconf.get('url')}/post/${pid}` : pid, object: utils.isNumber(pid) ? `${nconf.get('url')}/post/${pid}` : pid,

View File

@@ -22,84 +22,133 @@ describe('Outbound activities module', () => {
}); });
describe('.announce', () => { describe('.announce', () => {
describe('.topic() (remote topic; by cid)', () => { function commonTests() {
let pid; it('should not error when called', async function () {
let note; await activitypub.out.announce.topic(this.tid, this.uid);
let tid; const { payload, targets } = Array.from(activitypub._sent).pop()[1];
let cid; this.payload = payload;
this.targets = targets;
});
before(async () => { it('should send an Announce activity', function () {
({ id: pid, note } = helpers.mocks.note()); assert.strictEqual(activitypub._sent.size, 1);
({ cid } = await categories.create({ name: utils.generateUUID() })); assert.strictEqual(this.payload.type, 'Announce');
});
it('should contain the main post\'s pid in object', function () {
assert.strictEqual(this.payload.object, this.pid);
});
it('should have actor as the calling user or category as appropriate', function () {
if (this.uid) {
assert.strictEqual(this.payload.actor, `${nconf.get('url')}/uid/${this.uid}`);
} else {
assert.strictEqual(this.payload.actor, `${nconf.get('url')}/category/${this.cid}`);
}
});
}
describe('.topic() (remote topic; by cid)', () => {
before(async function () {
const { id: pid, note } = helpers.mocks.note();
const { cid } = await categories.create({ name: utils.generateUUID() });
await activitypub.notes.assert(0, pid, { skipChecks: 1, cid }); await activitypub.notes.assert(0, pid, { skipChecks: 1, cid });
tid = await posts.getPostField(pid, 'tid');
this.pid = pid;
this.note = note;
this.cid = cid;
this.tid = await posts.getPostField(pid, 'tid');
}); });
after(() => { after(() => {
activitypub._sent.clear(); activitypub._sent.clear();
}); });
it('should not error when called', async () => { commonTests();
await activitypub.out.announce.topic(tid);
it('should include the category\'s followers collection in cc', function () {
assert(this.payload.cc.includes(`${nconf.get('url')}/category/${this.cid}/followers`));
}); });
it('should send an Announce activity', () => { it('should include the author in cc', function () {
assert.strictEqual(activitypub._sent.size, 1); assert(this.payload.cc.includes(this.note.attributedTo));
const { payload } = Array.from(activitypub._sent).pop()[1];
assert.strictEqual(payload.type, 'Announce');
}); });
it('should contain the main post\'s pid in object', () => { it('should include the author in targets', function () {
const { payload } = Array.from(activitypub._sent).pop()[1]; assert(this.targets.includes(this.note.attributedTo));
assert.strictEqual(payload.object, pid);
});
it('should include the category\'s followers collection in cc', () => {
const { payload } = Array.from(activitypub._sent).pop()[1];
assert(payload.cc.includes(`${nconf.get('url')}/category/${cid}/followers`));
});
it('should include the author in cc', () => {
const { payload } = Array.from(activitypub._sent).pop()[1];
assert(payload.cc.includes(note.attributedTo));
});
it('should include the author in targets', () => {
const { targets } = Array.from(activitypub._sent).pop()[1];
assert(targets.includes(note.attributedTo));
}); });
}); });
describe('.topic() (local topic; by cid)', () => { describe('.topic() (local topic; by cid)', () => {
let uid; before(async function () {
let tid; const uid = await user.create({ username: utils.generateUUID().slice(0, 10) });
let cid; const { cid } = await categories.create({ name: utils.generateUUID() });
const { postData, topicData } = await topics.post({
before(async () => {
uid = await user.create({ username: utils.generateUUID().slice(0, 10) });
({ cid } = await categories.create({ name: utils.generateUUID() }));
const { topicData } = await topics.post({
cid, uid, cid, uid,
title: utils.generateUUID(), title: utils.generateUUID(),
content: utils.generateUUID(), content: utils.generateUUID(),
}); });
({ tid } = topicData);
this.tid = topicData.tid;
this.cid = cid;
this.pid = `${nconf.get('url')}/post/${topicData.mainPid}`;
this.note = await activitypub.mocks.notes.public(postData);
}); });
after(() => { after(() => {
activitypub._sent.clear(); activitypub._sent.clear();
}); });
it('should not error when called', async () => { commonTests();
await activitypub.out.announce.topic(tid);
it('should include the topic\'s mainPid in object', async function () {
const mainPid = await topics.getTopicField(this.tid, 'mainPid');
assert.strictEqual(this.payload.object, `${nconf.get('url')}/post/${mainPid}`);
});
});
describe('.topic() (remote topic; by uid)', () => {
before(async function () {
const uid = await user.create({ username: utils.generateUUID().slice(0, 10) });
const { id: pid, note } = helpers.mocks.note();
await activitypub.notes.assert(0, pid, { skipChecks: 1 });
this.pid = pid;
this.note = note;
this.tid = await posts.getPostField(pid, 'tid');
this.uid = uid;
}); });
it('should include the topic\'s mainPid in object', async () => { after(() => {
const mainPid = await topics.getTopicField(tid, 'mainPid'); activitypub._sent.clear();
const { payload } = Array.from(activitypub._sent).pop()[1];
assert.strictEqual(payload.object, `${nconf.get('url')}/post/${mainPid}`);
}); });
commonTests();
});
describe('.topic() (local topic; by uid)', () => {
before(async function () {
const uid = await user.create({ username: utils.generateUUID().slice(0, 10) });
const { cid } = await categories.create({ name: utils.generateUUID() });
const { postData, topicData } = await topics.post({
cid, uid,
title: utils.generateUUID(),
content: utils.generateUUID(),
});
this.tid = topicData.tid;
this.cid = cid;
this.pid = `${nconf.get('url')}/post/${topicData.mainPid}`;
this.note = await activitypub.mocks.notes.public(postData);
this.uid = uid;
});
after(() => {
activitypub._sent.clear();
});
commonTests();
}); });
}); });
}); });