@evanp/activitypub-bot 0.13.0 → 0.13.1

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 (41) hide show
  1. package/package.json +2 -2
  2. package/.github/dependabot.yml +0 -11
  3. package/.github/workflows/main.yml +0 -34
  4. package/.github/workflows/tag.yml +0 -106
  5. package/.nvmrc +0 -1
  6. package/Dockerfile +0 -17
  7. package/docs/activitypub.bot.drawio +0 -110
  8. package/tests/activitydistributor.test.js +0 -606
  9. package/tests/activityhandler.test.js +0 -2276
  10. package/tests/activitypubclient.test.js +0 -210
  11. package/tests/actorstorage.test.js +0 -283
  12. package/tests/app.test.js +0 -17
  13. package/tests/authorizer.test.js +0 -301
  14. package/tests/bot.donothing.test.js +0 -30
  15. package/tests/bot.ok.test.js +0 -101
  16. package/tests/botcontext.test.js +0 -720
  17. package/tests/botdatastorage.test.js +0 -88
  18. package/tests/botfactory.provincebotfactory.test.js +0 -430
  19. package/tests/digester.test.js +0 -56
  20. package/tests/fixtures/bots.js +0 -27
  21. package/tests/fixtures/eventloggingbot.js +0 -57
  22. package/tests/fixtures/provincebotfactory.js +0 -53
  23. package/tests/httpsignature.test.js +0 -199
  24. package/tests/httpsignatureauthenticator.test.js +0 -463
  25. package/tests/index.test.js +0 -12
  26. package/tests/keystorage.test.js +0 -124
  27. package/tests/microsyntax.test.js +0 -123
  28. package/tests/objectcache.test.js +0 -133
  29. package/tests/objectstorage.test.js +0 -149
  30. package/tests/remotekeystorage.test.js +0 -78
  31. package/tests/routes.actor.test.js +0 -214
  32. package/tests/routes.collection.test.js +0 -310
  33. package/tests/routes.health.test.js +0 -41
  34. package/tests/routes.inbox.test.js +0 -216
  35. package/tests/routes.object.test.js +0 -525
  36. package/tests/routes.server.test.js +0 -69
  37. package/tests/routes.sharedinbox.test.js +0 -473
  38. package/tests/routes.webfinger.test.js +0 -68
  39. package/tests/urlformatter.test.js +0 -164
  40. package/tests/utils/digest.js +0 -7
  41. package/tests/utils/nock.js +0 -499
