kopia lustrzana https://github.com/cloudflare/wildebeest
147 wiersze
5.6 KiB
TypeScript
147 wiersze
5.6 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, 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 db = await makeDB()
|
|
const connectedActor = await createPerson(domain, db, userKEK, 'sven@cloudflare.com')
|
|
const kv_cache: any = {
|
|
async get(key: string) {
|
|
assert.equal(key, connectedActor.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 actor = await createPerson(domain, db, userKEK, 'sven@cloudflare.com')
|
|
const fromActor = await createPerson(domain, db, userKEK, 'from@cloudflare.com')
|
|
|
|
const connectedActor = actor
|
|
const note = await createPublicNote(domain, db, 'my first status', connectedActor)
|
|
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 = await createPerson(domain, db, userKEK, 'sven@cloudflare.com')
|
|
const fromActor = 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 = 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 = await createPerson(domain, db, userKEK, 'from@cloudflare.com')
|
|
await sendLikeNotification(db, fromActor, actor, 'notifid')
|
|
})
|
|
})
|
|
})
|