import { makeDB } from '../utils'
import { createPublicNote } from 'wildebeest/backend/src/activitypub/objects/note'
import type { JWK } from 'wildebeest/backend/src/webpush/jwk'
import { strict as assert } from 'node:assert/strict'
import { cacheObject, getObjectById } from 'wildebeest/backend/src/activitypub/objects/'
import { addFollowing } from 'wildebeest/backend/src/mastodon/follow'
import * as activityHandler from 'wildebeest/backend/src/activitypub/activities/handle'
import { createPerson } from 'wildebeest/backend/src/activitypub/actors'
import { ObjectsRow } from 'wildebeest/backend/src/types/objects'
const adminEmail = 'admin@example.com'
const domain = 'cloudflare.com'
const userKEK = 'test_kek15'
const vapidKeys = {} as JWK
describe('ActivityPub', () => {
describe('handle Activity', () => {
describe('Announce', () => {
test('records reblog in db', async () => {
const db = await makeDB()
const actorA = await createPerson(domain, db, userKEK, 'a@cloudflare.com')
const actorB = await createPerson(domain, db, userKEK, 'b@cloudflare.com')
const note = await createPublicNote(domain, db, 'my first status', actorA)
const activity: any = {
type: 'Announce',
actor: actorB.id,
object: note.id,
}
await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys)
const entry = await db.prepare('SELECT * FROM actor_reblogs').first<{
actor_id: URL
object_id: URL
}>()
assert.equal(entry.actor_id.toString(), actorB.id.toString())
assert.equal(entry.object_id.toString(), note.id.toString())
})
test('creates notification', async () => {
const db = await makeDB()
const actorA = await createPerson(domain, db, userKEK, 'a@cloudflare.com')
const actorB = await createPerson(domain, db, userKEK, 'b@cloudflare.com')
const note = await createPublicNote(domain, db, 'my first status', actorA)
const activity: any = {
type: 'Announce',
actor: actorB.id,
object: note.id,
}
await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys)
const entry = await db.prepare('SELECT * FROM actor_notifications').first<{
type: string
actor_id: URL
from_actor_id: URL
}>()
assert(entry)
assert.equal(entry.type, 'reblog')
assert.equal(entry.actor_id.toString(), actorA.id.toString())
assert.equal(entry.from_actor_id.toString(), actorB.id.toString())
})
})
describe('Like', () => {
test('records like in db', async () => {
const db = await makeDB()
const actorA = await createPerson(domain, db, userKEK, 'a@cloudflare.com')
const actorB = await createPerson(domain, db, userKEK, 'b@cloudflare.com')
const note = await createPublicNote(domain, db, 'my first status', actorA)
const activity: any = {
type: 'Like',
actor: actorB.id,
object: note.id,
}
await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys)
const entry = await db.prepare('SELECT * FROM actor_favourites').first<{ actor_id: URL; object_id: URL }>()
assert.equal(entry.actor_id.toString(), actorB.id.toString())
assert.equal(entry.object_id.toString(), note.id.toString())
})
test('creates notification', async () => {
const db = await makeDB()
const actorA = await createPerson(domain, db, userKEK, 'a@cloudflare.com')
const actorB = await createPerson(domain, db, userKEK, 'b@cloudflare.com')
const note = await createPublicNote(domain, db, 'my first status', actorA)
const activity: any = {
type: 'Like',
actor: actorB.id,
object: note.id,
}
await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys)
const entry = await db.prepare('SELECT * FROM actor_notifications').first<{
type: string
actor_id: URL
from_actor_id: URL
}>()
assert.equal(entry.type, 'favourite')
assert.equal(entry.actor_id.toString(), actorA.id.toString())
assert.equal(entry.from_actor_id.toString(), actorB.id.toString())
})
test('records like in db', async () => {
const db = await makeDB()
const actorA = await createPerson(domain, db, userKEK, 'a@cloudflare.com')
const actorB = await createPerson(domain, db, userKEK, 'b@cloudflare.com')
const note = await createPublicNote(domain, db, 'my first status', actorA)
const activity: any = {
type: 'Like',
actor: actorB.id,
object: note.id,
}
await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys)
const entry = await db.prepare('SELECT * FROM actor_favourites').first<{
actor_id: URL
object_id: URL
}>()
assert.equal(entry.actor_id.toString(), actorB.id.toString())
assert.equal(entry.object_id.toString(), note.id.toString())
})
})
describe('Accept', () => {
beforeEach(() => {
globalThis.fetch = async (input: RequestInfo) => {
throw new Error('unexpected request to ' + input)
}
})
test('Accept follow request stores in db', async () => {
const db = await makeDB()
const actor = await createPerson(domain, db, userKEK, 'sven@cloudflare.com')
const actor2 = await createPerson(domain, db, userKEK, 'sven2@cloudflare.com')
await addFollowing(db, actor, actor2, 'not needed')
const activity = {
'@context': 'https://www.w3.org/ns/activitystreams',
type: 'Accept',
actor: { id: 'https://' + domain + '/ap/users/sven2' },
object: {
type: 'Follow',
actor: actor.id,
object: 'https://' + domain + '/ap/users/sven2',
},
}
await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys)
const row = await db
.prepare(`SELECT target_actor_id, state FROM actor_following WHERE actor_id=?`)
.bind(actor.id.toString())
.first<{
target_actor_id: string
state: string
}>()
assert(row)
assert.equal(row.target_actor_id, 'https://' + domain + '/ap/users/sven2')
assert.equal(row.state, 'accepted')
})
test('Object must be an object', async () => {
const db = await makeDB()
await createPerson(domain, db, userKEK, 'sven@cloudflare.com')
const activity = {
'@context': 'https://www.w3.org/ns/activitystreams',
type: 'Accept',
actor: 'https://example.com/actor',
object: 'a',
}
await assert.rejects(activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys), {
message: '`activity.object` must be of type object',
})
})
})
describe('Create', () => {
test('Object must be an object', async () => {
const db = await makeDB()
await createPerson(domain, db, userKEK, 'sven@cloudflare.com')
const activity = {
'@context': 'https://www.w3.org/ns/activitystreams',
type: 'Create',
actor: 'https://example.com/actor',
object: 'a',
}
await assert.rejects(activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys), {
message: '`activity.object` must be of type object',
})
})
test('Note to inbox stores in DB', async () => {
const db = await makeDB()
const actor = await createPerson(domain, db, userKEK, 'sven@cloudflare.com')
const activity: any = {
type: 'Create',
actor: actor.id.toString(),
to: [actor.id.toString()],
cc: [],
object: {
id: 'https://example.com/note1',
type: 'Note',
content: 'test note',
},
}
await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys)
const entry = await db
.prepare('SELECT objects.* FROM inbox_objects INNER JOIN objects ON objects.id=inbox_objects.object_id')
.first foo alert("evil")