@evanp/activitypub-bot 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/.github/dependabot.yml +11 -0
  2. package/.github/workflows/main-docker.yml +45 -0
  3. package/.github/workflows/tag-docker.yml +54 -0
  4. package/Dockerfile +23 -0
  5. package/LICENSE +661 -0
  6. package/README.md +82 -0
  7. package/bots/index.js +7 -0
  8. package/docs/activitypub.bot.drawio +110 -0
  9. package/index.js +23 -0
  10. package/lib/activitydistributor.js +263 -0
  11. package/lib/activityhandler.js +999 -0
  12. package/lib/activitypubclient.js +126 -0
  13. package/lib/activitystreams.js +41 -0
  14. package/lib/actorstorage.js +300 -0
  15. package/lib/app.js +173 -0
  16. package/lib/authorizer.js +133 -0
  17. package/lib/bot.js +44 -0
  18. package/lib/botcontext.js +520 -0
  19. package/lib/botdatastorage.js +87 -0
  20. package/lib/bots/donothing.js +11 -0
  21. package/lib/bots/ok.js +41 -0
  22. package/lib/digester.js +23 -0
  23. package/lib/httpsignature.js +195 -0
  24. package/lib/httpsignatureauthenticator.js +81 -0
  25. package/lib/keystorage.js +113 -0
  26. package/lib/microsyntax.js +140 -0
  27. package/lib/objectcache.js +48 -0
  28. package/lib/objectstorage.js +319 -0
  29. package/lib/remotekeystorage.js +116 -0
  30. package/lib/routes/collection.js +92 -0
  31. package/lib/routes/health.js +24 -0
  32. package/lib/routes/inbox.js +83 -0
  33. package/lib/routes/object.js +69 -0
  34. package/lib/routes/server.js +47 -0
  35. package/lib/routes/user.js +63 -0
  36. package/lib/routes/webfinger.js +36 -0
  37. package/lib/urlformatter.js +97 -0
  38. package/package.json +51 -0
  39. package/tests/activitydistributor.test.js +606 -0
  40. package/tests/activityhandler.test.js +2185 -0
  41. package/tests/activitypubclient.test.js +225 -0
  42. package/tests/actorstorage.test.js +261 -0
  43. package/tests/app.test.js +17 -0
  44. package/tests/authorizer.test.js +306 -0
  45. package/tests/bot.donothing.test.js +30 -0
  46. package/tests/bot.ok.test.js +101 -0
  47. package/tests/botcontext.test.js +674 -0
  48. package/tests/botdatastorage.test.js +87 -0
  49. package/tests/digester.test.js +56 -0
  50. package/tests/fixtures/bots.js +15 -0
  51. package/tests/httpsignature.test.js +200 -0
  52. package/tests/httpsignatureauthenticator.test.js +463 -0
  53. package/tests/keystorage.test.js +89 -0
  54. package/tests/microsyntax.test.js +122 -0
  55. package/tests/objectcache.test.js +133 -0
  56. package/tests/objectstorage.test.js +148 -0
  57. package/tests/remotekeystorage.test.js +76 -0
  58. package/tests/routes.actor.test.js +207 -0
  59. package/tests/routes.collection.test.js +434 -0
  60. package/tests/routes.health.test.js +41 -0
  61. package/tests/routes.inbox.test.js +135 -0
  62. package/tests/routes.object.test.js +519 -0
  63. package/tests/routes.server.test.js +69 -0
  64. package/tests/routes.webfinger.test.js +41 -0
  65. package/tests/urlformatter.test.js +164 -0
  66. package/tests/utils/digest.js +7 -0
  67. package/tests/utils/nock.js +276 -0
