wildebeest/backend/test/mastodon/timelines.spec.ts

271 wiersze
11 KiB
TypeScript

import { strict as assert } from 'node:assert/strict'
import { insertReply } from 'wildebeest/backend/src/mastodon/reply'
import { createImage } from 'wildebeest/backend/src/activitypub/objects/image'
import { addFollowing, acceptFollowing } from 'wildebeest/backend/src/mastodon/follow'
import { createPublicNote, createPrivateNote } from 'wildebeest/backend/src/activitypub/objects/note'
import { addObjectInOutbox } from 'wildebeest/backend/src/activitypub/actors/outbox'
import { createPerson } from 'wildebeest/backend/src/activitypub/actors'
import { makeDB, assertCORS, assertJSON, makeCache } from '../utils'
import * as timelines_home from 'wildebeest/functions/api/v1/timelines/home'
import * as timelines_public from 'wildebeest/functions/api/v1/timelines/public'
import * as timelines from 'wildebeest/backend/src/mastodon/timeline'
import { insertLike } from 'wildebeest/backend/src/mastodon/like'
import { insertReblog } from 'wildebeest/backend/src/mastodon/reblog'
const userKEK = 'test_kek6'
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms))
const domain = 'cloudflare.com'
describe('Mastodon APIs', () => {
describe('timelines', () => {
test('home returns Notes in following Actors', 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')
const actor3 = await createPerson(domain, db, userKEK, 'sven3@cloudflare.com')
// Actor is following actor2, but not actor3.
await addFollowing(db, actor, actor2, 'not needed')
await acceptFollowing(db, actor, actor2)
// Actor 2 is posting
const firstNoteFromActor2 = await createPublicNote(domain, db, 'first status from actor2', actor2)
await addObjectInOutbox(db, actor2, firstNoteFromActor2)
await sleep(10)
await addObjectInOutbox(db, actor2, await createPublicNote(domain, db, 'second status from actor2', actor2))
await sleep(10)
await addObjectInOutbox(db, actor3, await createPublicNote(domain, db, 'first status from actor3', actor3))
await sleep(10)
await insertLike(db, actor, firstNoteFromActor2)
await insertReblog(db, actor, firstNoteFromActor2)
// Actor should only see posts from actor2 in the timeline
const connectedActor: any = actor
const data = await timelines.getHomeTimeline(domain, db, connectedActor)
assert.equal(data.length, 2)
assert(data[0].id)
assert.equal(data[0].content, 'second status from actor2')
assert.equal(data[0].account.username, 'sven2')
assert.equal(data[1].content, 'first status from actor2')
assert.equal(data[1].account.username, 'sven2')
assert.equal(data[1].favourites_count, 1)
assert.equal(data[1].reblogs_count, 1)
})
test("home doesn't show private Notes from followed actors", async () => {
const db = await makeDB()
const actor1 = await createPerson(domain, db, userKEK, 'sven@cloudflare.com')
const actor2 = await createPerson(domain, db, userKEK, 'sven2@cloudflare.com')
const actor3 = await createPerson(domain, db, userKEK, 'sven3@cloudflare.com')
// actor3 follows actor1 and actor2
await addFollowing(db, actor3, actor1, 'not needed')
await acceptFollowing(db, actor3, actor1)
await addFollowing(db, actor3, actor2, 'not needed')
await acceptFollowing(db, actor3, actor2)
// actor2 sends a DM to actor1
const note = await createPrivateNote(domain, db, 'DM', actor2, actor1)
await addObjectInOutbox(db, actor2, note, undefined, actor1.id.toString())
// actor3 shouldn't see the private note
const data = await timelines.getHomeTimeline(domain, db, actor3)
assert.equal(data.length, 0)
})
test("public doesn't show private Notes", async () => {
const db = await makeDB()
const actor1 = await createPerson(domain, db, userKEK, 'sven@cloudflare.com')
const actor2 = await createPerson(domain, db, userKEK, 'sven2@cloudflare.com')
// actor2 sends a DM to actor1
const note = await createPrivateNote(domain, db, 'DM', actor2, actor1)
await addObjectInOutbox(db, actor2, note, undefined, actor1.id.toString())
const data = await timelines.getPublicTimeline(domain, db, timelines.LocalPreference.NotSet)
assert.equal(data.length, 0)
})
test('home returns Notes from ourself', async () => {
const db = await makeDB()
const actor = await createPerson(domain, db, userKEK, 'sven@cloudflare.com')
// Actor is posting
await addObjectInOutbox(db, actor, await createPublicNote(domain, db, 'status from myself', actor))
// Actor should only see posts from actor2 in the timeline
const connectedActor = actor
const data = await timelines.getHomeTimeline(domain, db, connectedActor)
assert.equal(data.length, 1)
assert(data[0].id)
assert.equal(data[0].content, 'status from myself')
assert.equal(data[0].account.username, 'sven')
})
test('home returns cache', async () => {
const db = await makeDB()
const connectedActor = await createPerson(domain, db, userKEK, 'sven@cloudflare.com')
const cache = makeCache()
await cache.put(connectedActor.id + '/timeline/home', 12345)
const req = new Request('https://' + domain)
const data = await timelines_home.handleRequest(req, cache, connectedActor)
assert.equal(await data.json(), 12345)
})
test('home returns empty if not in cache', async () => {
const db = await makeDB()
const connectedActor = await createPerson(domain, db, userKEK, 'sven@cloudflare.com')
const cache = makeCache()
const req = new Request('https://' + domain)
const data = await timelines_home.handleRequest(req, cache, connectedActor)
const posts = await data.json<Array<any>>()
assert.equal(posts.length, 0)
})
test('public returns Notes', 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')
const statusFromActor = await createPublicNote(domain, db, 'status from actor', actor)
await addObjectInOutbox(db, actor, statusFromActor)
await sleep(10)
await addObjectInOutbox(db, actor2, await createPublicNote(domain, db, 'status from actor2', actor2))
await insertLike(db, actor, statusFromActor)
await insertReblog(db, actor, statusFromActor)
const res = await timelines_public.handleRequest(domain, db)
assert.equal(res.status, 200)
assertJSON(res)
assertCORS(res)
const data = await res.json<any>()
assert.equal(data.length, 2)
assert(data[0].id)
assert.equal(data[0].content, 'status from actor2')
assert.equal(data[0].account.username, 'sven2')
assert.equal(data[1].content, 'status from actor')
assert.equal(data[1].account.username, 'sven')
assert.equal(data[1].favourites_count, 1)
assert.equal(data[1].reblogs_count, 1)
// if we request only remote objects nothing should be returned
const remoteRes = await timelines_public.handleRequest(domain, db, {
local: false,
remote: true,
only_media: false,
})
assert.equal(remoteRes.status, 200)
assertJSON(remoteRes)
assertCORS(remoteRes)
const remoteData = await remoteRes.json<any>()
assert.equal(remoteData.length, 0)
})
test('public includes attachment', async () => {
const db = await makeDB()
const actor = await createPerson(domain, db, userKEK, 'sven@cloudflare.com')
const properties = { url: 'https://example.com/image.jpg' }
const mediaAttachments = [await createImage(domain, db, actor, properties)]
const note = await createPublicNote(domain, db, 'status from actor', actor, mediaAttachments)
await addObjectInOutbox(db, actor, note)
const res = await timelines_public.handleRequest(domain, db)
assert.equal(res.status, 200)
const data = await res.json<any>()
assert.equal(data.length, 1)
assert.equal(data[0].media_attachments.length, 1)
assert.equal(data[0].media_attachments[0].type, 'image')
assert.equal(data[0].media_attachments[0].url, properties.url)
})
test('public timeline uses published_date', async () => {
const db = await makeDB()
const actor = await createPerson(domain, db, userKEK, 'sven@cloudflare.com')
const note1 = await createPublicNote(domain, db, 'note1', actor)
const note2 = await createPublicNote(domain, db, 'note2', actor)
const note3 = await createPublicNote(domain, db, 'note3', actor)
await addObjectInOutbox(db, actor, note1, '2022-12-10T23:48:38Z')
await addObjectInOutbox(db, actor, note2, '2000-12-10T23:48:38Z')
await addObjectInOutbox(db, actor, note3, '2048-12-10T23:48:38Z')
const res = await timelines_public.handleRequest(domain, db)
assert.equal(res.status, 200)
const data = await res.json<any>()
assert.equal(data[0].content, 'note3')
assert.equal(data[1].content, 'note1')
assert.equal(data[2].content, 'note2')
})
test('timelines hides and counts replies', async () => {
const db = await makeDB()
const actor = await createPerson(domain, db, userKEK, 'sven@cloudflare.com')
const note = await createPublicNote(domain, db, 'a post', actor)
await addObjectInOutbox(db, actor, note)
await sleep(10)
const inReplyTo = note.id
const reply = await createPublicNote(domain, db, 'a reply', actor, [], { inReplyTo })
await addObjectInOutbox(db, actor, reply)
await sleep(10)
await insertReply(db, actor, reply, note)
const connectedActor: any = actor
{
const data = await timelines.getHomeTimeline(domain, db, connectedActor)
assert.equal(data.length, 1)
assert.equal(data[0].content, 'a post')
assert.equal(data[0].replies_count, 1)
}
{
const data = await timelines.getPublicTimeline(domain, db, timelines.LocalPreference.NotSet)
assert.equal(data.length, 1)
assert.equal(data[0].content, 'a post')
assert.equal(data[0].replies_count, 1)
}
})
test('show status reblogged', async () => {
const db = await makeDB()
const actor = await createPerson(domain, db, userKEK, 'sven@cloudflare.com')
const note = await createPublicNote(domain, db, 'a post', actor)
await addObjectInOutbox(db, actor, note)
await insertReblog(db, actor, note)
const connectedActor: any = actor
const data = await timelines.getHomeTimeline(domain, db, connectedActor)
assert.equal(data.length, 1)
assert.equal(data[0].reblogged, true)
})
test('show status favourited', async () => {
const db = await makeDB()
const actor = await createPerson(domain, db, userKEK, 'sven@cloudflare.com')
const note = await createPublicNote(domain, db, 'a post', actor)
await addObjectInOutbox(db, actor, note)
await insertLike(db, actor, note)
const connectedActor: any = actor
const data = await timelines.getHomeTimeline(domain, db, connectedActor)
assert.equal(data.length, 1)
assert.equal(data[0].favourited, true)
})
})
})