@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.
- package/.github/dependabot.yml +11 -0
- package/.github/workflows/main-docker.yml +45 -0
- package/.github/workflows/tag-docker.yml +54 -0
- package/Dockerfile +23 -0
- package/LICENSE +661 -0
- package/README.md +82 -0
- package/bots/index.js +7 -0
- package/docs/activitypub.bot.drawio +110 -0
- package/index.js +23 -0
- package/lib/activitydistributor.js +263 -0
- package/lib/activityhandler.js +999 -0
- package/lib/activitypubclient.js +126 -0
- package/lib/activitystreams.js +41 -0
- package/lib/actorstorage.js +300 -0
- package/lib/app.js +173 -0
- package/lib/authorizer.js +133 -0
- package/lib/bot.js +44 -0
- package/lib/botcontext.js +520 -0
- package/lib/botdatastorage.js +87 -0
- package/lib/bots/donothing.js +11 -0
- package/lib/bots/ok.js +41 -0
- package/lib/digester.js +23 -0
- package/lib/httpsignature.js +195 -0
- package/lib/httpsignatureauthenticator.js +81 -0
- package/lib/keystorage.js +113 -0
- package/lib/microsyntax.js +140 -0
- package/lib/objectcache.js +48 -0
- package/lib/objectstorage.js +319 -0
- package/lib/remotekeystorage.js +116 -0
- package/lib/routes/collection.js +92 -0
- package/lib/routes/health.js +24 -0
- package/lib/routes/inbox.js +83 -0
- package/lib/routes/object.js +69 -0
- package/lib/routes/server.js +47 -0
- package/lib/routes/user.js +63 -0
- package/lib/routes/webfinger.js +36 -0
- package/lib/urlformatter.js +97 -0
- package/package.json +51 -0
- package/tests/activitydistributor.test.js +606 -0
- package/tests/activityhandler.test.js +2185 -0
- package/tests/activitypubclient.test.js +225 -0
- package/tests/actorstorage.test.js +261 -0
- package/tests/app.test.js +17 -0
- package/tests/authorizer.test.js +306 -0
- package/tests/bot.donothing.test.js +30 -0
- package/tests/bot.ok.test.js +101 -0
- package/tests/botcontext.test.js +674 -0
- package/tests/botdatastorage.test.js +87 -0
- package/tests/digester.test.js +56 -0
- package/tests/fixtures/bots.js +15 -0
- package/tests/httpsignature.test.js +200 -0
- package/tests/httpsignatureauthenticator.test.js +463 -0
- package/tests/keystorage.test.js +89 -0
- package/tests/microsyntax.test.js +122 -0
- package/tests/objectcache.test.js +133 -0
- package/tests/objectstorage.test.js +148 -0
- package/tests/remotekeystorage.test.js +76 -0
- package/tests/routes.actor.test.js +207 -0
- package/tests/routes.collection.test.js +434 -0
- package/tests/routes.health.test.js +41 -0
- package/tests/routes.inbox.test.js +135 -0
- package/tests/routes.object.test.js +519 -0
- package/tests/routes.server.test.js +69 -0
- package/tests/routes.webfinger.test.js +41 -0
- package/tests/urlformatter.test.js +164 -0
- package/tests/utils/digest.js +7 -0
- package/tests/utils/nock.js +276 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { describe, it } from 'node:test'
|
|
2
|
+
import { ObjectCache } from '../lib/objectcache.js'
|
|
3
|
+
import assert from 'node:assert/strict'
|
|
4
|
+
import as2 from '../lib/activitystreams.js'
|
|
5
|
+
|
|
6
|
+
describe('ObjectCache', async () => {
|
|
7
|
+
let cache = null
|
|
8
|
+
const longTTL = 3600 * 1000
|
|
9
|
+
const shortTTL = 300 * 1000
|
|
10
|
+
const maxItems = 1000
|
|
11
|
+
const makeObject = (num) =>
|
|
12
|
+
as2.import({
|
|
13
|
+
id: `https://example.com/${num}`,
|
|
14
|
+
name: `Object ${num}`,
|
|
15
|
+
type: 'Object',
|
|
16
|
+
attributedTo: `https://example.com/user${num}`,
|
|
17
|
+
to: 'https://www.w3.org/ns/activitystreams#Public'
|
|
18
|
+
})
|
|
19
|
+
const makeCollection = (num) =>
|
|
20
|
+
as2.import({
|
|
21
|
+
id: `https://example.com/collection${num}`,
|
|
22
|
+
type: 'Collection',
|
|
23
|
+
name: `Collection ${num}`,
|
|
24
|
+
totalItems: 1
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
const object1 = await makeObject(1)
|
|
28
|
+
const object2 = await makeObject(2)
|
|
29
|
+
const object3 = await makeObject(3)
|
|
30
|
+
const object4 = await makeObject(4)
|
|
31
|
+
const object5 = await makeObject(5)
|
|
32
|
+
const badid = 'https://example.com/badid'
|
|
33
|
+
const badcoll = 'https://example.com/badcoll'
|
|
34
|
+
const collection1 = await makeCollection(1)
|
|
35
|
+
const collection2 = await makeCollection(2)
|
|
36
|
+
const collection3 = await makeCollection(3)
|
|
37
|
+
|
|
38
|
+
it('should be a class', async () => {
|
|
39
|
+
assert.strictEqual(typeof ObjectCache, 'function')
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it('can be instantiated', async () => {
|
|
43
|
+
cache = new ObjectCache({ longTTL, shortTTL, maxItems })
|
|
44
|
+
assert.strictEqual(typeof cache, 'object')
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it('can be initialized', async () => {
|
|
48
|
+
try {
|
|
49
|
+
await cache.initialize()
|
|
50
|
+
assert.ok(true)
|
|
51
|
+
} catch (error) {
|
|
52
|
+
assert.fail(error)
|
|
53
|
+
}
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it('returns undefined if not found', async () => {
|
|
57
|
+
try {
|
|
58
|
+
const value = await cache.get(badid)
|
|
59
|
+
assert.strictEqual(value, undefined)
|
|
60
|
+
} catch (error) {
|
|
61
|
+
assert.fail(error)
|
|
62
|
+
}
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('can save', async () => {
|
|
66
|
+
try {
|
|
67
|
+
await cache.save(object1)
|
|
68
|
+
const dupe = await cache.get(object1.id)
|
|
69
|
+
assert.strictEqual(dupe.id, object1.id)
|
|
70
|
+
} catch (error) {
|
|
71
|
+
assert.fail(error)
|
|
72
|
+
}
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
it('can saveReceived', async () => {
|
|
76
|
+
try {
|
|
77
|
+
await cache.saveReceived(object2)
|
|
78
|
+
const dupe = await cache.get(object2.id)
|
|
79
|
+
assert.strictEqual(dupe.id, object2.id)
|
|
80
|
+
} catch (error) {
|
|
81
|
+
assert.fail(error)
|
|
82
|
+
}
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
it('can clear', async () => {
|
|
86
|
+
try {
|
|
87
|
+
await cache.save(object3)
|
|
88
|
+
await cache.clear(object3)
|
|
89
|
+
const dupe = await cache.get(object3.id)
|
|
90
|
+
assert.strictEqual(dupe, undefined)
|
|
91
|
+
} catch (error) {
|
|
92
|
+
assert.fail(error)
|
|
93
|
+
}
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
it('fails membership for unknown collection', async () => {
|
|
97
|
+
try {
|
|
98
|
+
const flag = await cache.isMember(collection3, badid)
|
|
99
|
+
assert.strictEqual(flag, undefined)
|
|
100
|
+
} catch (error) {
|
|
101
|
+
assert.fail(error)
|
|
102
|
+
}
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
it('fails membership for unknown object', async () => {
|
|
106
|
+
try {
|
|
107
|
+
const flag = await cache.isMember(badcoll, object4)
|
|
108
|
+
assert.strictEqual(flag, undefined)
|
|
109
|
+
} catch (error) {
|
|
110
|
+
assert.fail(error)
|
|
111
|
+
}
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
it('can saveMembership', async () => {
|
|
115
|
+
try {
|
|
116
|
+
await cache.saveMembership(collection1, object4)
|
|
117
|
+
const flag = await cache.isMember(collection1, object4)
|
|
118
|
+
assert.ok(flag)
|
|
119
|
+
} catch (error) {
|
|
120
|
+
assert.fail(error)
|
|
121
|
+
}
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
it('can saveMembershipReceived', async () => {
|
|
125
|
+
try {
|
|
126
|
+
await cache.saveMembershipReceived(collection2, object5)
|
|
127
|
+
const flag = await cache.isMember(collection2, object5)
|
|
128
|
+
assert.ok(flag)
|
|
129
|
+
} catch (error) {
|
|
130
|
+
assert.fail(error)
|
|
131
|
+
}
|
|
132
|
+
})
|
|
133
|
+
})
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { describe, it, before, after } from 'node:test'
|
|
2
|
+
import as2 from '../lib/activitystreams.js'
|
|
3
|
+
import assert from 'node:assert'
|
|
4
|
+
import { ObjectStorage, NoSuchObjectError } from '../lib/objectstorage.js'
|
|
5
|
+
import { Sequelize } from 'sequelize'
|
|
6
|
+
|
|
7
|
+
describe('ObjectStorage', async () => {
|
|
8
|
+
let doc = null
|
|
9
|
+
let doc2 = null
|
|
10
|
+
let doc3 = null
|
|
11
|
+
let connection = null
|
|
12
|
+
let storage = null
|
|
13
|
+
before(async () => {
|
|
14
|
+
doc = await as2.import({
|
|
15
|
+
'@context': 'https://www.w3.org/ns/activitystreams',
|
|
16
|
+
id: 'https://social.example/users/test/note/1',
|
|
17
|
+
type: 'Note',
|
|
18
|
+
name: 'test',
|
|
19
|
+
content: 'test'
|
|
20
|
+
})
|
|
21
|
+
doc2 = await as2.import({
|
|
22
|
+
'@context': 'https://www.w3.org/ns/activitystreams',
|
|
23
|
+
id: 'https://social.example/users/test/note/2',
|
|
24
|
+
type: 'Note',
|
|
25
|
+
name: 'test',
|
|
26
|
+
content: 'test',
|
|
27
|
+
inReplyTo: doc.id
|
|
28
|
+
})
|
|
29
|
+
doc3 = await as2.import({
|
|
30
|
+
'@context': 'https://www.w3.org/ns/activitystreams',
|
|
31
|
+
id: 'https://social.example/users/test/note/3',
|
|
32
|
+
type: 'Note',
|
|
33
|
+
name: 'test',
|
|
34
|
+
content: 'test'
|
|
35
|
+
})
|
|
36
|
+
connection = new Sequelize('sqlite::memory:', { logging: false })
|
|
37
|
+
await connection.authenticate()
|
|
38
|
+
})
|
|
39
|
+
after(async () => {
|
|
40
|
+
await connection.close()
|
|
41
|
+
})
|
|
42
|
+
it('can initialize', async () => {
|
|
43
|
+
storage = new ObjectStorage(connection)
|
|
44
|
+
await storage.initialize()
|
|
45
|
+
})
|
|
46
|
+
it('can create a new object', async () => {
|
|
47
|
+
await storage.create(doc)
|
|
48
|
+
})
|
|
49
|
+
it('can read a created object', async () => {
|
|
50
|
+
await storage.read(doc.id)
|
|
51
|
+
})
|
|
52
|
+
it('can update a created object', async () => {
|
|
53
|
+
const doc2 = await as2.import({
|
|
54
|
+
'@context': 'https://www.w3.org/ns/activitystreams',
|
|
55
|
+
id: 'https://social.example/users/test/note/1',
|
|
56
|
+
type: 'Note',
|
|
57
|
+
name: 'test2',
|
|
58
|
+
content: 'test2'
|
|
59
|
+
})
|
|
60
|
+
await storage.update(doc2)
|
|
61
|
+
const read = await storage.read(doc2.id)
|
|
62
|
+
assert.equal(read.name.get('en'), 'test2')
|
|
63
|
+
})
|
|
64
|
+
it('can delete a created object', async () => {
|
|
65
|
+
await storage.delete(doc)
|
|
66
|
+
try {
|
|
67
|
+
await storage.read(doc.id)
|
|
68
|
+
assert.fail('should not be able to read deleted object')
|
|
69
|
+
} catch (err) {
|
|
70
|
+
assert.ok(err instanceof NoSuchObjectError)
|
|
71
|
+
}
|
|
72
|
+
})
|
|
73
|
+
it('can get a collection', async () => {
|
|
74
|
+
const collection = await storage.getCollection(doc.id, 'replies')
|
|
75
|
+
assert.equal(typeof (collection), 'object')
|
|
76
|
+
assert.equal(typeof (collection.id), 'string')
|
|
77
|
+
assert.equal(collection.id, `${doc.id}/replies`)
|
|
78
|
+
assert.equal(collection.type, 'https://www.w3.org/ns/activitystreams#OrderedCollection')
|
|
79
|
+
assert.equal(collection.totalItems, 0)
|
|
80
|
+
assert.equal(collection.first.id, `${doc.id}/replies/1`)
|
|
81
|
+
assert.equal(collection.last.id, `${doc.id}/replies/1`)
|
|
82
|
+
})
|
|
83
|
+
it('can get a collection page', async () => {
|
|
84
|
+
const page = await storage.getCollectionPage(doc.id, 'replies', 1)
|
|
85
|
+
assert.equal(typeof page, 'object')
|
|
86
|
+
assert.equal(page.id, `${doc.id}/replies/1`)
|
|
87
|
+
assert.equal(page.type, 'https://www.w3.org/ns/activitystreams#OrderedCollectionPage')
|
|
88
|
+
assert.equal(page.partOf.id, `${doc.id}/replies`)
|
|
89
|
+
assert.ok(!page.next)
|
|
90
|
+
assert.ok(!page.prev)
|
|
91
|
+
assert.ok(!page.items)
|
|
92
|
+
})
|
|
93
|
+
it('can add to a collection', async () => {
|
|
94
|
+
await storage.addToCollection(doc.id, 'replies', doc2)
|
|
95
|
+
const page = await storage.getCollectionPage(doc.id, 'replies', 1)
|
|
96
|
+
assert.ok(Array.from(page.items).find(item => item.id === doc2.id))
|
|
97
|
+
})
|
|
98
|
+
it('can check collection membership', async () => {
|
|
99
|
+
assert.strictEqual(true, await storage.isInCollection(doc.id, 'replies', doc2))
|
|
100
|
+
assert.strictEqual(false, await storage.isInCollection(doc.id, 'replies', doc3))
|
|
101
|
+
})
|
|
102
|
+
it('can remove from a collection', async () => {
|
|
103
|
+
await storage.removeFromCollection(doc.id, 'replies', doc2)
|
|
104
|
+
const page = await storage.getCollectionPage(doc.id, 'replies', 1)
|
|
105
|
+
assert.ok(!page.items)
|
|
106
|
+
})
|
|
107
|
+
it('can add many items to a collection', async () => {
|
|
108
|
+
for (let i = 3; i < 103; i++) {
|
|
109
|
+
const reply = await as2.import({
|
|
110
|
+
'@context': 'https://www.w3.org/ns/activitystreams',
|
|
111
|
+
id: `https://social.example/users/test/note/${i}`,
|
|
112
|
+
type: 'Note',
|
|
113
|
+
name: 'test',
|
|
114
|
+
content: 'test',
|
|
115
|
+
inReplyTo: doc.id
|
|
116
|
+
})
|
|
117
|
+
await storage.addToCollection(doc.id, 'replies', reply)
|
|
118
|
+
}
|
|
119
|
+
const collection = await storage.getCollection(doc.id, 'replies')
|
|
120
|
+
assert.equal(collection.totalItems, 100)
|
|
121
|
+
assert.equal(collection.first.id, `${doc.id}/replies/5`)
|
|
122
|
+
assert.equal(collection.last.id, `${doc.id}/replies/1`)
|
|
123
|
+
const page = await storage.getCollectionPage(doc.id, 'replies', 3)
|
|
124
|
+
assert.ok(page.next)
|
|
125
|
+
// assert.ok(page.prev)
|
|
126
|
+
assert.ok(page.items)
|
|
127
|
+
const items = Array.from(page.items)
|
|
128
|
+
assert.equal(items.length, 20)
|
|
129
|
+
for (let i = 0; i < items.length; i++) {
|
|
130
|
+
assert.ok(items[i])
|
|
131
|
+
for (let j = i + 1; j < items.length; j++) {
|
|
132
|
+
assert.ok(items[j])
|
|
133
|
+
assert.ok(
|
|
134
|
+
items[i].id > items[j].id,
|
|
135
|
+
`item ${i} (${items[i].id}) <= item ${j} (${items[j].id})`
|
|
136
|
+
)
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
})
|
|
140
|
+
it('can iterate over a collection', async () => {
|
|
141
|
+
const seen = new Set()
|
|
142
|
+
for await (const item of storage.items(doc.id, 'replies')) {
|
|
143
|
+
assert.ok(!(item.id in seen))
|
|
144
|
+
seen.add(item.id)
|
|
145
|
+
}
|
|
146
|
+
assert.strictEqual(seen.size, 100)
|
|
147
|
+
})
|
|
148
|
+
})
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { describe, before, after, it } from 'node:test'
|
|
2
|
+
import { RemoteKeyStorage } from '../lib/remotekeystorage.js'
|
|
3
|
+
import assert from 'node:assert'
|
|
4
|
+
import { Sequelize } from 'sequelize'
|
|
5
|
+
import { KeyStorage } from '../lib/keystorage.js'
|
|
6
|
+
import { UrlFormatter } from '../lib/urlformatter.js'
|
|
7
|
+
import { ActivityPubClient } from '../lib/activitypubclient.js'
|
|
8
|
+
import { nockSetup, nockFormat, getPublicKey, nockKeyRotate } from './utils/nock.js'
|
|
9
|
+
import { HTTPSignature } from '../lib/httpsignature.js'
|
|
10
|
+
import Logger from 'pino'
|
|
11
|
+
import { Digester } from '../lib/digester.js'
|
|
12
|
+
|
|
13
|
+
describe('RemoteKeyStorage', async () => {
|
|
14
|
+
const origin = 'https://activitypubbot.example'
|
|
15
|
+
let connection = null
|
|
16
|
+
let remoteKeyStorage = null
|
|
17
|
+
let client = null
|
|
18
|
+
let logger = null
|
|
19
|
+
before(async () => {
|
|
20
|
+
logger = Logger({
|
|
21
|
+
level: 'silent'
|
|
22
|
+
})
|
|
23
|
+
connection = new Sequelize('sqlite::memory:', { logging: false })
|
|
24
|
+
await connection.authenticate()
|
|
25
|
+
const keyStorage = new KeyStorage(connection, logger)
|
|
26
|
+
await keyStorage.initialize()
|
|
27
|
+
const formatter = new UrlFormatter(origin)
|
|
28
|
+
const digester = new Digester(logger)
|
|
29
|
+
const signer = new HTTPSignature(logger)
|
|
30
|
+
client = new ActivityPubClient(keyStorage, formatter, signer, digester, logger)
|
|
31
|
+
nockSetup('social.example')
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
after(async () => {
|
|
35
|
+
await connection.close()
|
|
36
|
+
logger = null
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('can initialize', async () => {
|
|
40
|
+
remoteKeyStorage = new RemoteKeyStorage(client, connection, logger)
|
|
41
|
+
assert.ok(remoteKeyStorage)
|
|
42
|
+
await remoteKeyStorage.initialize()
|
|
43
|
+
assert.ok(true)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('can get a remote public key', async () => {
|
|
47
|
+
const username = 'test'
|
|
48
|
+
const domain = 'social.example'
|
|
49
|
+
const id = nockFormat({ username, key: true, domain })
|
|
50
|
+
const publicKey = await getPublicKey(username, domain)
|
|
51
|
+
const remote = await remoteKeyStorage.getPublicKey(id)
|
|
52
|
+
assert.equal(remote.publicKeyPem, publicKey)
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
it('can get the same remote public key twice', async () => {
|
|
56
|
+
const username = 'test'
|
|
57
|
+
const domain = 'social.example'
|
|
58
|
+
const id = nockFormat({ username, key: true, domain })
|
|
59
|
+
const publicKey = await getPublicKey(username, domain)
|
|
60
|
+
const remote = await remoteKeyStorage.getPublicKey(id)
|
|
61
|
+
assert.equal(remote.publicKeyPem, publicKey)
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it('can get the right public key after key rotation', async () => {
|
|
65
|
+
const username = 'test'
|
|
66
|
+
const domain = 'social.example'
|
|
67
|
+
const id = nockFormat({ username, key: true, domain })
|
|
68
|
+
const publicKey = await getPublicKey(username, domain)
|
|
69
|
+
const remote = await remoteKeyStorage.getPublicKey(id)
|
|
70
|
+
assert.equal(remote.publicKeyPem, publicKey)
|
|
71
|
+
await nockKeyRotate(username)
|
|
72
|
+
const publicKey2 = await getPublicKey(username, domain)
|
|
73
|
+
const remote2 = await remoteKeyStorage.getPublicKey(id, false)
|
|
74
|
+
assert.equal(remote2.publicKeyPem, publicKey2)
|
|
75
|
+
})
|
|
76
|
+
})
|
|
@@ -0,0 +1,207 @@
|
|
|
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('actor routes', async () => {
|
|
8
|
+
const databaseUrl = 'sqlite::memory:'
|
|
9
|
+
const origin = 'https://activitypubbot.test'
|
|
10
|
+
const app = await makeApp(databaseUrl, origin, bots, 'silent')
|
|
11
|
+
|
|
12
|
+
describe('GET /user/{botid}', async () => {
|
|
13
|
+
let response = null
|
|
14
|
+
it('should work without an error', async () => {
|
|
15
|
+
response = await request(app).get('/user/ok')
|
|
16
|
+
})
|
|
17
|
+
it('should return 200 OK', async () => {
|
|
18
|
+
assert.strictEqual(response.status, 200)
|
|
19
|
+
})
|
|
20
|
+
it('should return AS2', async () => {
|
|
21
|
+
assert.strictEqual(response.type, 'application/activity+json')
|
|
22
|
+
})
|
|
23
|
+
it('should return an object', async () => {
|
|
24
|
+
assert.strictEqual(typeof response.body, 'object')
|
|
25
|
+
})
|
|
26
|
+
it('should return an object with an id', async () => {
|
|
27
|
+
assert.strictEqual(typeof response.body.id, 'string')
|
|
28
|
+
})
|
|
29
|
+
it('should return an object with an id matching the request', async () => {
|
|
30
|
+
assert.strictEqual(response.body.id, origin + '/user/ok')
|
|
31
|
+
})
|
|
32
|
+
it('should return an object with a type', async () => {
|
|
33
|
+
assert.strictEqual(typeof response.body.type, 'string')
|
|
34
|
+
})
|
|
35
|
+
it('should return an object with a type matching the request', async () => {
|
|
36
|
+
assert.strictEqual(response.body.type, 'Service')
|
|
37
|
+
})
|
|
38
|
+
it('should return an object with a preferredUsername', async () => {
|
|
39
|
+
assert.strictEqual(typeof response.body.preferredUsername, 'string')
|
|
40
|
+
})
|
|
41
|
+
it('should return an object with a preferredUsername matching the request', async () => {
|
|
42
|
+
assert.strictEqual(response.body.preferredUsername, 'ok')
|
|
43
|
+
})
|
|
44
|
+
it('should return an object with an inbox', async () => {
|
|
45
|
+
assert.strictEqual(typeof response.body.inbox, 'string')
|
|
46
|
+
})
|
|
47
|
+
it('should return an object with an outbox', async () => {
|
|
48
|
+
assert.strictEqual(typeof response.body.outbox, 'string')
|
|
49
|
+
})
|
|
50
|
+
it('should return an object with a followers', async () => {
|
|
51
|
+
assert.strictEqual(typeof response.body.followers, 'string')
|
|
52
|
+
})
|
|
53
|
+
it('should return an object with a following', async () => {
|
|
54
|
+
assert.strictEqual(typeof response.body.following, 'string')
|
|
55
|
+
})
|
|
56
|
+
it('should return an object with a liked', async () => {
|
|
57
|
+
assert.strictEqual(typeof response.body.liked, 'string')
|
|
58
|
+
})
|
|
59
|
+
it('should return an object with a to', async () => {
|
|
60
|
+
assert.strictEqual(typeof response.body.to, 'string')
|
|
61
|
+
})
|
|
62
|
+
it('should return an object with a to matching the request', async () => {
|
|
63
|
+
assert.strictEqual(response.body.to, 'as:Public')
|
|
64
|
+
})
|
|
65
|
+
it('should return an object with a summary', async () => {
|
|
66
|
+
assert.strictEqual(typeof response.body.summary, 'string')
|
|
67
|
+
})
|
|
68
|
+
it('should return an object with a summary matching the request', async () => {
|
|
69
|
+
assert.strictEqual(response.body.summary, 'A bot that says "OK" when mentioned.')
|
|
70
|
+
})
|
|
71
|
+
it('should return an object with a name', async () => {
|
|
72
|
+
assert.strictEqual(typeof response.body.name, 'string')
|
|
73
|
+
})
|
|
74
|
+
it('should return an object with a name matching the request', async () => {
|
|
75
|
+
assert.strictEqual(response.body.name, 'OK Bot')
|
|
76
|
+
})
|
|
77
|
+
it('should return an object with a publicKey', async () => {
|
|
78
|
+
assert.strictEqual(typeof response.body.publicKey, 'object')
|
|
79
|
+
assert.ok(response.body.publicKey)
|
|
80
|
+
})
|
|
81
|
+
it('should return an object with a publicKey matching the request', async () => {
|
|
82
|
+
assert.strictEqual(response.body.publicKey.id, origin + '/user/ok/publickey')
|
|
83
|
+
})
|
|
84
|
+
it('should return an object with a publicKey with an owner matching the request', async () => {
|
|
85
|
+
assert.strictEqual(response.body.publicKey.owner, origin + '/user/ok')
|
|
86
|
+
})
|
|
87
|
+
it('should return an object with a publicKey with a type', async () => {
|
|
88
|
+
assert.strictEqual(response.body.publicKey.type, 'CryptographicKey')
|
|
89
|
+
})
|
|
90
|
+
it('should return an object with a publicKey with a to', async () => {
|
|
91
|
+
assert.strictEqual(response.body.publicKey.to, 'as:Public')
|
|
92
|
+
})
|
|
93
|
+
it('should return an object with a publicKey with a publicKeyPem', async () => {
|
|
94
|
+
assert.strictEqual(typeof response.body.publicKey.publicKeyPem, 'string')
|
|
95
|
+
})
|
|
96
|
+
it('publicKeyPem should be an RSA PKCS-8 key', async () => {
|
|
97
|
+
assert.match(response.body.publicKey.publicKeyPem, /^-----BEGIN PUBLIC KEY-----\n/)
|
|
98
|
+
assert.match(response.body.publicKey.publicKeyPem, /\n-----END PUBLIC KEY-----\n$/)
|
|
99
|
+
})
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
describe('GET non-existent user', async () => {
|
|
103
|
+
let response = null
|
|
104
|
+
it('should work without an error', async () => {
|
|
105
|
+
response = await request(app).get('/user/dne')
|
|
106
|
+
})
|
|
107
|
+
it('should return 404 Not Found', async () => {
|
|
108
|
+
assert.strictEqual(response.status, 404)
|
|
109
|
+
})
|
|
110
|
+
it('should return Problem Details JSON', async () => {
|
|
111
|
+
assert.strictEqual(response.type, 'application/problem+json')
|
|
112
|
+
})
|
|
113
|
+
it('should return an object', async () => {
|
|
114
|
+
assert.strictEqual(typeof response.body, 'object')
|
|
115
|
+
})
|
|
116
|
+
it('should return an object with a type', async () => {
|
|
117
|
+
assert.strictEqual(typeof response.body.type, 'string')
|
|
118
|
+
})
|
|
119
|
+
it('should return an object with an type matching the request', async () => {
|
|
120
|
+
assert.strictEqual(response.body.type, 'about:blank')
|
|
121
|
+
})
|
|
122
|
+
it('should return an object with a title', async () => {
|
|
123
|
+
assert.strictEqual(typeof response.body.title, 'string')
|
|
124
|
+
})
|
|
125
|
+
it('should return an object with a title matching the request', async () => {
|
|
126
|
+
assert.strictEqual(response.body.title, 'Not Found')
|
|
127
|
+
})
|
|
128
|
+
it('should return an object with a status', async () => {
|
|
129
|
+
assert.strictEqual(typeof response.body.status, 'number')
|
|
130
|
+
})
|
|
131
|
+
it('should return an object with a status matching the request', async () => {
|
|
132
|
+
assert.strictEqual(response.body.status, 404)
|
|
133
|
+
})
|
|
134
|
+
it('should return an object with a detail', async () => {
|
|
135
|
+
assert.strictEqual(typeof response.body.detail, 'string')
|
|
136
|
+
})
|
|
137
|
+
it('should return an object with a detail matching the request', async () => {
|
|
138
|
+
assert.strictEqual(response.body.detail, 'User dne not found')
|
|
139
|
+
})
|
|
140
|
+
})
|
|
141
|
+
describe('GET /user/{dne}/publickey', async () => {
|
|
142
|
+
let response = null
|
|
143
|
+
it('should work without an error', async () => {
|
|
144
|
+
response = await request(app).get('/user/dne')
|
|
145
|
+
})
|
|
146
|
+
it('should return 404 Not Found', async () => {
|
|
147
|
+
assert.strictEqual(response.status, 404)
|
|
148
|
+
})
|
|
149
|
+
it('should return Problem Details JSON', async () => {
|
|
150
|
+
assert.strictEqual(response.type, 'application/problem+json')
|
|
151
|
+
})
|
|
152
|
+
it('should return the right object', async () => {
|
|
153
|
+
assert.strictEqual(typeof response.body, 'object')
|
|
154
|
+
assert.strictEqual(response.body.type, 'about:blank')
|
|
155
|
+
assert.strictEqual(response.body.title, 'Not Found')
|
|
156
|
+
assert.strictEqual(response.body.status, 404)
|
|
157
|
+
assert.strictEqual(response.body.detail, 'User dne not found')
|
|
158
|
+
})
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
describe('GET /user/{botid}/publickey', async () => {
|
|
162
|
+
let response = null
|
|
163
|
+
it('should work without an error', async () => {
|
|
164
|
+
response = await request(app).get('/user/ok/publickey')
|
|
165
|
+
})
|
|
166
|
+
it('should return 200 OK', async () => {
|
|
167
|
+
assert.strictEqual(response.status, 200)
|
|
168
|
+
})
|
|
169
|
+
it('should return AS2', async () => {
|
|
170
|
+
assert.strictEqual(response.type, 'application/activity+json')
|
|
171
|
+
})
|
|
172
|
+
it('should return an object', async () => {
|
|
173
|
+
assert.strictEqual(typeof response.body, 'object')
|
|
174
|
+
})
|
|
175
|
+
it('should return an object with an id', async () => {
|
|
176
|
+
assert.strictEqual(typeof response.body.id, 'string')
|
|
177
|
+
})
|
|
178
|
+
it('should return an object with the requested public key id', async () => {
|
|
179
|
+
assert.strictEqual(response.body.id, origin + '/user/ok/publickey')
|
|
180
|
+
})
|
|
181
|
+
it('should return an object with an owner', async () => {
|
|
182
|
+
assert.strictEqual(typeof response.body.owner, 'string')
|
|
183
|
+
})
|
|
184
|
+
it('should return an object with the bot as owner', async () => {
|
|
185
|
+
assert.strictEqual(response.body.owner, origin + '/user/ok')
|
|
186
|
+
})
|
|
187
|
+
it('should return an object with a publicKeyPem', async () => {
|
|
188
|
+
assert.strictEqual(typeof response.body.publicKeyPem, 'string')
|
|
189
|
+
})
|
|
190
|
+
it('publicKeyPem should be an RSA PKCS-8 key', async () => {
|
|
191
|
+
assert.match(response.body.publicKeyPem, /^-----BEGIN PUBLIC KEY-----\n/)
|
|
192
|
+
assert.match(response.body.publicKeyPem, /\n-----END PUBLIC KEY-----\n$/)
|
|
193
|
+
})
|
|
194
|
+
it('should return an object with a type', async () => {
|
|
195
|
+
assert.strictEqual(typeof response.body.type, 'string')
|
|
196
|
+
})
|
|
197
|
+
it('should return an object with a type matching the request', async () => {
|
|
198
|
+
assert.strictEqual(response.body.type, 'CryptographicKey')
|
|
199
|
+
})
|
|
200
|
+
it('should return an object with a to', async () => {
|
|
201
|
+
assert.strictEqual(typeof response.body.to, 'string')
|
|
202
|
+
})
|
|
203
|
+
it('should return an object with a to matching the request', async () => {
|
|
204
|
+
assert.strictEqual(response.body.to, 'as:Public')
|
|
205
|
+
})
|
|
206
|
+
})
|
|
207
|
+
})
|