@@ -0,0 +1,519 @@
1
+ import { describe, it, before } from 'node:test'
2
+ import assert from 'node:assert'
3
+ import { makeApp } from '../lib/app.js'
4
+ import request from 'supertest'
5
+ import bots from './fixtures/bots.js'
6
+ import as2 from '../lib/activitystreams.js'
7
+ import { nockSetup, nockFormat, nockSignature, makeActor } from './utils/nock.js'
8
+
9
+ const uppercase = (string) => string.charAt(0).toUpperCase() + string.slice(1)
10
+
11
+ describe('object collection routes', async () => {
12
+ const databaseUrl = 'sqlite::memory:'
13
+ const origin = 'https://activitypubbot.test'
14
+ const username = 'ok'
15
+ const type = 'object'
16
+ const nanoid = 'hUQC9HWian7dzOxZJlJBA'
17
+ let app = null
18
+ let obj = null
19
+ let reply = null
20
+ let like = null
21
+ let share = null
22
+ let privateObj = null
23
+ const privateNanoid = 'Ic3Sa_0xOQKvlPsWU16as'
24
+
25
+ before(async () => {
26
+ app = await makeApp(databaseUrl, origin, bots, 'silent')
27
+ const { formatter, objectStorage, actorStorage } = app.locals
28
+ nockSetup('social.example')
29
+ obj = await as2.import({
30
+ id: formatter.format({ username, type, nanoid }),
31
+ type: uppercase(type),
32
+ attributedTo: formatter.format({ username }),
33
+ summaryMap: {
34
+ en: 'Test object for the object collection routes'
35
+ },
36
+ replies: formatter.format({ username, type, nanoid, collection: 'replies' }),
37
+ likes: formatter.format({ username, type, nanoid, collection: 'likes' }),
38
+ shares: formatter.format({ username, type, nanoid, collection: 'shares' }),
39
+ to: ['as:Public']
40
+ })
41
+ await objectStorage.create(obj)
42
+ await objectStorage.addToCollection(obj.id, 'thread', obj)
43
+ reply = await as2.import({
44
+ id: nockFormat({
45
+ domain: 'social.example',
46
+ username: 'replier1',
47
+ type: 'note',
48
+ num: 1
49
+ }),
50
+ type: 'Note',
51
+ attributedTo: nockFormat({
52
+ domain: 'social.example',
53
+ username: 'replier1'
54
+ }),
55
+ content: 'This is a reply to the test object',
56
+ inReplyTo: obj.id,
57
+ to: [formatter.format({ username }), 'as:Public']
58
+ })
59
+ await objectStorage.addToCollection(obj.id, 'replies', reply)
60
+ await objectStorage.addToCollection(obj.id, 'thread', reply)
61
+ like = await as2.import({
62
+ id: nockFormat({
63
+ domain: 'social.example',
64
+ username: 'liker1',
65
+ type: 'like',
66
+ num: 1,
67
+ obj: obj.id
68
+ }),
69
+ type: 'Like',
70
+ attributedTo: nockFormat({
71
+ domain: 'social.example',
72
+ username: 'liker1'
73
+ }),
74
+ object: obj.id,
75
+ to: [formatter.format({ username }), 'as:Public']
76
+ })
77
+ await objectStorage.addToCollection(obj.id, 'likes', like)
78
+ share = await as2.import({
79
+ id: nockFormat({
80
+ domain: 'social.example',
81
+ username: 'sharer1',
82
+ type: 'announce',
83
+ num: 1,
84
+ obj: obj.id
85
+ }),
86
+ type: 'Announce',
87
+ attributedTo: nockFormat({
88
+ domain: 'social.example',
89
+ username: 'sharer1'
90
+ }),
91
+ object: obj.id,
92
+ to: [formatter.format({ username }), 'as:Public']
93
+ })
94
+ await objectStorage.addToCollection(obj.id, 'shares', share)
95
+ privateObj = await as2.import({
96
+ id: formatter.format({ username, type, nanoid: privateNanoid }),
97
+ type: uppercase(type),
98
+ attributedTo: formatter.format({ username }),
99
+ summaryMap: {
100
+ en: 'Test object for the object collection routes'
101
+ },
102
+ replies: formatter.format({ username, type, nanoid, collection: 'replies' }),
103
+ likes: formatter.format({ username, type, nanoid, collection: 'likes' }),
104
+ shares: formatter.format({ username, type, nanoid, collection: 'shares' }),
105
+ to: formatter.format({ username, collection: 'followers' })
106
+ })
107
+ await objectStorage.create(privateObj)
108
+ await actorStorage.addToCollection(
109
+ username,
110
+ 'followers',
111
+ await makeActor('follower1', 'social.example')
112
+ )
113
+ })
114
+
115
+ describe('GET /user/{username}/{type}/{nanoid}', async () => {
116
+ let response = null
117
+ const url = `/user/${username}/${type}/${nanoid}`
118
+ it('should work without an error', async () => {
119
+ response = await request(app)
120
+ .get(url)
121
+ })
122
+ it('should return a 200 status', async () => {
123
+ assert.strictEqual(response.status, 200)
124
+ })
125
+ it('should return AS2', async () => {
126
+ assert.strictEqual(response.type, 'application/activity+json')
127
+ })
128
+ it('should return an object', async () => {
129
+ assert.strictEqual(typeof response.body, 'object')
130
+ })
131
+ it('should return an object with the right id', async () => {
132
+ assert.strictEqual(typeof response.body.id, 'string')
133
+ assert.strictEqual(response.body.id, `https://activitypubbot.test/user/${username}/${type}/${nanoid}`)
134
+ })
135
+ it('should return an object with the right type', async () => {
136
+ assert.strictEqual(typeof response.body.type, 'string')
137
+ assert.strictEqual(response.body.type, 'Object')
138
+ })
139
+ it('should return an object with the right summary', async () => {
140
+ assert.strictEqual(typeof response.body.summaryMap, 'object')
141
+ assert.strictEqual(response.body.summaryMap.en, 'Test object for the object collection routes')
142
+ })
143
+ it('should return an object with the right replies', async () => {
144
+ assert.strictEqual(typeof response.body.replies, 'string')
145
+ assert.strictEqual(response.body.replies, `https://activitypubbot.test/user/${username}/${type}/${nanoid}/replies`)
146
+ })
147
+ it('should return an object with the right likes', async () => {
148
+ assert.strictEqual(typeof response.body.likes, 'string')
149
+ assert.strictEqual(response.body.likes, `https://activitypubbot.test/user/${username}/${type}/${nanoid}/likes`)
150
+ })
151
+ it('should return an object with the right shares', async () => {
152
+ assert.strictEqual(typeof response.body.shares, 'string')
153
+ assert.strictEqual(response.body.shares, `https://activitypubbot.test/user/${username}/${type}/${nanoid}/shares`)
154
+ })
155
+ })
156
+
157
+ describe('GET /user/{username}/{type}/{nanoid}/replies', async () => {
158
+ let response = null
159
+ const url = `/user/${username}/${type}/${nanoid}/replies`
160
+ it('should work without an error', async () => {
161
+ response = await request(app)
162
+ .get(url)
163
+ })
164
+ it('should return a 200 status', async () => {
165
+ assert.strictEqual(response.status, 200)
166
+ })
167
+ it('should return AS2', async () => {
168
+ assert.strictEqual(response.type, 'application/activity+json')
169
+ })
170
+ it('should return an object', async () => {
171
+ assert.strictEqual(typeof response.body, 'object')
172
+ })
173
+ it('should return an object the right id', async () => {
174
+ assert.strictEqual(typeof response.body.id, 'string')
175
+ assert.strictEqual(response.body.id, `https://activitypubbot.test/user/${username}/${type}/${nanoid}/replies`)
176
+ })
177
+ it('should return an object with the right type', async () => {
178
+ assert.strictEqual(typeof response.body.type, 'string')
179
+ assert.strictEqual(response.body.type, 'OrderedCollection')
180
+ })
181
+ it('should return an object with the right totalItems', async () => {
182
+ assert.strictEqual(typeof response.body.totalItems, 'number')
183
+ assert.strictEqual(response.body.totalItems, 1)
184
+ })
185
+ it('should return an object with the right first', async () => {
186
+ assert.strictEqual(typeof response.body.first, 'string')
187
+ assert.strictEqual(response.body.first, `https://activitypubbot.test/user/${username}/${type}/${nanoid}/replies/1`)
188
+ })
189
+ it('should return an object with the right last', async () => {
190
+ assert.strictEqual(typeof response.body.last, 'string')
191
+ assert.strictEqual(response.body.last, `https://activitypubbot.test/user/${username}/${type}/${nanoid}/replies/1`)
192
+ })
193
+ it('should return an object with the right repliesOf', async () => {
194
+ assert.strictEqual(typeof response.body.repliesOf, 'string')
195
+ assert.strictEqual(response.body.repliesOf, `${origin}/user/${username}/${type}/${nanoid}`)
196
+ })
197
+ })
198
+
199
+ describe('GET /user/{username}/{type}/{nanoid}/replies/1', async () => {
200
+ let response = null
201
+ const url = `/user/${username}/${type}/${nanoid}/replies/1`
202
+ it('should work without an error', async () => {
203
+ response = await request(app)
204
+ .get(url)
205
+ })
206
+ it('should return a 200 status', async () => {
207
+ assert.strictEqual(response.status, 200)
208
+ })
209
+ it('should return AS2', async () => {
210
+ assert.strictEqual(response.type, 'application/activity+json')
211
+ })
212
+ it('should return an object', async () => {
213
+ assert.strictEqual(typeof response.body, 'object')
214
+ })
215
+ it('should return an object the right id', async () => {
216
+ assert.strictEqual(typeof response.body.id, 'string')
217
+ assert.strictEqual(response.body.id, `https://activitypubbot.test/user/${username}/${type}/${nanoid}/replies/1`)
218
+ })
219
+ it('should return an object with the right type', async () => {
220
+ assert.strictEqual(typeof response.body.type, 'string')
221
+ assert.strictEqual(response.body.type, 'OrderedCollectionPage')
222
+ })
223
+ it('should return an object with the right partOf', async () => {
224
+ assert.strictEqual(typeof response.body.partOf, 'string')
225
+ assert.strictEqual(response.body.partOf, `https://activitypubbot.test/user/${username}/${type}/${nanoid}/replies`)
226
+ })
227
+ it('should return an object with the right items', async () => {
228
+ assert.strictEqual(typeof response.body.items, 'object')
229
+ assert.strictEqual(response.body.items.length, 1)
230
+ assert.strictEqual(response.body.items[0], reply.id)
231
+ })
232
+ })
233
+
234
+ describe('GET /user/{username}/{type}/{nanoid}/likes', async () => {
235
+ let response = null
236
+ const url = `/user/${username}/${type}/${nanoid}/likes`
237
+ it('should work without an error', async () => {
238
+ response = await request(app)
239
+ .get(url)
240
+ })
241
+ it('should return a 200 status', async () => {
242
+ assert.strictEqual(response.status, 200)
243
+ })
244
+ it('should return AS2', async () => {
245
+ assert.strictEqual(response.type, 'application/activity+json')
246
+ })
247
+ it('should return an object', async () => {
248
+ assert.strictEqual(typeof response.body, 'object')
249
+ })
250
+ it('should return an object the right id', async () => {
251
+ assert.strictEqual(typeof response.body.id, 'string')
252
+ assert.strictEqual(response.body.id, `https://activitypubbot.test/user/${username}/${type}/${nanoid}/likes`)
253
+ })
254
+ it('should return an object with the right type', async () => {
255
+ assert.strictEqual(typeof response.body.type, 'string')
256
+ assert.strictEqual(response.body.type, 'OrderedCollection')
257
+ })
258
+ it('should return an object with the right totalItems', async () => {
259
+ assert.strictEqual(typeof response.body.totalItems, 'number')
260
+ assert.strictEqual(response.body.totalItems, 1)
261
+ })
262
+ it('should return an object with the right first', async () => {
263
+ assert.strictEqual(typeof response.body.first, 'string')
264
+ assert.strictEqual(response.body.first, `https://activitypubbot.test/user/${username}/${type}/${nanoid}/likes/1`)
265
+ })
266
+ it('should return an object with the right last', async () => {
267
+ assert.strictEqual(typeof response.body.last, 'string')
268
+ assert.strictEqual(response.body.last, `https://activitypubbot.test/user/${username}/${type}/${nanoid}/likes/1`)
269
+ })
270
+ it('should return an object with the right likesOf', async () => {
271
+ assert.strictEqual(typeof response.body.likesOf, 'string')
272
+ assert.strictEqual(response.body.likesOf, `${origin}/user/${username}/${type}/${nanoid}`)
273
+ })
274
+ })
275
+
276
+ describe('GET /user/{username}/{type}/{nanoid}/likes/1', async () => {
277
+ let response = null
278
+ const url = `/user/${username}/${type}/${nanoid}/likes/1`
279
+ it('should work without an error', async () => {
280
+ response = await request(app)
281
+ .get(url)
282
+ })
283
+ it('should return a 200 status', async () => {
284
+ assert.strictEqual(response.status, 200)
285
+ })
286
+ it('should return AS2', async () => {
287
+ assert.strictEqual(response.type, 'application/activity+json')
288
+ })
289
+ it('should return an object', async () => {
290
+ assert.strictEqual(typeof response.body, 'object')
291
+ })
292
+ it('should return an object the right id', async () => {
293
+ assert.strictEqual(typeof response.body.id, 'string')
294
+ assert.strictEqual(response.body.id, `https://activitypubbot.test/user/${username}/${type}/${nanoid}/likes/1`)
295
+ })
296
+ it('should return an object with the right type', async () => {
297
+ assert.strictEqual(typeof response.body.type, 'string')
298
+ assert.strictEqual(response.body.type, 'OrderedCollectionPage')
299
+ })
300
+ it('should return an object with the right partOf', async () => {
301
+ assert.strictEqual(typeof response.body.partOf, 'string')
302
+ assert.strictEqual(response.body.partOf, `https://activitypubbot.test/user/${username}/${type}/${nanoid}/likes`)
303
+ })
304
+ it('should return an object with the right items', async () => {
305
+ assert.strictEqual(typeof response.body.items, 'object')
306
+ assert.strictEqual(response.body.items.length, 1)
307
+ assert.strictEqual(response.body.items[0], like.id)
308
+ })
309
+ })
310
+
311
+ describe('GET /user/{username}/{type}/{nanoid}/shares', async () => {
312
+ let response = null
313
+ const url = `/user/${username}/${type}/${nanoid}/shares`
314
+ it('should work without an error', async () => {
315
+ response = await request(app)
316
+ .get(url)
317
+ })
318
+ it('should return a 200 status', async () => {
319
+ assert.strictEqual(response.status, 200)
320
+ })
321
+ it('should return AS2', async () => {
322
+ assert.strictEqual(response.type, 'application/activity+json')
323
+ })
324
+ it('should return an object', async () => {
325
+ assert.strictEqual(typeof response.body, 'object')
326
+ })
327
+ it('should return an object the right id', async () => {
328
+ assert.strictEqual(typeof response.body.id, 'string')
329
+ assert.strictEqual(response.body.id, `https://activitypubbot.test/user/${username}/${type}/${nanoid}/shares`)
330
+ })
331
+ it('should return an object with the right type', async () => {
332
+ assert.strictEqual(typeof response.body.type, 'string')
333
+ assert.strictEqual(response.body.type, 'OrderedCollection')
334
+ })
335
+ it('should return an object with the right totalItems', async () => {
336
+ assert.strictEqual(typeof response.body.totalItems, 'number')
337
+ assert.strictEqual(response.body.totalItems, 1)
338
+ })
339
+ it('should return an object with the right first', async () => {
340
+ assert.strictEqual(typeof response.body.first, 'string')
341
+ assert.strictEqual(response.body.first, `https://activitypubbot.test/user/${username}/${type}/${nanoid}/shares/1`)
342
+ })
343
+ it('should return an object with the right last', async () => {
344
+ assert.strictEqual(typeof response.body.last, 'string')
345
+ assert.strictEqual(response.body.last, `https://activitypubbot.test/user/${username}/${type}/${nanoid}/shares/1`)
346
+ })
347
+ it('should return an object with the right sharesOf', async () => {
348
+ assert.strictEqual(typeof response.body.sharesOf, 'string')
349
+ assert.strictEqual(response.body.sharesOf, `${origin}/user/${username}/${type}/${nanoid}`)
350
+ })
351
+ })
352
+
353
+ describe('GET /user/{username}/{type}/{nanoid}/shares/1', async () => {
354
+ let response = null
355
+ const url = `/user/${username}/${type}/${nanoid}/shares/1`
356
+ it('should work without an error', async () => {
357
+ response = await request(app)
358
+ .get(url)
359
+ })
360
+ it('should return a 200 status', async () => {
361
+ assert.strictEqual(response.status, 200)
362
+ })
363
+ it('should return AS2', async () => {
364
+ assert.strictEqual(response.type, 'application/activity+json')
365
+ })
366
+ it('should return an object', async () => {
367
+ assert.strictEqual(typeof response.body, 'object')
368
+ })
369
+ it('should return an object the right id', async () => {
370
+ assert.strictEqual(typeof response.body.id, 'string')
371
+ assert.strictEqual(response.body.id, `https://activitypubbot.test/user/${username}/${type}/${nanoid}/shares/1`)
372
+ })
373
+ it('should return an object with the right type', async () => {
374
+ assert.strictEqual(typeof response.body.type, 'string')
375
+ assert.strictEqual(response.body.type, 'OrderedCollectionPage')
376
+ })
377
+ it('should return an object with the right partOf', async () => {
378
+ assert.strictEqual(typeof response.body.partOf, 'string')
379
+ assert.strictEqual(response.body.partOf, `https://activitypubbot.test/user/${username}/${type}/${nanoid}/shares`)
380
+ })
381
+ it('should return an object with the right items', async () => {
382
+ assert.strictEqual(typeof response.body.items, 'object')
383
+ assert.strictEqual(response.body.items.length, 1)
384
+ assert.strictEqual(response.body.items[0], share.id)
385
+ })
386
+ })
387
+
388
+ describe('Get private object anonymously', async () => {
389
+ let response = null
390
+ const url = `/user/${username}/${type}/${privateNanoid}`
391
+ it('should work without an error', async () => {
392
+ response = await request(app)
393
+ .get(url)
394
+ })
395
+ it('should return a 403 status', async () => {
396
+ assert.strictEqual(response.status, 403)
397
+ })
398
+ })
399
+
400
+ describe('Get private object collection anonymously', async () => {
401
+ let response = null
402
+ const url = `/user/${username}/${type}/${privateNanoid}/replies`
403
+ it('should work without an error', async () => {
404
+ response = await request(app)
405
+ .get(url)
406
+ })
407
+ it('should return a 403 status', async () => {
408
+ assert.strictEqual(response.status, 403)
409
+ })
410
+ })
411
+
412
+ describe('Get private object collection page anonymously', async () => {
413
+ let response = null
414
+ const url = `/user/${username}/${type}/${privateNanoid}/replies/1`
415
+ it('should work without an error', async () => {
416
+ response = await request(app)
417
+ .get(url)
418
+ })
419
+ it('should return a 403 status', async () => {
420
+ assert.strictEqual(response.status, 403)
421
+ })
422
+ })
423
+
424
+ describe('Get private object with follower', async () => {
425
+ let response = null
426
+ const path = `/user/${username}/${type}/${privateNanoid}`
427
+ const url = `${origin}${path}`
428
+ const date = new Date().toISOString()
429
+ const signature = await nockSignature({ username: 'follower1', url, date })
430
+ it('should work without an error', async () => {
431
+ response = await request(app)
432
+ .get(path)
433
+ .set('Signature', signature)
434
+ .set('Date', date)
435
+ .set('Host', 'activitypubbot.test')
436
+ })
437
+ it('should return a 200 status', async () => {
438
+ assert.strictEqual(response.status, 200)
439
+ })
440
+ })
441
+
442
+ describe('GET /user/{username}/{type}/{nanoid}/thread', async () => {
443
+ let response = null
444
+ const url = `/user/${username}/${type}/${nanoid}/thread`
445
+ it('should work without an error', async () => {
446
+ response = await request(app)
447
+ .get(url)
448
+ })
449
+ it('should return a 200 status', async () => {
450
+ assert.strictEqual(response.status, 200)
451
+ })
452
+ it('should return AS2', async () => {
453
+ assert.strictEqual(response.type, 'application/activity+json')
454
+ })
455
+ it('should return an object', async () => {
456
+ assert.strictEqual(typeof response.body, 'object')
457
+ })
458
+ it('should return an object the right id', async () => {
459
+ assert.strictEqual(typeof response.body.id, 'string')
460
+ assert.strictEqual(response.body.id, `https://activitypubbot.test/user/${username}/${type}/${nanoid}/thread`)
461
+ })
462
+ it('should return an object with the right type', async () => {
463
+ assert.strictEqual(typeof response.body.type, 'string')
464
+ assert.strictEqual(response.body.type, 'OrderedCollection')
465
+ })
466
+ it('should return an object with the right totalItems', async () => {
467
+ assert.strictEqual(typeof response.body.totalItems, 'number')
468
+ assert.strictEqual(response.body.totalItems, 2)
469
+ })
470
+ it('should return an object with the right first', async () => {
471
+ assert.strictEqual(typeof response.body.first, 'string')
472
+ assert.strictEqual(response.body.first, `https://activitypubbot.test/user/${username}/${type}/${nanoid}/thread/1`)
473
+ })
474
+ it('should return an object with the right last', async () => {
475
+ assert.strictEqual(typeof response.body.last, 'string')
476
+ assert.strictEqual(response.body.last, `https://activitypubbot.test/user/${username}/${type}/${nanoid}/thread/1`)
477
+ })
478
+ it('should return an object with the right root', async () => {
479
+ assert.strictEqual(typeof response.body.root, 'string')
480
+ assert.strictEqual(response.body.root, `${origin}/user/${username}/${type}/${nanoid}`)
481
+ })
482
+ })
483
+
484
+ describe('GET /user/{username}/{type}/{nanoid}/thread/1', async () => {
485
+ let response = null
486
+ const url = `/user/${username}/${type}/${nanoid}/thread/1`
487
+ it('should work without an error', async () => {
488
+ response = await request(app)
489
+ .get(url)
490
+ })
491
+ it('should return a 200 status', async () => {
492
+ assert.strictEqual(response.status, 200)
493
+ })
494
+ it('should return AS2', async () => {
495
+ assert.strictEqual(response.type, 'application/activity+json')
496
+ })
497
+ it('should return an object', async () => {
498
+ assert.strictEqual(typeof response.body, 'object')
499
+ })
500
+ it('should return an object the right id', async () => {
501
+ assert.strictEqual(typeof response.body.id, 'string')
502
+ assert.strictEqual(response.body.id, `https://activitypubbot.test/user/${username}/${type}/${nanoid}/thread/1`)
503
+ })
504
+ it('should return an object with the right type', async () => {
505
+ assert.strictEqual(typeof response.body.type, 'string')
506
+ assert.strictEqual(response.body.type, 'OrderedCollectionPage')
507
+ })
508
+ it('should return an object with the right partOf', async () => {
509
+ assert.strictEqual(typeof response.body.partOf, 'string')
510
+ assert.strictEqual(response.body.partOf, `https://activitypubbot.test/user/${username}/${type}/${nanoid}/thread`)
511
+ })
512
+ it('should return an object with the right items', async () => {
513
+ assert.strictEqual(typeof response.body.items, 'object')
514
+ assert.strictEqual(response.body.items.length, 2)
515
+ assert.strictEqual(response.body.items[0], reply.id)
516
+ assert.strictEqual(response.body.items[1], obj.id)
517
+ })
518
+ })
519
+ })
@@ -0,0 +1,69 @@
1
+ import { describe, it } from 'node:test'
2
+ import assert from 'node:assert'
3
+ import { makeApp } from '../lib/app.js'
4
+ import request from 'supertest'
5
+ import bots from './fixtures/bots.js'
6
+
7
+ describe('server routes', async () => {
8
+ const databaseUrl = 'sqlite::memory:'
9
+ const origin = 'https://activitypubbot.test'
10
+ const app = await makeApp(databaseUrl, origin, bots, 'silent')
11
+ describe('GET /', async () => {
12
+ let response = null
13
+ it('should work without an error', async () => {
14
+ response = await request(app).get('/')
15
+ })
16
+ it('should return 200 OK', async () => {
17
+ assert.strictEqual(response.status, 200)
18
+ })
19
+ it('should return AS2', async () => {
20
+ assert.strictEqual(response.type, 'application/activity+json')
21
+ })
22
+ it('should return an object', async () => {
23
+ assert.strictEqual(typeof response.body, 'object')
24
+ })
25
+ it('should return an object with an id', async () => {
26
+ assert.strictEqual(typeof response.body.id, 'string')
27
+ })
28
+ it('should return an object with an id matching the origin', async () => {
29
+ assert.strictEqual(response.body.id, origin + '/')
30
+ })
31
+ it('should return an object with a publicKey', async () => {
32
+ assert.strictEqual(typeof response.body.publicKey, 'string')
33
+ })
34
+ })
35
+ describe('GET /publickey', async () => {
36
+ let response = null
37
+ it('should work without an error', async () => {
38
+ response = await request(app).get('/publickey')
39
+ })
40
+ it('should return 200 OK', async () => {
41
+ assert.strictEqual(response.status, 200)
42
+ })
43
+ it('should return AS2', async () => {
44
+ assert.strictEqual(response.type, 'application/activity+json')
45
+ })
46
+ it('should return an object', async () => {
47
+ assert.strictEqual(typeof response.body, 'object')
48
+ })
49
+ it('should return an object with an id', async () => {
50
+ assert.strictEqual(typeof response.body.id, 'string')
51
+ })
52
+ it('should return an object with an id matching the origin', async () => {
53
+ assert.strictEqual(response.body.id, origin + '/publickey')
54
+ })
55
+ it('should return an object with an owner', async () => {
56
+ assert.strictEqual(typeof response.body.owner, 'string')
57
+ })
58
+ it('should return an object with the origin as owner', async () => {
59
+ assert.strictEqual(response.body.owner, origin + '/')
60
+ })
61
+ it('should return an object with a publicKeyPem', async () => {
62
+ assert.strictEqual(typeof response.body.publicKeyPem, 'string')
63
+ })
64
+ it('publicKeyPem should be an RSA PKCS-8 key', async () => {
65
+ assert.match(response.body.publicKeyPem, /^-----BEGIN PUBLIC KEY-----\n/)
66
+ assert.match(response.body.publicKeyPem, /\n-----END PUBLIC KEY-----\n$/)
67
+ })
68
+ })
69
+ })
@@ -0,0 +1,41 @@
1
+ import { describe, it } from 'node:test'
2
+ import assert from 'node:assert'
3
+ import { makeApp } from '../lib/app.js'
4
+ import request from 'supertest'
5
+ import bots from './fixtures/bots.js'
6
+
7
+ describe('webfinger routes', async () => {
8
+ const databaseUrl = 'sqlite::memory:'
9
+ const origin = 'https://activitypubbot.test'
10
+ const app = await makeApp(databaseUrl, origin, bots, 'silent')
11
+ describe('GET /.well-known/webfinger', async () => {
12
+ let response = null
13
+ it('should work without an error', async () => {
14
+ response = await request(app).get('/.well-known/webfinger?resource=acct%3Aok%40activitypubbot.test')
15
+ })
16
+ it('should return 200 OK', async () => {
17
+ assert.strictEqual(response.status, 200)
18
+ })
19
+ it('should return JRD', async () => {
20
+ assert.strictEqual(response.type, 'application/jrd+json')
21
+ })
22
+ it('should return an object with a subject', async () => {
23
+ assert.strictEqual(typeof response.body.subject, 'string')
24
+ })
25
+ it('should return an object with an subject matching the request', async () => {
26
+ assert.strictEqual(response.body.subject, 'acct:ok@activitypubbot.test')
27
+ })
28
+ it('should return an object with a links array', async () => {
29
+ assert.strictEqual(Array.isArray(response.body.links), true)
30
+ })
31
+ it('should return an object with a links array containing the actor id', async () => {
32
+ assert.strictEqual(response.body.links.length, 1)
33
+ assert.strictEqual(typeof response.body.links[0].rel, 'string')
34
+ assert.strictEqual(response.body.links[0].rel, 'self')
35
+ assert.strictEqual(typeof response.body.links[0].type, 'string')
36
+ assert.strictEqual(response.body.links[0].type, 'application/activity+json')
37
+ assert.strictEqual(typeof response.body.links[0].href, 'string')
38
+ assert.strictEqual(response.body.links[0].href, 'https://activitypubbot.test/user/ok')
39
+ })
40
+ })
41
+ })