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

155 wiersze
5.7 KiB
TypeScript

import * as notifications_get from 'wildebeest/functions/api/v1/notifications/[id]'
import { createPublicNote } from 'wildebeest/backend/src/activitypub/objects/note'
import { createNotification, insertFollowNotification } from 'wildebeest/backend/src/mastodon/notification'
import { createPerson } from 'wildebeest/backend/src/activitypub/actors'
import * as notifications from 'wildebeest/functions/api/v1/notifications'
import { makeDB, assertJSON, assertCORS, createTestClient } from '../utils'
import { strict as assert } from 'node:assert/strict'
import { sendLikeNotification } from 'wildebeest/backend/src/mastodon/notification'
import { createSubscription } from 'wildebeest/backend/src/mastodon/subscription'
import { generateVAPIDKeys, configure } from 'wildebeest/backend/src/config'
import { arrayBufferToBase64 } from 'wildebeest/backend/src/utils/key-ops'
import { getNotifications } from 'wildebeest/backend/src/mastodon/notification'
const userKEK = 'test_kek15'
const domain = 'cloudflare.com'
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms))
function parseCryptoKey(s: string): any {
const parts = s.split(';')
const out: any = {}
for (let i = 0, len = parts.length; i < len; i++) {
const parts2 = parts[i].split('=')
out[parts2[0]] = parts2[1]
}
return out
}
describe('Mastodon APIs', () => {
describe('notifications', () => {
test('returns notifications stored in KV cache', async () => {
const connectedActor: any = { id: 'id' }
const kv_cache: any = {
async get(key: string) {
assert.equal(key, 'id/notifications')
return 'cached data'
},
}
const req = new Request('https://' + domain)
const data = await notifications.handleRequest(req, kv_cache, connectedActor)
assert.equal(await data.text(), 'cached data')
})
test('returns notifications stored in db', async () => {
const db = await makeDB()
const actorId = await createPerson(domain, db, userKEK, 'sven@cloudflare.com')
const fromActorId = await createPerson(domain, db, userKEK, 'from@cloudflare.com')
const connectedActor: any = {
id: actorId,
}
const note = await createPublicNote(domain, db, 'my first status', connectedActor)
const fromActor: any = {
id: fromActorId,
}
await insertFollowNotification(db, connectedActor, fromActor)
await sleep(10)
await createNotification(db, 'favourite', connectedActor, fromActor, note)
await sleep(10)
await createNotification(db, 'mention', connectedActor, fromActor, note)
const notifications: any = await getNotifications(db, connectedActor)
assert.equal(notifications[0].type, 'mention')
assert.equal(notifications[0].account.username, 'from')
assert.equal(notifications[0].status.id, note.mastodonId)
assert.equal(notifications[1].type, 'favourite')
assert.equal(notifications[1].account.username, 'from')
assert.equal(notifications[1].status.id, note.mastodonId)
assert.equal(notifications[1].status.account.username, 'sven')
assert.equal(notifications[2].type, 'follow')
assert.equal(notifications[2].account.username, 'from')
assert.equal(notifications[2].status, undefined)
})
test('get single non existant notification', async () => {
const db = await makeDB()
const actor: any = { id: await createPerson(domain, db, userKEK, 'sven@cloudflare.com') }
const fromActor: any = { id: await createPerson(domain, db, userKEK, 'from@cloudflare.com') }
const note = await createPublicNote(domain, db, 'my first status', actor)
await createNotification(db, 'favourite', actor, fromActor, note)
const res = await notifications_get.handleRequest(domain, '1', db, actor)
assert.equal(res.status, 200)
assertJSON(res)
const data = await res.json<any>()
assert.equal(data.id, '1')
assert.equal(data.type, 'favourite')
assert.equal(data.account.acct, 'from@cloudflare.com')
assert.equal(data.status.content, 'my first status')
})
test('send like notification', async () => {
const db = await makeDB()
await generateVAPIDKeys(db)
await configure(db, { title: 'title', description: 'a', email: 'email' })
const clientKeys = (await crypto.subtle.generateKey({ name: 'ECDSA', namedCurve: 'P-256' }, true, [
'sign',
'verify',
])) as CryptoKeyPair
globalThis.fetch = async (input: RequestInfo, data: any) => {
if (input === 'https://push.com') {
assert(data.headers['Authorization'].includes('WebPush'))
const cryptoKeyHeader = parseCryptoKey(data.headers['Crypto-Key'])
assert(cryptoKeyHeader.dh)
assert(cryptoKeyHeader.p256ecdsa)
// Ensure the data has a valid signature using the client public key
const sign = await crypto.subtle.sign({ name: 'ECDSA', hash: 'SHA-256' }, clientKeys.privateKey, data.body)
assert(await crypto.subtle.verify({ name: 'ECDSA', hash: 'SHA-256' }, clientKeys.publicKey, sign, data.body))
// TODO: eventually decrypt what the server pushed
return new Response()
}
throw new Error('unexpected request to ' + input)
}
const client = await createTestClient(db)
const actor: any = { id: await createPerson(domain, db, userKEK, 'sven@cloudflare.com') }
const p256dh = arrayBufferToBase64((await crypto.subtle.exportKey('raw', clientKeys.publicKey)) as ArrayBuffer)
const auth = arrayBufferToBase64(crypto.getRandomValues(new Uint8Array(16)))
await createSubscription(db, actor, client, {
subscription: {
endpoint: 'https://push.com',
keys: {
p256dh,
auth,
},
},
data: {
alerts: {},
policy: 'all',
},
})
const fromActor: any = {
id: await createPerson(domain, db, userKEK, 'from@cloudflare.com'),
icon: { url: 'icon.com' },
}
await sendLikeNotification(db, fromActor, actor, 'notifid')
})
})
})