| 
									
										
										
										
											2022-12-05 20:14:56 +00:00
										 |  |  | import { strict as assert } from 'node:assert/strict' | 
					
						
							|  |  |  | import { insertReply } from 'wildebeest/backend/src/mastodon/reply' | 
					
						
							|  |  |  | import { getMentions } from 'wildebeest/backend/src/mastodon/status' | 
					
						
							|  |  |  | import { addObjectInOutbox } from 'wildebeest/backend/src/activitypub/actors/outbox' | 
					
						
							|  |  |  | import { createPublicNote } from 'wildebeest/backend/src/activitypub/objects/note' | 
					
						
							|  |  |  | import { createImage } from 'wildebeest/backend/src/activitypub/objects/image' | 
					
						
							|  |  |  | import * as statuses from 'wildebeest/functions/api/v1/statuses' | 
					
						
							|  |  |  | import * as statuses_get from 'wildebeest/functions/api/v1/statuses/[id]' | 
					
						
							|  |  |  | import * as statuses_favourite from 'wildebeest/functions/api/v1/statuses/[id]/favourite' | 
					
						
							|  |  |  | import * as statuses_reblog from 'wildebeest/functions/api/v1/statuses/[id]/reblog' | 
					
						
							|  |  |  | import * as statuses_context from 'wildebeest/functions/api/v1/statuses/[id]/context' | 
					
						
							|  |  |  | import { createPerson } from 'wildebeest/backend/src/activitypub/actors' | 
					
						
							|  |  |  | import { insertLike } from 'wildebeest/backend/src/mastodon/like' | 
					
						
							|  |  |  | import { insertReblog } from 'wildebeest/backend/src/mastodon/reblog' | 
					
						
							| 
									
										
										
										
											2023-01-13 10:56:24 +00:00
										 |  |  | import { isUrlValid, makeDB, assertJSON, streamToArrayBuffer, makeQueue } from '../utils' | 
					
						
							| 
									
										
										
										
											2023-01-16 14:16:54 +00:00
										 |  |  | import * as activities from 'wildebeest/backend/src/activitypub/activities' | 
					
						
							| 
									
										
										
										
											2023-01-13 10:56:24 +00:00
										 |  |  | import { addFollowing, acceptFollowing } from 'wildebeest/backend/src/mastodon/follow' | 
					
						
							|  |  |  | import { MessageType } from 'wildebeest/backend/src/types/queue' | 
					
						
							| 
									
										
										
										
											2022-12-05 20:14:56 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | const userKEK = 'test_kek4' | 
					
						
							|  |  |  | const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms)) | 
					
						
							|  |  |  | const domain = 'cloudflare.com' | 
					
						
							| 
									
										
										
										
											2023-01-16 19:05:45 +00:00
										 |  |  | const kv_cache: any = { | 
					
						
							|  |  |  | 	async put() {}, | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2022-12-05 20:14:56 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | describe('Mastodon APIs', () => { | 
					
						
							|  |  |  | 	describe('statuses', () => { | 
					
						
							|  |  |  | 		test('create new status missing params', async () => { | 
					
						
							|  |  |  | 			const db = await makeDB() | 
					
						
							| 
									
										
										
										
											2023-01-13 10:56:24 +00:00
										 |  |  | 			const queue = makeQueue() | 
					
						
							| 
									
										
										
										
											2022-12-05 20:14:56 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			const body = { status: 'my status' } | 
					
						
							|  |  |  | 			const req = new Request('https://example.com', { | 
					
						
							|  |  |  | 				method: 'POST', | 
					
						
							| 
									
										
										
										
											2023-01-11 08:17:48 +00:00
										 |  |  | 				headers: { 'content-type': 'application/json' }, | 
					
						
							| 
									
										
										
										
											2022-12-05 20:14:56 +00:00
										 |  |  | 				body: JSON.stringify(body), | 
					
						
							|  |  |  | 			}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			const connectedActor: any = {} | 
					
						
							| 
									
										
										
										
											2023-01-16 19:05:45 +00:00
										 |  |  | 			const res = await statuses.handleRequest(req, db, connectedActor, userKEK, queue, kv_cache) | 
					
						
							| 
									
										
										
										
											2022-12-05 20:14:56 +00:00
										 |  |  | 			assert.equal(res.status, 400) | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		test('create new status creates Note', async () => { | 
					
						
							|  |  |  | 			const db = await makeDB() | 
					
						
							| 
									
										
										
										
											2023-01-13 10:56:24 +00:00
										 |  |  | 			const queue = makeQueue() | 
					
						
							| 
									
										
										
										
											2023-01-05 09:33:46 +00:00
										 |  |  | 			const actor = await createPerson(domain, db, userKEK, 'sven@cloudflare.com') | 
					
						
							| 
									
										
										
										
											2022-12-05 20:14:56 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			const body = { | 
					
						
							|  |  |  | 				status: 'my status', | 
					
						
							|  |  |  | 				visibility: 'public', | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			const req = new Request('https://example.com', { | 
					
						
							|  |  |  | 				method: 'POST', | 
					
						
							| 
									
										
										
										
											2023-01-11 08:17:48 +00:00
										 |  |  | 				headers: { 'content-type': 'application/json' }, | 
					
						
							| 
									
										
										
										
											2022-12-05 20:14:56 +00:00
										 |  |  | 				body: JSON.stringify(body), | 
					
						
							|  |  |  | 			}) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-05 09:33:46 +00:00
										 |  |  | 			const connectedActor = actor | 
					
						
							| 
									
										
										
										
											2023-01-16 19:05:45 +00:00
										 |  |  | 			const res = await statuses.handleRequest(req, db, connectedActor, userKEK, queue, kv_cache) | 
					
						
							| 
									
										
										
										
											2022-12-05 20:14:56 +00:00
										 |  |  | 			assert.equal(res.status, 200) | 
					
						
							|  |  |  | 			assertJSON(res) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			const data = await res.json<any>() | 
					
						
							|  |  |  | 			assert(data.uri.includes('example.com')) | 
					
						
							|  |  |  | 			assert(data.uri.includes(data.id)) | 
					
						
							|  |  |  | 			// Required fields from https://github.com/mastodon/mastodon-android/blob/master/mastodon/src/main/java/org/joinmastodon/android/model/Status.java
 | 
					
						
							|  |  |  | 			assert(data.created_at !== undefined) | 
					
						
							|  |  |  | 			assert(data.account !== undefined) | 
					
						
							|  |  |  | 			assert(data.visibility !== undefined) | 
					
						
							|  |  |  | 			assert(data.spoiler_text !== undefined) | 
					
						
							|  |  |  | 			assert(data.media_attachments !== undefined) | 
					
						
							|  |  |  | 			assert(data.mentions !== undefined) | 
					
						
							|  |  |  | 			assert(data.tags !== undefined) | 
					
						
							|  |  |  | 			assert(data.emojis !== undefined) | 
					
						
							|  |  |  | 			assert(!isUrlValid(data.id)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			const row = await db | 
					
						
							|  |  |  | 				.prepare( | 
					
						
							|  |  |  | 					`
 | 
					
						
							|  |  |  |           SELECT | 
					
						
							|  |  |  |               json_extract(properties, '$.content') as content, | 
					
						
							|  |  |  |               original_actor_id, | 
					
						
							|  |  |  |               original_object_id | 
					
						
							|  |  |  |           FROM objects | 
					
						
							|  |  |  |         `
 | 
					
						
							|  |  |  | 				) | 
					
						
							|  |  |  | 				.first() | 
					
						
							|  |  |  | 			assert.equal(row.content, 'my status') | 
					
						
							| 
									
										
										
										
											2023-01-05 09:33:46 +00:00
										 |  |  | 			assert.equal(row.original_actor_id.toString(), actor.id.toString()) | 
					
						
							| 
									
										
										
										
											2022-12-05 20:14:56 +00:00
										 |  |  | 			assert.equal(row.original_object_id, null) | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-16 19:05:45 +00:00
										 |  |  | 		test('create new status regenerates the timeline and contains post', async () => { | 
					
						
							|  |  |  | 			const db = await makeDB() | 
					
						
							|  |  |  | 			const queue = makeQueue() | 
					
						
							|  |  |  | 			const actor = await createPerson(domain, db, userKEK, 'sven@cloudflare.com') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			let cache = null | 
					
						
							|  |  |  | 			const kv_cache: any = { | 
					
						
							|  |  |  | 				async put(key: string, value: any) { | 
					
						
							|  |  |  | 					assert.equal(key, actor.id + '/timeline/home') | 
					
						
							|  |  |  | 					cache = value | 
					
						
							|  |  |  | 				}, | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			const body = { | 
					
						
							|  |  |  | 				status: 'my status', | 
					
						
							|  |  |  | 				visibility: 'public', | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			const req = new Request('https://example.com', { | 
					
						
							|  |  |  | 				method: 'POST', | 
					
						
							|  |  |  | 				headers: { 'content-type': 'application/json' }, | 
					
						
							|  |  |  | 				body: JSON.stringify(body), | 
					
						
							|  |  |  | 			}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			const res = await statuses.handleRequest(req, db, actor, userKEK, queue, kv_cache) | 
					
						
							|  |  |  | 			assert.equal(res.status, 200) | 
					
						
							|  |  |  | 			assertJSON(res) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			const data = await res.json<any>() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			assert(cache) | 
					
						
							|  |  |  | 			const timeline = JSON.parse(cache) | 
					
						
							|  |  |  | 			assert.equal(timeline.length, 1) | 
					
						
							|  |  |  | 			assert.equal(timeline[0].id, data.id) | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-12-05 20:14:56 +00:00
										 |  |  | 		test("create new status adds to Actor's outbox", async () => { | 
					
						
							|  |  |  | 			const db = await makeDB() | 
					
						
							| 
									
										
										
										
											2023-01-13 10:56:24 +00:00
										 |  |  | 			const queue = makeQueue() | 
					
						
							| 
									
										
										
										
											2023-01-05 09:33:46 +00:00
										 |  |  | 			const actor = await createPerson(domain, db, userKEK, 'sven@cloudflare.com') | 
					
						
							| 
									
										
										
										
											2022-12-05 20:14:56 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			const body = { | 
					
						
							|  |  |  | 				status: 'my status', | 
					
						
							|  |  |  | 				visibility: 'public', | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			const req = new Request('https://example.com', { | 
					
						
							|  |  |  | 				method: 'POST', | 
					
						
							| 
									
										
										
										
											2023-01-11 08:17:48 +00:00
										 |  |  | 				headers: { 'content-type': 'application/json' }, | 
					
						
							| 
									
										
										
										
											2022-12-05 20:14:56 +00:00
										 |  |  | 				body: JSON.stringify(body), | 
					
						
							|  |  |  | 			}) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-05 09:33:46 +00:00
										 |  |  | 			const connectedActor = actor | 
					
						
							| 
									
										
										
										
											2023-01-16 19:05:45 +00:00
										 |  |  | 			const res = await statuses.handleRequest(req, db, connectedActor, userKEK, queue, kv_cache) | 
					
						
							| 
									
										
										
										
											2022-12-05 20:14:56 +00:00
										 |  |  | 			assert.equal(res.status, 200) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			const row = await db.prepare(`SELECT count(*) as count FROM outbox_objects`).first() | 
					
						
							|  |  |  | 			assert.equal(row.count, 1) | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-13 10:56:24 +00:00
										 |  |  | 		test('create new status delivers to followers via Queue', async () => { | 
					
						
							|  |  |  | 			const queue = makeQueue() | 
					
						
							|  |  |  | 			const db = await makeDB() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			const actor = await createPerson(domain, db, userKEK, 'sven@cloudflare.com') | 
					
						
							|  |  |  | 			const followerA = await createPerson(domain, db, userKEK, 'followerA@cloudflare.com') | 
					
						
							|  |  |  | 			const followerB = await createPerson(domain, db, userKEK, 'followerB@cloudflare.com') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			await addFollowing(db, followerA, actor, 'not needed') | 
					
						
							|  |  |  | 			await sleep(10) | 
					
						
							|  |  |  | 			await addFollowing(db, followerB, actor, 'not needed') | 
					
						
							|  |  |  | 			await acceptFollowing(db, followerA, actor) | 
					
						
							|  |  |  | 			await acceptFollowing(db, followerB, actor) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			const body = { status: 'my status', visibility: 'public' } | 
					
						
							|  |  |  | 			const req = new Request('https://example.com', { | 
					
						
							|  |  |  | 				method: 'POST', | 
					
						
							|  |  |  | 				headers: { 'content-type': 'application/json' }, | 
					
						
							|  |  |  | 				body: JSON.stringify(body), | 
					
						
							|  |  |  | 			}) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-16 19:05:45 +00:00
										 |  |  | 			const res = await statuses.handleRequest(req, db, actor, userKEK, queue, kv_cache) | 
					
						
							| 
									
										
										
										
											2023-01-13 10:56:24 +00:00
										 |  |  | 			assert.equal(res.status, 200) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			assert.equal(queue.messages.length, 2) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			assert.equal(queue.messages[0].type, MessageType.Deliver) | 
					
						
							|  |  |  | 			assert.equal(queue.messages[0].userKEK, userKEK) | 
					
						
							|  |  |  | 			assert.equal(queue.messages[0].actorId, actor.id.toString()) | 
					
						
							|  |  |  | 			assert.equal(queue.messages[0].toActorId, followerA.id.toString()) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			assert.equal(queue.messages[1].type, MessageType.Deliver) | 
					
						
							|  |  |  | 			assert.equal(queue.messages[1].userKEK, userKEK) | 
					
						
							|  |  |  | 			assert.equal(queue.messages[1].actorId, actor.id.toString()) | 
					
						
							|  |  |  | 			assert.equal(queue.messages[1].toActorId, followerB.id.toString()) | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-12-05 20:14:56 +00:00
										 |  |  | 		test('create new status with mention delivers ActivityPub Note', async () => { | 
					
						
							|  |  |  | 			let deliveredNote: any = null | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			globalThis.fetch = async (input: RequestInfo, data: any) => { | 
					
						
							|  |  |  | 				if (input.toString() === 'https://remote.com/.well-known/webfinger?resource=acct%3Asven%40remote.com') { | 
					
						
							|  |  |  | 					return new Response( | 
					
						
							|  |  |  | 						JSON.stringify({ | 
					
						
							|  |  |  | 							links: [ | 
					
						
							|  |  |  | 								{ | 
					
						
							|  |  |  | 									rel: 'self', | 
					
						
							|  |  |  | 									type: 'application/activity+json', | 
					
						
							| 
									
										
										
										
											2023-01-13 10:56:24 +00:00
										 |  |  | 									href: 'https://social.com/users/sven', | 
					
						
							| 
									
										
										
										
											2022-12-05 20:14:56 +00:00
										 |  |  | 								}, | 
					
						
							|  |  |  | 							], | 
					
						
							|  |  |  | 						}) | 
					
						
							|  |  |  | 					) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-13 10:56:24 +00:00
										 |  |  | 				if (input.toString() === 'https://social.com/users/sven') { | 
					
						
							| 
									
										
										
										
											2022-12-05 20:14:56 +00:00
										 |  |  | 					return new Response( | 
					
						
							|  |  |  | 						JSON.stringify({ | 
					
						
							| 
									
										
										
										
											2023-01-13 10:56:24 +00:00
										 |  |  | 							id: 'https://social.com/users/sven', | 
					
						
							| 
									
										
										
										
											2022-12-05 20:14:56 +00:00
										 |  |  | 							inbox: 'https://social.com/sven/inbox', | 
					
						
							|  |  |  | 						}) | 
					
						
							|  |  |  | 					) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				if (input === 'https://social.com/sven/inbox') { | 
					
						
							|  |  |  | 					assert.equal(data.method, 'POST') | 
					
						
							|  |  |  | 					const body = JSON.parse(data.body) | 
					
						
							|  |  |  | 					deliveredNote = body | 
					
						
							|  |  |  | 					return new Response() | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				// @ts-ignore: shut up
 | 
					
						
							|  |  |  | 				if (Object.keys(input).includes('url') && input.url === 'https://social.com/sven/inbox') { | 
					
						
							|  |  |  | 					const request = input as Request | 
					
						
							|  |  |  | 					assert.equal(request.method, 'POST') | 
					
						
							|  |  |  | 					const bodyB = await streamToArrayBuffer(request.body as ReadableStream) | 
					
						
							|  |  |  | 					const dec = new TextDecoder() | 
					
						
							|  |  |  | 					const body = JSON.parse(dec.decode(bodyB)) | 
					
						
							|  |  |  | 					deliveredNote = body | 
					
						
							|  |  |  | 					return new Response() | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				throw new Error('unexpected request to ' + input) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			const db = await makeDB() | 
					
						
							| 
									
										
										
										
											2023-01-13 10:56:24 +00:00
										 |  |  | 			const queue = makeQueue() | 
					
						
							| 
									
										
										
										
											2023-01-05 09:33:46 +00:00
										 |  |  | 			const actor = await createPerson(domain, db, userKEK, 'sven@cloudflare.com') | 
					
						
							| 
									
										
										
										
											2022-12-05 20:14:56 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			const body = { | 
					
						
							|  |  |  | 				status: '@sven@remote.com my status', | 
					
						
							|  |  |  | 				visibility: 'public', | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			const req = new Request('https://example.com', { | 
					
						
							|  |  |  | 				method: 'POST', | 
					
						
							| 
									
										
										
										
											2023-01-11 08:17:48 +00:00
										 |  |  | 				headers: { 'content-type': 'application/json' }, | 
					
						
							| 
									
										
										
										
											2022-12-05 20:14:56 +00:00
										 |  |  | 				body: JSON.stringify(body), | 
					
						
							|  |  |  | 			}) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-05 09:33:46 +00:00
										 |  |  | 			const connectedActor = actor | 
					
						
							| 
									
										
										
										
											2023-01-16 19:05:45 +00:00
										 |  |  | 			const res = await statuses.handleRequest(req, db, connectedActor, userKEK, queue, kv_cache) | 
					
						
							| 
									
										
										
										
											2022-12-05 20:14:56 +00:00
										 |  |  | 			assert.equal(res.status, 200) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			assert(deliveredNote) | 
					
						
							|  |  |  | 			assert.equal(deliveredNote.type, 'Create') | 
					
						
							|  |  |  | 			assert.equal(deliveredNote.actor, `https://${domain}/ap/users/sven`) | 
					
						
							|  |  |  | 			assert.equal(deliveredNote.object.attributedTo, `https://${domain}/ap/users/sven`) | 
					
						
							|  |  |  | 			assert.equal(deliveredNote.object.type, 'Note') | 
					
						
							| 
									
										
										
										
											2023-01-16 14:16:54 +00:00
										 |  |  | 			assert(deliveredNote.object.to.includes(activities.PUBLIC_GROUP)) | 
					
						
							| 
									
										
										
										
											2022-12-05 20:14:56 +00:00
										 |  |  | 			assert.equal(deliveredNote.object.cc.length, 1) | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		test('create new status with image', async () => { | 
					
						
							|  |  |  | 			const db = await makeDB() | 
					
						
							| 
									
										
										
										
											2023-01-13 10:56:24 +00:00
										 |  |  | 			const queue = makeQueue() | 
					
						
							| 
									
										
										
										
											2023-01-05 09:33:46 +00:00
										 |  |  | 			const connectedActor = await createPerson(domain, db, userKEK, 'sven@cloudflare.com') | 
					
						
							| 
									
										
										
										
											2022-12-05 20:14:56 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-16 19:05:45 +00:00
										 |  |  | 			const properties = { url: 'https://example.com/image.jpg' } | 
					
						
							| 
									
										
										
										
											2022-12-05 20:14:56 +00:00
										 |  |  | 			const image = await createImage(domain, db, connectedActor, properties) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			const body = { | 
					
						
							|  |  |  | 				status: 'my status', | 
					
						
							|  |  |  | 				media_ids: [image.mastodonId], | 
					
						
							|  |  |  | 				visibility: 'public', | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			const req = new Request('https://example.com', { | 
					
						
							|  |  |  | 				method: 'POST', | 
					
						
							| 
									
										
										
										
											2023-01-11 08:17:48 +00:00
										 |  |  | 				headers: { 'content-type': 'application/json' }, | 
					
						
							| 
									
										
										
										
											2022-12-05 20:14:56 +00:00
										 |  |  | 				body: JSON.stringify(body), | 
					
						
							|  |  |  | 			}) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-16 19:05:45 +00:00
										 |  |  | 			const res = await statuses.handleRequest(req, db, connectedActor, userKEK, queue, kv_cache) | 
					
						
							| 
									
										
										
										
											2022-12-05 20:14:56 +00:00
										 |  |  | 			assert.equal(res.status, 200) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			const data = await res.json<any>() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			assert(!isUrlValid(data.id)) | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		test('favourite status sends Like activity', async () => { | 
					
						
							|  |  |  | 			let deliveredActivity: any = null | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			const db = await makeDB() | 
					
						
							| 
									
										
										
										
											2023-01-05 09:33:46 +00:00
										 |  |  | 			const actor = await createPerson(domain, db, userKEK, 'sven@cloudflare.com') | 
					
						
							| 
									
										
										
										
											2022-12-05 20:14:56 +00:00
										 |  |  | 			const originalObjectId = 'https://example.com/note123' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			await db | 
					
						
							|  |  |  | 				.prepare( | 
					
						
							|  |  |  | 					'INSERT INTO objects (id, type, properties, original_actor_id, original_object_id, local, mastodon_id) VALUES (?, ?, ?, ?, ?, 1, ?)' | 
					
						
							|  |  |  | 				) | 
					
						
							|  |  |  | 				.bind( | 
					
						
							|  |  |  | 					'https://example.com/object1', | 
					
						
							|  |  |  | 					'Note', | 
					
						
							|  |  |  | 					JSON.stringify({ content: 'my first status' }), | 
					
						
							|  |  |  | 					actor.id.toString(), | 
					
						
							|  |  |  | 					originalObjectId, | 
					
						
							|  |  |  | 					'mastodonid1' | 
					
						
							|  |  |  | 				) | 
					
						
							|  |  |  | 				.run() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-06 09:06:16 +00:00
										 |  |  | 			globalThis.fetch = async (input: any) => { | 
					
						
							| 
									
										
										
										
											2023-01-13 10:56:24 +00:00
										 |  |  | 				if (input.url === actor.id.toString() + '/inbox') { | 
					
						
							| 
									
										
										
										
											2022-12-05 20:14:56 +00:00
										 |  |  | 					assert.equal(input.method, 'POST') | 
					
						
							|  |  |  | 					const body = await input.json() | 
					
						
							|  |  |  | 					deliveredActivity = body | 
					
						
							|  |  |  | 					return new Response() | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				throw new Error('unexpected request to ' + JSON.stringify(input)) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			const connectedActor: any = actor | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			const res = await statuses_favourite.handleRequest(db, 'mastodonid1', connectedActor, userKEK) | 
					
						
							|  |  |  | 			assert.equal(res.status, 200) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			assert(deliveredActivity) | 
					
						
							|  |  |  | 			assert.equal(deliveredActivity.type, 'Like') | 
					
						
							|  |  |  | 			assert.equal(deliveredActivity.object, originalObjectId) | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		test('favourite records in db', async () => { | 
					
						
							|  |  |  | 			const db = await makeDB() | 
					
						
							| 
									
										
										
										
											2023-01-05 09:33:46 +00:00
										 |  |  | 			const actor = await createPerson(domain, db, userKEK, 'sven@cloudflare.com') | 
					
						
							| 
									
										
										
										
											2022-12-05 20:14:56 +00:00
										 |  |  | 			const note = await createPublicNote(domain, db, 'my first status', actor) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			const connectedActor: any = actor | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			const res = await statuses_favourite.handleRequest(db, note.mastodonId!, connectedActor, userKEK) | 
					
						
							|  |  |  | 			assert.equal(res.status, 200) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			const data = await res.json<any>() | 
					
						
							|  |  |  | 			assert.equal(data.favourited, true) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			const row = await db.prepare(`SELECT * FROM actor_favourites`).first() | 
					
						
							|  |  |  | 			assert.equal(row.actor_id, actor.id.toString()) | 
					
						
							|  |  |  | 			assert.equal(row.object_id, note.id.toString()) | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		test('get mentions from status', () => { | 
					
						
							|  |  |  | 			{ | 
					
						
							|  |  |  | 				const mentions = getMentions('test status') | 
					
						
							|  |  |  | 				assert.equal(mentions.length, 0) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			{ | 
					
						
							|  |  |  | 				const mentions = getMentions('@sven@instance.horse test status') | 
					
						
							|  |  |  | 				assert.equal(mentions.length, 1) | 
					
						
							|  |  |  | 				assert.equal(mentions[0].localPart, 'sven') | 
					
						
							|  |  |  | 				assert.equal(mentions[0].domain, 'instance.horse') | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			{ | 
					
						
							|  |  |  | 				const mentions = getMentions('@sven test status') | 
					
						
							|  |  |  | 				assert.equal(mentions.length, 1) | 
					
						
							|  |  |  | 				assert.equal(mentions[0].localPart, 'sven') | 
					
						
							|  |  |  | 				assert.equal(mentions[0].domain, null) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			{ | 
					
						
							|  |  |  | 				const mentions = getMentions('@sven @james @pete') | 
					
						
							|  |  |  | 				assert.deepEqual(mentions, [ | 
					
						
							|  |  |  | 					{ localPart: 'sven', domain: null }, | 
					
						
							|  |  |  | 					{ localPart: 'james', domain: null }, | 
					
						
							|  |  |  | 					{ localPart: 'pete', domain: null }, | 
					
						
							|  |  |  | 				]) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			{ | 
					
						
							|  |  |  | 				const mentions = getMentions('<p>@sven</p>') | 
					
						
							|  |  |  | 				assert.deepEqual(mentions, [{ localPart: 'sven', domain: null }]) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		test('get status count likes', async () => { | 
					
						
							|  |  |  | 			const db = await makeDB() | 
					
						
							| 
									
										
										
										
											2023-01-05 09:33:46 +00:00
										 |  |  | 			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') | 
					
						
							| 
									
										
										
										
											2022-12-05 20:14:56 +00:00
										 |  |  | 			const note = await createPublicNote(domain, db, 'my first status', actor) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			await insertLike(db, actor2, note) | 
					
						
							|  |  |  | 			await insertLike(db, actor3, note) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			const res = await statuses_get.handleRequest(db, note.mastodonId!) | 
					
						
							|  |  |  | 			assert.equal(res.status, 200) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			const data = await res.json<any>() | 
					
						
							| 
									
										
										
										
											2023-01-06 16:48:28 +00:00
										 |  |  | 			// FIXME: temporarly disable favourites counts
 | 
					
						
							|  |  |  | 			assert.equal(data.favourites_count, 0) | 
					
						
							| 
									
										
										
										
											2022-12-05 20:14:56 +00:00
										 |  |  | 		}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		test('get status with image', async () => { | 
					
						
							|  |  |  | 			const db = await makeDB() | 
					
						
							| 
									
										
										
										
											2023-01-05 09:33:46 +00:00
										 |  |  | 			const actor = await createPerson(domain, db, userKEK, 'sven@cloudflare.com') | 
					
						
							| 
									
										
										
										
											2022-12-05 20:14:56 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			const properties = { url: 'https://example.com/image.jpg' } | 
					
						
							|  |  |  | 			const mediaAttachments = [await createImage(domain, db, actor, properties)] | 
					
						
							|  |  |  | 			const note = await createPublicNote(domain, db, 'my first status', actor, mediaAttachments) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			const res = await statuses_get.handleRequest(db, note.mastodonId!) | 
					
						
							|  |  |  | 			assert.equal(res.status, 200) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			const data = await res.json<any>() | 
					
						
							|  |  |  | 			assert.equal(data.media_attachments.length, 1) | 
					
						
							|  |  |  | 			assert.equal(data.media_attachments[0].url, properties.url) | 
					
						
							|  |  |  | 			assert.equal(data.media_attachments[0].preview_url, properties.url) | 
					
						
							|  |  |  | 			assert.equal(data.media_attachments[0].type, 'image') | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		test('status context shows descendants', async () => { | 
					
						
							|  |  |  | 			const db = await makeDB() | 
					
						
							| 
									
										
										
										
											2023-01-05 09:33:46 +00:00
										 |  |  | 			const actor = await createPerson(domain, db, userKEK, 'sven@cloudflare.com') | 
					
						
							| 
									
										
										
										
											2022-12-05 20:14:56 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			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 res = await statuses_context.handleRequest(domain, db, note.mastodonId!) | 
					
						
							|  |  |  | 			assert.equal(res.status, 200) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			const data = await res.json<any>() | 
					
						
							|  |  |  | 			assert.equal(data.ancestors.length, 0) | 
					
						
							|  |  |  | 			assert.equal(data.descendants.length, 1) | 
					
						
							|  |  |  | 			assert.equal(data.descendants[0].content, 'a reply') | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		describe('reblog', () => { | 
					
						
							|  |  |  | 			test('get status count reblogs', async () => { | 
					
						
							|  |  |  | 				const db = await makeDB() | 
					
						
							| 
									
										
										
										
											2023-01-05 09:33:46 +00:00
										 |  |  | 				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') | 
					
						
							| 
									
										
										
										
											2022-12-05 20:14:56 +00:00
										 |  |  | 				const note = await createPublicNote(domain, db, 'my first status', actor) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				await insertReblog(db, actor2, note) | 
					
						
							|  |  |  | 				await insertReblog(db, actor3, note) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				const res = await statuses_get.handleRequest(db, note.mastodonId!) | 
					
						
							|  |  |  | 				assert.equal(res.status, 200) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				const data = await res.json<any>() | 
					
						
							| 
									
										
										
										
											2023-01-06 16:48:28 +00:00
										 |  |  | 				// FIXME: temporarly disable reblogs counts
 | 
					
						
							|  |  |  | 				assert.equal(data.reblogs_count, 0) | 
					
						
							| 
									
										
										
										
											2022-12-05 20:14:56 +00:00
										 |  |  | 			}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			test('reblog records in db', async () => { | 
					
						
							|  |  |  | 				const db = await makeDB() | 
					
						
							| 
									
										
										
										
											2023-01-13 10:56:24 +00:00
										 |  |  | 				const queue = makeQueue() | 
					
						
							| 
									
										
										
										
											2023-01-05 09:33:46 +00:00
										 |  |  | 				const actor = await createPerson(domain, db, userKEK, 'sven@cloudflare.com') | 
					
						
							| 
									
										
										
										
											2022-12-05 20:14:56 +00:00
										 |  |  | 				const note = await createPublicNote(domain, db, 'my first status', actor) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				const connectedActor: any = actor | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-13 10:56:24 +00:00
										 |  |  | 				const res = await statuses_reblog.handleRequest(db, note.mastodonId!, connectedActor, userKEK, queue) | 
					
						
							| 
									
										
										
										
											2022-12-05 20:14:56 +00:00
										 |  |  | 				assert.equal(res.status, 200) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				const data = await res.json<any>() | 
					
						
							|  |  |  | 				assert.equal(data.reblogged, true) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				const row = await db.prepare(`SELECT * FROM actor_reblogs`).first() | 
					
						
							|  |  |  | 				assert.equal(row.actor_id, actor.id.toString()) | 
					
						
							|  |  |  | 				assert.equal(row.object_id, note.id.toString()) | 
					
						
							|  |  |  | 			}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			test('reblog status adds in actor outbox', async () => { | 
					
						
							|  |  |  | 				const db = await makeDB() | 
					
						
							| 
									
										
										
										
											2023-01-05 09:33:46 +00:00
										 |  |  | 				const actor = await createPerson(domain, db, userKEK, 'sven@cloudflare.com') | 
					
						
							| 
									
										
										
										
											2023-01-13 10:56:24 +00:00
										 |  |  | 				const queue = makeQueue() | 
					
						
							| 
									
										
										
										
											2022-12-05 20:14:56 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-13 10:56:24 +00:00
										 |  |  | 				const note = await createPublicNote(domain, db, 'my first status', actor) | 
					
						
							| 
									
										
										
										
											2022-12-05 20:14:56 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 				const connectedActor: any = actor | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-13 10:56:24 +00:00
										 |  |  | 				const res = await statuses_reblog.handleRequest(db, note.mastodonId!, connectedActor, userKEK, queue) | 
					
						
							| 
									
										
										
										
											2022-12-05 20:14:56 +00:00
										 |  |  | 				assert.equal(res.status, 200) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				const row = await db.prepare(`SELECT * FROM outbox_objects`).first() | 
					
						
							|  |  |  | 				assert.equal(row.actor_id, actor.id.toString()) | 
					
						
							| 
									
										
										
										
											2023-01-13 10:56:24 +00:00
										 |  |  | 				assert.equal(row.object_id, note.id.toString()) | 
					
						
							| 
									
										
										
										
											2022-12-05 20:14:56 +00:00
										 |  |  | 			}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			test('reblog remote status status sends Announce activity to author', async () => { | 
					
						
							|  |  |  | 				let deliveredActivity: any = null | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				const db = await makeDB() | 
					
						
							| 
									
										
										
										
											2023-01-13 10:56:24 +00:00
										 |  |  | 				const queue = makeQueue() | 
					
						
							| 
									
										
										
										
											2023-01-05 09:33:46 +00:00
										 |  |  | 				const actor = await createPerson(domain, db, userKEK, 'sven@cloudflare.com') | 
					
						
							| 
									
										
										
										
											2022-12-05 20:14:56 +00:00
										 |  |  | 				const originalObjectId = 'https://example.com/note123' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				await db | 
					
						
							|  |  |  | 					.prepare( | 
					
						
							|  |  |  | 						'INSERT INTO objects (id, type, properties, original_actor_id, original_object_id, mastodon_id, local) VALUES (?, ?, ?, ?, ?, ?, 0)' | 
					
						
							|  |  |  | 					) | 
					
						
							|  |  |  | 					.bind( | 
					
						
							|  |  |  | 						'https://example.com/object1', | 
					
						
							|  |  |  | 						'Note', | 
					
						
							|  |  |  | 						JSON.stringify({ content: 'my first status' }), | 
					
						
							|  |  |  | 						actor.id.toString(), | 
					
						
							|  |  |  | 						originalObjectId, | 
					
						
							|  |  |  | 						'mastodonid1' | 
					
						
							|  |  |  | 					) | 
					
						
							|  |  |  | 					.run() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-06 09:06:16 +00:00
										 |  |  | 				globalThis.fetch = async (input: any) => { | 
					
						
							| 
									
										
										
										
											2023-01-13 10:56:24 +00:00
										 |  |  | 					if (input.url === 'https://cloudflare.com/ap/users/sven/inbox') { | 
					
						
							| 
									
										
										
										
											2022-12-05 20:14:56 +00:00
										 |  |  | 						assert.equal(input.method, 'POST') | 
					
						
							|  |  |  | 						const body = await input.json() | 
					
						
							|  |  |  | 						deliveredActivity = body | 
					
						
							|  |  |  | 						return new Response() | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 					throw new Error('unexpected request to ' + JSON.stringify(input)) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				const connectedActor: any = actor | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-13 10:56:24 +00:00
										 |  |  | 				const res = await statuses_reblog.handleRequest(db, 'mastodonid1', connectedActor, userKEK, queue) | 
					
						
							| 
									
										
										
										
											2022-12-05 20:14:56 +00:00
										 |  |  | 				assert.equal(res.status, 200) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				assert(deliveredActivity) | 
					
						
							|  |  |  | 				assert.equal(deliveredActivity.type, 'Announce') | 
					
						
							|  |  |  | 				assert.equal(deliveredActivity.actor, actor.id.toString()) | 
					
						
							|  |  |  | 				assert.equal(deliveredActivity.object, originalObjectId) | 
					
						
							|  |  |  | 			}) | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | }) |