@@ -1,210 +0,0 @@
1
- import { describe, before, after, it, beforeEach } from 'node:test'
2
- import { KeyStorage } from '../lib/keystorage.js'
3
- import { UrlFormatter } from '../lib/urlformatter.js'
4
- import { ActivityPubClient } from '../lib/activitypubclient.js'
5
- import assert from 'node:assert'
6
- import { Sequelize } from 'sequelize'
7
- import as2 from '../lib/activitystreams.js'
8
- import Logger from 'pino'
9
- import { HTTPSignature } from '../lib/httpsignature.js'
10
- import { Digester } from '../lib/digester.js'
11
- import { runMigrations } from '../lib/migrations/index.js'
12
- import {
13
- nockSetup,
14
- getRequestHeaders,
15
- resetRequestHeaders,
16
- addToCollection,
17
- nockFormat
18
- } from './utils/nock.js'
19
-
20
- describe('ActivityPubClient', async () => {
21
- let connection = null
22
- let keyStorage = null
23
- let formatter = null
24
- let client = null
25
- let signer = null
26
- let digester = null
27
- let logger = null
28
- const remoteUser = 'remote1'
29
- const remoteCollection = 1
30
- const remoteOrderedCollection = 2
31
- const remotePagedCollection = 3
32
- const remotePagedOrderedCollection = 4
33
- const maxItems = 10
34
- before(async () => {
35
- logger = new Logger({
36
- level: 'silent'
37
- })
38
- digester = new Digester(logger)
39
- signer = new HTTPSignature(logger)
40
- connection = new Sequelize({ dialect: 'sqlite', storage: ':memory:', logging: false })
41
- await connection.authenticate()
42
- await runMigrations(connection)
43
- keyStorage = new KeyStorage(connection, logger)
44
- formatter = new UrlFormatter('https://activitypubbot.example')
45
- const remote = 'social.example'
46
- nockSetup(remote, logger)
47
- for (let i = 0; i < maxItems; i++) {
48
- const id = nockFormat({ username: remoteUser, type: 'note', num: i })
49
- addToCollection(remoteUser, remoteCollection, id, remote)
50
- }
51
- for (let i = maxItems; i < 2 * maxItems; i++) {
52
- const id = nockFormat({ username: remoteUser, type: 'note', num: i })
53
- addToCollection(remoteUser, remoteOrderedCollection, id, remote)
54
- }
55
- for (let i = 2 * maxItems; i < 7 * maxItems; i++) {
56
- const id = nockFormat({ username: remoteUser, type: 'note', num: i })
57
- addToCollection(remoteUser, remotePagedCollection, id, remote)
58
- }
59
- for (let i = 7 * maxItems; i < 12 * maxItems; i++) {
60
- const id = nockFormat({ username: remoteUser, type: 'note', num: i })
61
- addToCollection(remoteUser, remotePagedOrderedCollection, id, remote)
62
- }
63
- })
64
- after(async () => {
65
- await connection.close()
66
- keyStorage = null
67
- connection = null
68
- formatter = null
69
- client = null
70
- logger = null
71
- digester = null
72
- signer = null
73
- })
74
- beforeEach(async () => {
75
- resetRequestHeaders()
76
- })
77
- it('can initialize', () => {
78
- client = new ActivityPubClient(keyStorage, formatter, signer, digester, logger)
79
- assert.ok(client)
80
- })
81
- it('can get a remote object with a username', async () => {
82
- const id = 'https://social.example/user/evan/note/1'
83
- const obj = await client.get(id, 'foobot')
84
- assert.ok(obj)
85
- assert.equal(typeof obj, 'object')
86
- assert.equal(obj.id, id)
87
- const h = getRequestHeaders(id)
88
- assert.ok(h.signature)
89
- assert.match(h.signature, /^keyId="https:\/\/activitypubbot\.example\/user\/foobot\/publickey",headers="\(request-target\) host date user-agent accept",signature=".*",algorithm="rsa-sha256"$/)
90
- assert.equal(typeof h.digest, 'undefined')
91
- assert.equal(typeof h.date, 'string')
92
- assert.match(h.date, /^\w{3}, \d{2} \w{3} \d{4} \d{2}:\d{2}:\d{2} GMT$/)
93
- assert.doesNotThrow(() => {
94
- Date.parse(h.date)
95
- })
96
- })
97
- it('can get a remote object without a username', async () => {
98
- const id = 'https://social.example/user/evan/note/1'
99
- const obj = await client.get(id)
100
- assert.ok(obj)
101
- assert.equal(typeof obj, 'object')
102
- assert.equal(obj.id, id)
103
- const h = getRequestHeaders(id)
104
- assert.ok(h.signature)
105
- assert.match(h.signature, /^keyId="https:\/\/activitypubbot\.example\/publickey",headers="\(request-target\) host date user-agent accept",signature=".*",algorithm="rsa-sha256"$/)
106
- assert.equal(typeof h.digest, 'undefined')
107
- assert.equal(typeof h.date, 'string')
108
- assert.match(h.date, /^\w{3}, \d{2} \w{3} \d{4} \d{2}:\d{2}:\d{2} GMT$/)
109
- assert.doesNotThrow(() => {
110
- Date.parse(h.date)
111
- })
112
- })
113
- it('can get a remote key without a signature', async () => {
114
- const id = 'https://social.example/user/evan/publickey'
115
- const obj = await client.getKey(id)
116
- assert.ok(obj)
117
- assert.equal(typeof obj, 'object')
118
- assert.equal(obj.id, id)
119
- const h = getRequestHeaders(id)
120
- assert.equal(h.signature, undefined)
121
- assert.equal(typeof h.digest, 'undefined')
122
- assert.equal(typeof h.date, 'string')
123
- assert.match(h.date, /^\w{3}, \d{2} \w{3} \d{4} \d{2}:\d{2}:\d{2} GMT$/)
124
- assert.doesNotThrow(() => {
125
- Date.parse(h.date)
126
- })
127
- })
128
- it('can deliver an activity', async () => {
129
- const obj = as2.follow()
130
- .actor('https://activitypubbot.example/user/foobot')
131
- .object('https://social.example/user/evan')
132
- .to('https://social.example/user/evan')
133
- .publishedNow()
134
- .get()
135
- const inbox = 'https://social.example/user/evan/inbox'
136
- await client.post(inbox, obj, 'foobot')
137
- const h = getRequestHeaders(inbox)
138
- assert.ok(h.signature)
139
- assert.ok(h.digest)
140
- assert.match(h.signature, /^keyId="https:\/\/activitypubbot\.example\/user\/foobot\/publickey",headers="\(request-target\) host date user-agent content-type digest",signature=".*",algorithm="rsa-sha256"$/)
141
- assert.match(h.digest, /^sha-256=[0-9a-zA-Z=+/]*$/)
142
- assert.equal(typeof h.date, 'string')
143
- assert.match(h.date, /^\w{3}, \d{2} \w{3} \d{4} \d{2}:\d{2}:\d{2} GMT$/)
144
- assert.doesNotThrow(() => {
145
- Date.parse(h.date)
146
- })
147
- })
148
- it('throws an error on a non-2xx response', async () => {
149
- const inbox = 'https://social.example/user/evan/inbox'
150
- try {
151
- await client.get(inbox, 'foobot')
152
- assert.fail('should have thrown')
153
- } catch (error) {
154
- assert.ok(error)
155
- assert.equal(error.status, 403)
156
- }
157
- })
158
- it('can iterate over a Collection', async () => {
159
- const collectionUri = nockFormat({
160
- username: remoteUser,
161
- type: 'Collection',
162
- num: remoteCollection
163
- })
164
- let counter = 0
165
- for await (const item of client.items(collectionUri)) {
166
- assert.ok(item)
167
- counter = counter + 1
168
- }
169
- assert.strictEqual(counter, maxItems)
170
- })
171
- it('can iterate over an OrderedCollection', async () => {
172
- const collectionUri = nockFormat({
173
- username: remoteUser,
174
- type: 'OrderedCollection',
175
- num: remoteOrderedCollection
176
- })
177
- let counter = 0
178
- for await (const item of client.items(collectionUri)) {
179
- assert.ok(item)
180
- counter = counter + 1
181
- }
182
- assert.strictEqual(counter, maxItems)
183
- })
184
- it('can iterate over a paged Collection', async () => {
185
- const collectionUri = nockFormat({
186
- username: remoteUser,
187
- type: 'PagedCollection', // Fake type
188
- num: remotePagedCollection
189
- })
190
- let counter = 0
191
- for await (const item of client.items(collectionUri)) {
192
- assert.ok(item)
193
- counter = counter + 1
194
- }
195
- assert.strictEqual(counter, 5 * maxItems)
196
- })
197
- it('can iterate over a paged OrderedCollection', async () => {
198
- const collectionUri = nockFormat({
199
- username: remoteUser,
200
- type: 'PagedOrderedCollection', // Fake type
201
- num: remotePagedOrderedCollection
202
- })
203
- let counter = 0
204
- for await (const item of client.items(collectionUri)) {
205
- assert.ok(item)
206
- counter = counter + 1
207
- }
208
- assert.strictEqual(counter, 5 * maxItems)
209
- })
210
- })
@@ -1,283 +0,0 @@
1
- import { describe, it, before, after } from 'node:test'
2
- import assert from 'node:assert'
3
- import { ActorStorage } from '../lib/actorstorage.js'
4
- import { Sequelize } from 'sequelize'
5
- import { UrlFormatter } from '../lib/urlformatter.js'
6
- import as2 from '../lib/activitystreams.js'
7
- import { runMigrations } from '../lib/migrations/index.js'
8
-
9
- const AS2_NS = 'https://www.w3.org/ns/activitystreams#'
10
-
11
- describe('ActorStorage', () => {
12
- let connection = null
13
- let storage = null
14
- let formatter = null
15
- let other = null
16
- let unfollowed = null
17
- before(async () => {
18
- connection = new Sequelize({ dialect: 'sqlite', storage: ':memory:', logging: false })
19
- await connection.authenticate()
20
- await runMigrations(connection)
21
- formatter = new UrlFormatter('https://activitypubbot.example')
22
- other = await as2.import({
23
- id: 'https://social.example/user/test2',
24
- type: 'Person'
25
- })
26
- unfollowed = await as2.import({
27
- id: 'https://social.example/user/test3',
28
- type: 'Person'
29
- })
30
- })
31
- after(async () => {
32
- await connection.close()
33
- connection = null
34
- formatter = null
35
- })
36
- it('can create an instance', () => {
37
- storage = new ActorStorage(connection, formatter)
38
- assert.ok(storage instanceof ActorStorage)
39
- })
40
- it('can initialize the storage', async () => {
41
- })
42
- it('can get an actor', async () => {
43
- const actor = await storage.getActor('test')
44
- assert.ok(actor)
45
- assert.ok(actor.id)
46
- assert.ok(actor.inbox)
47
- assert.ok(actor.outbox)
48
- assert.ok(actor.followers)
49
- assert.ok(actor.following)
50
- assert.ok(actor.liked)
51
- assert.strictEqual(actor.get('preferredUsername').first, 'test')
52
- })
53
-
54
- it('can get an actor by id', async () => {
55
- const actor = await storage.getActorById('https://activitypubbot.example/user/test')
56
- assert.ok(actor)
57
- assert.ok(actor.id)
58
- assert.ok(actor.inbox)
59
- assert.ok(actor.outbox)
60
- assert.ok(actor.followers)
61
- assert.ok(actor.following)
62
- assert.ok(actor.liked)
63
- assert.strictEqual(actor.get('preferredUsername').first, 'test')
64
- })
65
- it('can get an empty collection', async () => {
66
- const collection = await storage.getCollection('test', 'followers')
67
- assert.ok(collection)
68
- assert.strictEqual(collection.id, 'https://activitypubbot.example/user/test/followers')
69
- assert.strictEqual(collection.type, 'https://www.w3.org/ns/activitystreams#OrderedCollection')
70
- assert.strictEqual(collection.totalItems, 0)
71
- assert.ok(collection.first)
72
- assert.ok(collection.last)
73
- })
74
- it('can get an empty collection page', async () => {
75
- const page = await storage.getCollectionPage('test', 'followers', 1)
76
- assert.ok(page)
77
- assert.strictEqual(
78
- page.id,
79
- 'https://activitypubbot.example/user/test/followers/1'
80
- )
81
- assert.strictEqual(page.type, 'https://www.w3.org/ns/activitystreams#OrderedCollectionPage')
82
- assert.strictEqual(
83
- page.partOf.id,
84
- 'https://activitypubbot.example/user/test/followers'
85
- )
86
- assert.ok(!page.next)
87
- assert.ok(!page.prev)
88
- })
89
- it('can add to a collection', async () => {
90
- const collection = await storage.getCollection('test3', 'followers')
91
- assert.strictEqual(collection.totalItems, 0)
92
- await storage.addToCollection(
93
- 'test3',
94
- 'followers',
95
- other
96
- )
97
- const collection2 = await storage.getCollection('test3', 'followers')
98
- assert.strictEqual(collection2.totalItems, 1)
99
- const page = await storage.getCollectionPage('test3', 'followers', 1)
100
- assert.strictEqual(page.items.length, 1)
101
- assert.strictEqual(Array.from(page.items)[0].id, 'https://social.example/user/test2')
102
- })
103
- it('can remove from a collection', async () => {
104
- await storage.removeFromCollection(
105
- 'test3',
106
- 'followers',
107
- other
108
- )
109
- const collection2 = await storage.getCollection('test3', 'followers')
110
- assert.strictEqual(collection2.totalItems, 0)
111
- const page = await storage.getCollectionPage('test3', 'followers', 1)
112
- assert.ok(!page.items)
113
- })
114
- it('can add a lot of items a collection', async () => {
115
- for (let i = 0; i < 100; i++) {
116
- const other = await as2.import({
117
- id: `https://social.example/user/foo/note/${i}`,
118
- type: 'Note',
119
- content: `Hello World ${i}`
120
- })
121
- await storage.addToCollection(
122
- 'test4',
123
- 'liked',
124
- other
125
- )
126
- }
127
- const collection = await storage.getCollection('test4', 'liked')
128
- assert.strictEqual(collection.totalItems, 100)
129
- const page = await storage.getCollectionPage('test4', 'liked', 3)
130
- assert.strictEqual(page.items.length, 20)
131
- assert.strictEqual(page.next.id, 'https://activitypubbot.example/user/test4/liked/2')
132
- })
133
- it('can iterate over a collection', async () => {
134
- const seen = new Set()
135
- for await (const item of storage.items('test4', 'liked')) {
136
- assert.ok(!(item.id in seen))
137
- seen.add(item.id)
138
- }
139
- assert.strictEqual(seen.size, 100)
140
- })
141
- it('can add twice and remove once from a collection', async () => {
142
- const other = await as2.import({
143
- id: 'https://social.example/user/foo/note/200',
144
- type: 'Note',
145
- content: 'Hello World 200'
146
- })
147
- const other2 = await as2.import({
148
- id: 'https://social.example/user/foo/note/201',
149
- type: 'Note',
150
- content: 'Hello World 201'
151
- })
152
- const collection = await storage.getCollection('test5', 'liked')
153
- assert.strictEqual(collection.totalItems, 0)
154
- await storage.addToCollection(
155
- 'test5',
156
- 'liked',
157
- other
158
- )
159
- await storage.addToCollection(
160
- 'test5',
161
- 'liked',
162
- other2
163
- )
164
- const collection2 = await storage.getCollection('test5', 'liked')
165
- assert.strictEqual(collection2.totalItems, 2)
166
- await storage.removeFromCollection(
167
- 'test5',
168
- 'liked',
169
- other
170
- )
171
- const collection3 = await storage.getCollection('test5', 'liked')
172
- assert.strictEqual(collection3.totalItems, 1)
173
- })
174
- it('can check if something is in the collection', async () => {
175
- const other = await as2.import({
176
- id: 'https://social.example/user/foo/note/300',
177
- type: 'Note',
178
- content: 'Hello World 300'
179
- })
180
- const other2 = await as2.import({
181
- id: 'https://social.example/user/foo/note/301',
182
- type: 'Note',
183
- content: 'Hello World 301'
184
- })
185
- let collection = await storage.getCollection('test6', 'liked')
186
- assert.strictEqual(collection.totalItems, 0)
187
- await storage.addToCollection(
188
- 'test6',
189
- 'liked',
190
- other
191
- )
192
- collection = await storage.getCollection('test6', 'liked')
193
- assert.strictEqual(collection.totalItems, 1)
194
- assert.ok(await storage.isInCollection(
195
- 'test6',
196
- 'liked',
197
- other
198
- ))
199
- assert.ok(!await storage.isInCollection(
200
- 'test6',
201
- 'liked',
202
- other2
203
- ))
204
- })
205
-
206
- it('retains totalItems when we remove an absent object', async () => {
207
- const other = await as2.import({
208
- id: 'https://social.example/user/foo/note/400',
209
- type: 'Note',
210
- content: 'Hello World 400'
211
- })
212
- const other2 = await as2.import({
213
- id: 'https://social.example/user/foo/note/401',
214
- type: 'Note',
215
- content: 'Hello World 401'
216
- })
217
- const other3 = await as2.import({
218
- id: 'https://social.example/user/foo/note/402',
219
- type: 'Note',
220
- content: 'Hello World 402'
221
- })
222
- let collection = await storage.getCollection('test7', 'liked')
223
- assert.strictEqual(collection.totalItems, 0)
224
- await storage.addToCollection(
225
- 'test7',
226
- 'liked',
227
- other
228
- )
229
- await storage.addToCollection(
230
- 'test7',
231
- 'liked',
232
- other2
233
- )
234
- collection = await storage.getCollection('test7', 'liked')
235
- assert.strictEqual(collection.totalItems, 2)
236
- await storage.removeFromCollection(
237
- 'test7',
238
- 'liked',
239
- other3
240
- )
241
- collection = await storage.getCollection('test7', 'liked')
242
- assert.strictEqual(collection.totalItems, 2)
243
- })
244
- it('can get an actor with custom properties', async () => {
245
- const props = {
246
- name: 'Test User',
247
- summary: 'A test user',
248
- type: 'Person'
249
- }
250
- const actor = await storage.getActor('test8', props)
251
- assert.ok(actor)
252
- assert.ok(actor.id)
253
- assert.ok(actor.inbox)
254
- assert.ok(actor.outbox)
255
- assert.ok(actor.followers)
256
- assert.ok(actor.following)
257
- assert.ok(actor.liked)
258
- assert.strictEqual(actor.get('preferredUsername').first, 'test8')
259
- assert.strictEqual(actor.name.get(), 'Test User')
260
- assert.strictEqual(actor.summary.get(), 'A test user')
261
- assert.ok(Array.isArray(actor.type))
262
- assert.ok(actor.type.includes(AS2_NS + 'Person'))
263
- assert.ok(actor.type.includes(AS2_NS + 'Service'))
264
- })
265
-
266
- it('can get all actors with an object in a collection', async () => {
267
- for (const i of [101, 102, 103, 104, 105]) {
268
- await storage.addToCollection(`test${i}`, 'following', other)
269
- }
270
- const usernames = await storage.getUsernamesWith('following', other)
271
- assert.strictEqual(usernames.length, 5)
272
- assert.ok(usernames.includes('test101'))
273
- assert.ok(usernames.includes('test102'))
274
- assert.ok(usernames.includes('test103'))
275
- assert.ok(usernames.includes('test104'))
276
- assert.ok(usernames.includes('test105'))
277
- })
278
-
279
- it('gets zero usernames when an object is in no collection', async () => {
280
- const usernames = await storage.getUsernamesWith('following', unfollowed)
281
- assert.strictEqual(usernames.length, 0)
282
- })
283
- })
package/tests/app.test.js DELETED
@@ -1,17 +0,0 @@
1
- import { describe, it } from 'node:test'
2
- import assert from 'node:assert'
3
- import { makeApp } from '../lib/app.js'
4
- import bots from './fixtures/bots.js'
5
-
6
- describe('app', async () => {
7
- const databaseUrl = 'sqlite::memory:'
8
- const origin = 'https://activitypubbot.test'
9
- let app = null
10
- it('should be a function', async () => {
11
- assert.strictEqual(typeof makeApp, 'function')
12
- })
13
- it('should return a function', async () => {
14
- app = await makeApp(databaseUrl, origin, bots, 'silent')
15
- assert.strictEqual(typeof app, 'function')
16
- })
17
- })