@evanp/activitypub-bot 0.8.0 → 0.11.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.
- package/.github/workflows/main.yml +34 -0
- package/.github/workflows/{tag-docker.yml → tag.yml} +57 -5
- package/.nvmrc +1 -0
- package/Dockerfile +11 -16
- package/README.md +262 -12
- package/activitypub-bot.js +68 -0
- package/lib/activitydeliverer.js +260 -0
- package/lib/activityhandler.js +14 -0
- package/lib/activitypubclient.js +52 -1
- package/lib/activitystreams.js +31 -0
- package/lib/actorstorage.js +18 -28
- package/lib/app.js +18 -7
- package/lib/bot.js +7 -0
- package/lib/botcontext.js +62 -0
- package/lib/botdatastorage.js +0 -13
- package/lib/botfactory.js +24 -0
- package/lib/botmaker.js +23 -0
- package/lib/keystorage.js +7 -24
- package/lib/migrations/001-initial.js +107 -0
- package/lib/migrations/index.js +28 -0
- package/lib/objectcache.js +4 -1
- package/lib/objectstorage.js +0 -36
- package/lib/remotekeystorage.js +0 -24
- package/lib/routes/collection.js +6 -2
- package/lib/routes/inbox.js +7 -20
- package/lib/routes/sharedinbox.js +54 -0
- package/lib/routes/user.js +11 -5
- package/lib/routes/webfinger.js +11 -2
- package/lib/urlformatter.js +8 -0
- package/package.json +18 -11
- package/tests/activitydistributor.test.js +3 -3
- package/tests/activityhandler.test.js +96 -5
- package/tests/activitypubclient.test.js +115 -130
- package/tests/actorstorage.test.js +26 -4
- package/tests/authorizer.test.js +3 -8
- package/tests/botcontext.test.js +109 -63
- package/tests/botdatastorage.test.js +3 -2
- package/tests/botfactory.provincebotfactory.test.js +430 -0
- package/tests/fixtures/bots.js +13 -1
- package/tests/fixtures/eventloggingbot.js +57 -0
- package/tests/fixtures/provincebotfactory.js +53 -0
- package/tests/httpsignature.test.js +3 -4
- package/tests/httpsignatureauthenticator.test.js +3 -3
- package/tests/keystorage.test.js +37 -2
- package/tests/microsyntax.test.js +3 -2
- package/tests/objectstorage.test.js +4 -3
- package/tests/remotekeystorage.test.js +10 -8
- package/tests/routes.actor.test.js +7 -0
- package/tests/routes.collection.test.js +0 -1
- package/tests/routes.inbox.test.js +1 -0
- package/tests/routes.object.test.js +44 -38
- package/tests/routes.sharedinbox.test.js +473 -0
- package/tests/routes.webfinger.test.js +27 -0
- package/tests/utils/nock.js +250 -27
- package/.github/workflows/main-docker.yml +0 -45
- package/index.js +0 -23
|
@@ -17,6 +17,9 @@ import bots from './fixtures/bots.js'
|
|
|
17
17
|
import { nockSetup, postInbox, makeActor, nockFormat } from './utils/nock.js'
|
|
18
18
|
import { Digester } from '../lib/digester.js'
|
|
19
19
|
import { HTTPSignature } from '../lib/httpsignature.js'
|
|
20
|
+
import { runMigrations } from '../lib/migrations/index.js'
|
|
21
|
+
import { BotContext } from '../lib/botcontext.js'
|
|
22
|
+
import { Transformer } from '../lib/microsyntax.js'
|
|
20
23
|
|
|
21
24
|
describe('ActivityHandler', () => {
|
|
22
25
|
const domain = 'activitypubbot.example'
|
|
@@ -36,27 +39,46 @@ describe('ActivityHandler', () => {
|
|
|
36
39
|
let botId = null
|
|
37
40
|
const botName = 'ok'
|
|
38
41
|
let bot = null
|
|
42
|
+
const loggerBotName = 'logging'
|
|
43
|
+
let lb = null
|
|
44
|
+
let lbId = null
|
|
45
|
+
let transformer = null
|
|
39
46
|
before(async () => {
|
|
40
47
|
logger = Logger({ level: 'silent' })
|
|
41
48
|
formatter = new UrlFormatter(origin)
|
|
42
|
-
connection = new Sequelize('sqlite
|
|
49
|
+
connection = new Sequelize({ dialect: 'sqlite', storage: ':memory:', logging: false })
|
|
43
50
|
await connection.authenticate()
|
|
51
|
+
await runMigrations(connection)
|
|
44
52
|
botDataStorage = new BotDataStorage(connection)
|
|
45
|
-
await botDataStorage.initialize()
|
|
46
53
|
objectStorage = new ObjectStorage(connection)
|
|
47
|
-
await objectStorage.initialize()
|
|
48
54
|
keyStorage = new KeyStorage(connection, logger)
|
|
49
|
-
await keyStorage.initialize()
|
|
50
55
|
actorStorage = new ActorStorage(connection, formatter)
|
|
51
|
-
await actorStorage.initialize()
|
|
52
56
|
const signer = new HTTPSignature(logger)
|
|
53
57
|
const digester = new Digester(logger)
|
|
54
58
|
client = new ActivityPubClient(keyStorage, formatter, signer, digester, logger)
|
|
55
59
|
distributor = new ActivityDistributor(client, formatter, actorStorage, logger)
|
|
56
60
|
authz = new Authorizer(actorStorage, formatter, client)
|
|
57
61
|
cache = new ObjectCache({ longTTL: 3600 * 1000, shortTTL: 300 * 1000, maxItems: 1000 })
|
|
62
|
+
transformer = new Transformer(`${origin}/tag/`, client)
|
|
63
|
+
await Promise.all(
|
|
64
|
+
Object.values(bots).map(bot => bot.initialize(
|
|
65
|
+
new BotContext(
|
|
66
|
+
bot.username,
|
|
67
|
+
botDataStorage,
|
|
68
|
+
objectStorage,
|
|
69
|
+
actorStorage,
|
|
70
|
+
client,
|
|
71
|
+
distributor,
|
|
72
|
+
formatter,
|
|
73
|
+
transformer,
|
|
74
|
+
logger
|
|
75
|
+
)
|
|
76
|
+
))
|
|
77
|
+
)
|
|
58
78
|
botId = formatter.format({ username: botName })
|
|
79
|
+
lbId = formatter.format({ username: loggerBotName })
|
|
59
80
|
bot = bots[botName]
|
|
81
|
+
lb = bots[loggerBotName]
|
|
60
82
|
await objectStorage.create(await as2.import({
|
|
61
83
|
id: formatter.format({ username: 'test1', type: 'object', nanoid: '_pEWsKke-7lACTdM3J_qd' }),
|
|
62
84
|
type: 'Object',
|
|
@@ -299,6 +321,19 @@ describe('ActivityHandler', () => {
|
|
|
299
321
|
await handler.onIdle()
|
|
300
322
|
assert.ok(!postInbox.follower3)
|
|
301
323
|
})
|
|
324
|
+
it('notifies the bot of a follow activity', async () => {
|
|
325
|
+
const actor = await makeActor('follower4')
|
|
326
|
+
const activity = await as2.import({
|
|
327
|
+
type: 'Follow',
|
|
328
|
+
id: 'https://social.example/user/follower4/follow/1',
|
|
329
|
+
actor: actor.id,
|
|
330
|
+
object: lbId,
|
|
331
|
+
to: lbId
|
|
332
|
+
})
|
|
333
|
+
await handler.handleActivity(lb, activity)
|
|
334
|
+
assert.ok(lb.follows.has(activity.id))
|
|
335
|
+
await handler.onIdle()
|
|
336
|
+
})
|
|
302
337
|
it('can handle an accept activity', async () => {
|
|
303
338
|
const actor = await makeActor('accepter1')
|
|
304
339
|
const followActivity = await as2.import({
|
|
@@ -811,6 +846,34 @@ describe('ActivityHandler', () => {
|
|
|
811
846
|
await objectStorage.isInCollection(note.id, 'likes', activity2)
|
|
812
847
|
)
|
|
813
848
|
})
|
|
849
|
+
it('notifies the bot of a like activity', async () => {
|
|
850
|
+
const actor = await makeActor('liker9')
|
|
851
|
+
const note = await as2.import({
|
|
852
|
+
attributedTo: lbId,
|
|
853
|
+
id: formatter.format({
|
|
854
|
+
username: loggerBotName,
|
|
855
|
+
type: 'note',
|
|
856
|
+
nanoid: 'IGeAucyHD-s3Ywg9X9sCo'
|
|
857
|
+
}),
|
|
858
|
+
type: 'Note',
|
|
859
|
+
content: 'Hello, world!',
|
|
860
|
+
to: 'as:Public'
|
|
861
|
+
})
|
|
862
|
+
await objectStorage.create(note)
|
|
863
|
+
const activity = await as2.import({
|
|
864
|
+
type: 'Like',
|
|
865
|
+
actor: actor.id,
|
|
866
|
+
id: nockFormat({
|
|
867
|
+
username: 'liker9',
|
|
868
|
+
type: 'Like',
|
|
869
|
+
nanoid: '3fKK6LcMtqrAp1Ekn471u'
|
|
870
|
+
}),
|
|
871
|
+
object: note.id,
|
|
872
|
+
to: [lbId, 'as:Public']
|
|
873
|
+
})
|
|
874
|
+
await handler.handleActivity(lb, activity)
|
|
875
|
+
assert.ok(lb.likes.has(activity.id))
|
|
876
|
+
})
|
|
814
877
|
it('can handle an announce activity', async () => {
|
|
815
878
|
const actor = await makeActor('announcer1')
|
|
816
879
|
const note = await as2.import({
|
|
@@ -1017,6 +1080,34 @@ describe('ActivityHandler', () => {
|
|
|
1017
1080
|
await objectStorage.isInCollection(note.id, 'shares', activity2)
|
|
1018
1081
|
)
|
|
1019
1082
|
})
|
|
1083
|
+
it('notifies the bot on an announce activity', async () => {
|
|
1084
|
+
const actor = await makeActor('announcer9')
|
|
1085
|
+
const note = await as2.import({
|
|
1086
|
+
attributedTo: lbId,
|
|
1087
|
+
id: formatter.format({
|
|
1088
|
+
username: loggerBotName,
|
|
1089
|
+
type: 'note',
|
|
1090
|
+
nanoid: 'LNCVgovrjpA6oSKnGDax2'
|
|
1091
|
+
}),
|
|
1092
|
+
type: 'Note',
|
|
1093
|
+
content: 'Hello, world!',
|
|
1094
|
+
to: 'as:Public'
|
|
1095
|
+
})
|
|
1096
|
+
await objectStorage.create(note)
|
|
1097
|
+
const activity = await as2.import({
|
|
1098
|
+
type: 'Announce',
|
|
1099
|
+
actor: actor.id,
|
|
1100
|
+
id: nockFormat({
|
|
1101
|
+
username: 'announcer9',
|
|
1102
|
+
type: 'Announce',
|
|
1103
|
+
nanoid: 'LmVvlEBHNf2X6nfgzMe6F'
|
|
1104
|
+
}),
|
|
1105
|
+
object: note.id,
|
|
1106
|
+
to: [lbId, 'as:Public']
|
|
1107
|
+
})
|
|
1108
|
+
await handler.handleActivity(lb, activity)
|
|
1109
|
+
assert.ok(lb.shares.has(activity.id))
|
|
1110
|
+
})
|
|
1020
1111
|
it('can handle a block activity', async () => {
|
|
1021
1112
|
const actor = await makeActor('blocker1')
|
|
1022
1113
|
await actorStorage.addToCollection(botName, 'followers', actor)
|
|
@@ -4,128 +4,62 @@ import { UrlFormatter } from '../lib/urlformatter.js'
|
|
|
4
4
|
import { ActivityPubClient } from '../lib/activitypubclient.js'
|
|
5
5
|
import assert from 'node:assert'
|
|
6
6
|
import { Sequelize } from 'sequelize'
|
|
7
|
-
import nock from 'nock'
|
|
8
7
|
import as2 from '../lib/activitystreams.js'
|
|
9
8
|
import Logger from 'pino'
|
|
10
9
|
import { HTTPSignature } from '../lib/httpsignature.js'
|
|
11
10
|
import { Digester } from '../lib/digester.js'
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
followers: `https://social.example/user/${username}/followers`,
|
|
21
|
-
following: `https://social.example/user/${username}/following`,
|
|
22
|
-
liked: `https://social.example/user/${username}/liked`,
|
|
23
|
-
publicKey: {
|
|
24
|
-
id: `https://social.example/user/${username}/publickey`,
|
|
25
|
-
owner: `https://social.example/user/${username}`,
|
|
26
|
-
type: 'CryptographicKey',
|
|
27
|
-
publicKeyPem: '-----BEGIN PUBLIC KEY-----\nFAKEFAKEFAKE\n-----END PUBLIC KEY-----'
|
|
28
|
-
}
|
|
29
|
-
})
|
|
30
|
-
|
|
31
|
-
const makeKey = (username) =>
|
|
32
|
-
as2.import({
|
|
33
|
-
id: `https://social.example/user/${username}/publickey`,
|
|
34
|
-
owner: `https://social.example/user/${username}`,
|
|
35
|
-
type: 'CryptographicKey',
|
|
36
|
-
publicKeyPem: '-----BEGIN PUBLIC KEY-----\nFAKEFAKEFAKE\n-----END PUBLIC KEY-----'
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
const makeNote = (username, num) =>
|
|
40
|
-
as2.import({
|
|
41
|
-
id: `https://social.example/user/${username}/note/${num}`,
|
|
42
|
-
type: 'Object',
|
|
43
|
-
attributedTo: `https://social.example/user/${username}`,
|
|
44
|
-
to: 'https://www.w3.org/ns/activitystreams#Public',
|
|
45
|
-
content: `This is note ${num} by ${username}.`
|
|
46
|
-
})
|
|
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'
|
|
47
19
|
|
|
48
20
|
describe('ActivityPubClient', async () => {
|
|
49
21
|
let connection = null
|
|
50
22
|
let keyStorage = null
|
|
51
23
|
let formatter = null
|
|
52
24
|
let client = null
|
|
53
|
-
let postInbox = null
|
|
54
|
-
let signature = null
|
|
55
|
-
let digest = null
|
|
56
|
-
let date = null
|
|
57
25
|
let signer = null
|
|
58
26
|
let digester = null
|
|
59
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
|
|
60
34
|
before(async () => {
|
|
61
35
|
logger = new Logger({
|
|
62
36
|
level: 'silent'
|
|
63
37
|
})
|
|
64
38
|
digester = new Digester(logger)
|
|
65
39
|
signer = new HTTPSignature(logger)
|
|
66
|
-
connection = new Sequelize('sqlite
|
|
40
|
+
connection = new Sequelize({ dialect: 'sqlite', storage: ':memory:', logging: false })
|
|
67
41
|
await connection.authenticate()
|
|
42
|
+
await runMigrations(connection)
|
|
68
43
|
keyStorage = new KeyStorage(connection, logger)
|
|
69
|
-
await keyStorage.initialize()
|
|
70
44
|
formatter = new UrlFormatter('https://activitypubbot.example')
|
|
71
|
-
const remote = '
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
digest[remote + uri] = headers.digest
|
|
90
|
-
date[remote + uri] = headers.date
|
|
91
|
-
const username = uri.match(/\/user\/(\w+)\/inbox$/)[1]
|
|
92
|
-
if (username in postInbox) {
|
|
93
|
-
postInbox[username] += 1
|
|
94
|
-
} else {
|
|
95
|
-
postInbox[username] = 1
|
|
96
|
-
}
|
|
97
|
-
return [202, 'accepted']
|
|
98
|
-
})
|
|
99
|
-
.persist()
|
|
100
|
-
.get(/\/user\/(\w+)\/note\/(\d+)$/)
|
|
101
|
-
.reply(async function (uri, requestBody) {
|
|
102
|
-
const headers = this.req.headers
|
|
103
|
-
signature[remote + uri] = headers.signature
|
|
104
|
-
digest[remote + uri] = headers.digest
|
|
105
|
-
date[remote + uri] = headers.date
|
|
106
|
-
const match = uri.match(/\/user\/(\w+)\/note\/(\d+)$/)
|
|
107
|
-
const username = match[1]
|
|
108
|
-
const num = match[2]
|
|
109
|
-
const obj = await makeNote(username, num)
|
|
110
|
-
const objText = await obj.write()
|
|
111
|
-
return [200, objText, { 'Content-Type': 'application/activity+json' }]
|
|
112
|
-
})
|
|
113
|
-
.get(/\/user\/(\w+)\/inbox$/)
|
|
114
|
-
.reply(async function (uri, requestBody) {
|
|
115
|
-
return [403, 'Forbidden', { 'Content-Type': 'text/plain' }]
|
|
116
|
-
})
|
|
117
|
-
.get(/\/user\/(\w+)\/publickey$/)
|
|
118
|
-
.reply(async function (uri, requestBody) {
|
|
119
|
-
const headers = this.req.headers
|
|
120
|
-
signature[remote + uri] = headers.signature
|
|
121
|
-
digest[remote + uri] = headers.digest
|
|
122
|
-
date[remote + uri] = headers.date
|
|
123
|
-
const username = uri.match(/\/user\/(\w+)\/publickey$/)[1]
|
|
124
|
-
const key = await makeKey(username)
|
|
125
|
-
const keyText = await key.write()
|
|
126
|
-
return [200, keyText, { 'Content-Type': 'application/activity+json' }]
|
|
127
|
-
})
|
|
128
|
-
.persist()
|
|
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
|
+
}
|
|
129
63
|
})
|
|
130
64
|
after(async () => {
|
|
131
65
|
await connection.close()
|
|
@@ -133,17 +67,12 @@ describe('ActivityPubClient', async () => {
|
|
|
133
67
|
connection = null
|
|
134
68
|
formatter = null
|
|
135
69
|
client = null
|
|
136
|
-
postInbox = null
|
|
137
|
-
signature = null
|
|
138
70
|
logger = null
|
|
139
71
|
digester = null
|
|
140
72
|
signer = null
|
|
141
73
|
})
|
|
142
74
|
beforeEach(async () => {
|
|
143
|
-
|
|
144
|
-
digest = {}
|
|
145
|
-
postInbox = {}
|
|
146
|
-
date = {}
|
|
75
|
+
resetRequestHeaders()
|
|
147
76
|
})
|
|
148
77
|
it('can initialize', () => {
|
|
149
78
|
client = new ActivityPubClient(keyStorage, formatter, signer, digester, logger)
|
|
@@ -155,13 +84,14 @@ describe('ActivityPubClient', async () => {
|
|
|
155
84
|
assert.ok(obj)
|
|
156
85
|
assert.equal(typeof obj, 'object')
|
|
157
86
|
assert.equal(obj.id, id)
|
|
158
|
-
|
|
159
|
-
assert.
|
|
160
|
-
assert.
|
|
161
|
-
assert.equal(typeof
|
|
162
|
-
assert.
|
|
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$/)
|
|
163
93
|
assert.doesNotThrow(() => {
|
|
164
|
-
Date.parse(date
|
|
94
|
+
Date.parse(h.date)
|
|
165
95
|
})
|
|
166
96
|
})
|
|
167
97
|
it('can get a remote object without a username', async () => {
|
|
@@ -170,13 +100,14 @@ describe('ActivityPubClient', async () => {
|
|
|
170
100
|
assert.ok(obj)
|
|
171
101
|
assert.equal(typeof obj, 'object')
|
|
172
102
|
assert.equal(obj.id, id)
|
|
173
|
-
|
|
174
|
-
assert.
|
|
175
|
-
assert.
|
|
176
|
-
assert.equal(typeof
|
|
177
|
-
assert.
|
|
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$/)
|
|
178
109
|
assert.doesNotThrow(() => {
|
|
179
|
-
Date.parse(date
|
|
110
|
+
Date.parse(h.date)
|
|
180
111
|
})
|
|
181
112
|
})
|
|
182
113
|
it('can get a remote key without a signature', async () => {
|
|
@@ -185,12 +116,13 @@ describe('ActivityPubClient', async () => {
|
|
|
185
116
|
assert.ok(obj)
|
|
186
117
|
assert.equal(typeof obj, 'object')
|
|
187
118
|
assert.equal(obj.id, id)
|
|
188
|
-
|
|
189
|
-
assert.equal(
|
|
190
|
-
assert.equal(typeof
|
|
191
|
-
assert.
|
|
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$/)
|
|
192
124
|
assert.doesNotThrow(() => {
|
|
193
|
-
Date.parse(date
|
|
125
|
+
Date.parse(h.date)
|
|
194
126
|
})
|
|
195
127
|
})
|
|
196
128
|
it('can deliver an activity', async () => {
|
|
@@ -202,14 +134,15 @@ describe('ActivityPubClient', async () => {
|
|
|
202
134
|
.get()
|
|
203
135
|
const inbox = 'https://social.example/user/evan/inbox'
|
|
204
136
|
await client.post(inbox, obj, 'foobot')
|
|
205
|
-
|
|
206
|
-
assert.ok(
|
|
207
|
-
assert.
|
|
208
|
-
assert.match(
|
|
209
|
-
assert.
|
|
210
|
-
assert.
|
|
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$/)
|
|
211
144
|
assert.doesNotThrow(() => {
|
|
212
|
-
Date.parse(date
|
|
145
|
+
Date.parse(h.date)
|
|
213
146
|
})
|
|
214
147
|
})
|
|
215
148
|
it('throws an error on a non-2xx response', async () => {
|
|
@@ -222,4 +155,56 @@ describe('ActivityPubClient', async () => {
|
|
|
222
155
|
assert.equal(error.status, 403)
|
|
223
156
|
}
|
|
224
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
|
+
})
|
|
225
210
|
})
|
|
@@ -4,6 +4,7 @@ import { ActorStorage } from '../lib/actorstorage.js'
|
|
|
4
4
|
import { Sequelize } from 'sequelize'
|
|
5
5
|
import { UrlFormatter } from '../lib/urlformatter.js'
|
|
6
6
|
import as2 from '../lib/activitystreams.js'
|
|
7
|
+
import { runMigrations } from '../lib/migrations/index.js'
|
|
7
8
|
|
|
8
9
|
const AS2_NS = 'https://www.w3.org/ns/activitystreams#'
|
|
9
10
|
|
|
@@ -12,14 +13,20 @@ describe('ActorStorage', () => {
|
|
|
12
13
|
let storage = null
|
|
13
14
|
let formatter = null
|
|
14
15
|
let other = null
|
|
16
|
+
let unfollowed = null
|
|
15
17
|
before(async () => {
|
|
16
|
-
connection = new Sequelize('sqlite
|
|
18
|
+
connection = new Sequelize({ dialect: 'sqlite', storage: ':memory:', logging: false })
|
|
17
19
|
await connection.authenticate()
|
|
20
|
+
await runMigrations(connection)
|
|
18
21
|
formatter = new UrlFormatter('https://activitypubbot.example')
|
|
19
22
|
other = await as2.import({
|
|
20
23
|
id: 'https://social.example/user/test2',
|
|
21
24
|
type: 'Person'
|
|
22
25
|
})
|
|
26
|
+
unfollowed = await as2.import({
|
|
27
|
+
id: 'https://social.example/user/test3',
|
|
28
|
+
type: 'Person'
|
|
29
|
+
})
|
|
23
30
|
})
|
|
24
31
|
after(async () => {
|
|
25
32
|
await connection.close()
|
|
@@ -31,7 +38,6 @@ describe('ActorStorage', () => {
|
|
|
31
38
|
assert.ok(storage instanceof ActorStorage)
|
|
32
39
|
})
|
|
33
40
|
it('can initialize the storage', async () => {
|
|
34
|
-
await storage.initialize()
|
|
35
41
|
})
|
|
36
42
|
it('can get an actor', async () => {
|
|
37
43
|
const actor = await storage.getActor('test')
|
|
@@ -252,10 +258,26 @@ describe('ActorStorage', () => {
|
|
|
252
258
|
assert.strictEqual(actor.get('preferredUsername').first, 'test8')
|
|
253
259
|
assert.strictEqual(actor.name.get(), 'Test User')
|
|
254
260
|
assert.strictEqual(actor.summary.get(), 'A test user')
|
|
255
|
-
console.log(actor.type)
|
|
256
|
-
console.log(await actor.write())
|
|
257
261
|
assert.ok(Array.isArray(actor.type))
|
|
258
262
|
assert.ok(actor.type.includes(AS2_NS + 'Person'))
|
|
259
263
|
assert.ok(actor.type.includes(AS2_NS + 'Service'))
|
|
260
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
|
+
})
|
|
261
283
|
})
|
package/tests/authorizer.test.js
CHANGED
|
@@ -3,7 +3,6 @@ import { Authorizer } from '../lib/authorizer.js'
|
|
|
3
3
|
import { ActorStorage } from '../lib/actorstorage.js'
|
|
4
4
|
import { Sequelize } from 'sequelize'
|
|
5
5
|
import { UrlFormatter } from '../lib/urlformatter.js'
|
|
6
|
-
import { ObjectStorage } from '../lib/objectstorage.js'
|
|
7
6
|
import { KeyStorage } from '../lib/keystorage.js'
|
|
8
7
|
import { ActivityPubClient } from '../lib/activitypubclient.js'
|
|
9
8
|
import as2 from '../lib/activitystreams.js'
|
|
@@ -12,13 +11,13 @@ import { nanoid } from 'nanoid'
|
|
|
12
11
|
import { HTTPSignature } from '../lib/httpsignature.js'
|
|
13
12
|
import Logger from 'pino'
|
|
14
13
|
import { Digester } from '../lib/digester.js'
|
|
14
|
+
import { runMigrations } from '../lib/migrations/index.js'
|
|
15
15
|
|
|
16
16
|
describe('Authorizer', () => {
|
|
17
17
|
let authorizer = null
|
|
18
18
|
let actorStorage = null
|
|
19
19
|
let formatter = null
|
|
20
20
|
let connection = null
|
|
21
|
-
let objectStorage = null
|
|
22
21
|
let keyStorage = null
|
|
23
22
|
let client = null
|
|
24
23
|
|
|
@@ -39,14 +38,11 @@ describe('Authorizer', () => {
|
|
|
39
38
|
level: 'silent'
|
|
40
39
|
})
|
|
41
40
|
formatter = new UrlFormatter('https://activitypubbot.example')
|
|
42
|
-
connection = new Sequelize('sqlite
|
|
41
|
+
connection = new Sequelize({ dialect: 'sqlite', storage: ':memory:', logging: false })
|
|
43
42
|
await connection.authenticate()
|
|
43
|
+
await runMigrations(connection)
|
|
44
44
|
actorStorage = new ActorStorage(connection, formatter)
|
|
45
|
-
await actorStorage.initialize()
|
|
46
|
-
objectStorage = new ObjectStorage(connection)
|
|
47
|
-
await objectStorage.initialize()
|
|
48
45
|
keyStorage = new KeyStorage(connection, logger)
|
|
49
|
-
await keyStorage.initialize()
|
|
50
46
|
const signer = new HTTPSignature(logger)
|
|
51
47
|
const digester = new Digester(logger)
|
|
52
48
|
client = new ActivityPubClient(keyStorage, formatter, signer, digester, logger)
|
|
@@ -134,7 +130,6 @@ describe('Authorizer', () => {
|
|
|
134
130
|
actorStorage = null
|
|
135
131
|
connection = null
|
|
136
132
|
authorizer = null
|
|
137
|
-
objectStorage = null
|
|
138
133
|
})
|
|
139
134
|
|
|
140
135
|
it('should be a class', async () => {
|