microfed 0.0.12 → 0.0.14
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/README.md +194 -65
- package/example/server.js +252 -0
- package/index.js +36 -0
- package/package.json +22 -4
- package/src/auth.js +152 -0
- package/src/inbox.js +188 -0
- package/src/outbox.js +268 -0
- package/src/profile.js +114 -0
- package/src/webfinger.js +182 -0
- package/test/auth.test.js +218 -0
- package/test/inbox.test.js +135 -0
- package/test/live.test.js +109 -0
- package/test/mastodon.test.js +243 -0
- package/test/outbox.test.js +195 -0
- package/test/profile.test.js +213 -0
- package/test/webfinger.test.js +139 -0
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Microfed Profile Module Tests
|
|
3
|
+
* Run: node --test test/profile.test.js
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { test, describe } from 'node:test'
|
|
7
|
+
import assert from 'node:assert'
|
|
8
|
+
import { createActor, createMinimalActor, parseActor, getActorId, buildActorUrl } from '../src/profile.js'
|
|
9
|
+
|
|
10
|
+
describe('createActor', () => {
|
|
11
|
+
test('creates actor with required fields', () => {
|
|
12
|
+
const actor = createActor({
|
|
13
|
+
id: 'https://example.com/users/alice',
|
|
14
|
+
username: 'alice'
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
assert.strictEqual(actor.id, 'https://example.com/users/alice')
|
|
18
|
+
assert.strictEqual(actor.type, 'Person')
|
|
19
|
+
assert.strictEqual(actor.preferredUsername, 'alice')
|
|
20
|
+
assert.strictEqual(actor.inbox, 'https://example.com/users/alice/inbox')
|
|
21
|
+
assert.strictEqual(actor.outbox, 'https://example.com/users/alice/outbox')
|
|
22
|
+
assert.strictEqual(actor.followers, 'https://example.com/users/alice/followers')
|
|
23
|
+
assert.strictEqual(actor.following, 'https://example.com/users/alice/following')
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
test('includes @context with ActivityStreams and security', () => {
|
|
27
|
+
const actor = createActor({
|
|
28
|
+
id: 'https://example.com/users/alice',
|
|
29
|
+
username: 'alice'
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
assert.ok(Array.isArray(actor['@context']))
|
|
33
|
+
assert.ok(actor['@context'].includes('https://www.w3.org/ns/activitystreams'))
|
|
34
|
+
assert.ok(actor['@context'].includes('https://w3id.org/security/v1'))
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
test('supports different actor types', () => {
|
|
38
|
+
const service = createActor({
|
|
39
|
+
id: 'https://example.com/bot',
|
|
40
|
+
username: 'bot',
|
|
41
|
+
type: 'Service'
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
assert.strictEqual(service.type, 'Service')
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
test('includes optional fields when provided', () => {
|
|
48
|
+
const actor = createActor({
|
|
49
|
+
id: 'https://example.com/users/alice',
|
|
50
|
+
username: 'alice',
|
|
51
|
+
name: 'Alice',
|
|
52
|
+
summary: '<p>Hello!</p>',
|
|
53
|
+
url: 'https://example.com/@alice'
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
assert.strictEqual(actor.name, 'Alice')
|
|
57
|
+
assert.strictEqual(actor.summary, '<p>Hello!</p>')
|
|
58
|
+
assert.strictEqual(actor.url, 'https://example.com/@alice')
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
test('formats icon as Image object', () => {
|
|
62
|
+
const actor = createActor({
|
|
63
|
+
id: 'https://example.com/users/alice',
|
|
64
|
+
username: 'alice',
|
|
65
|
+
icon: 'https://example.com/avatar.png'
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
assert.deepStrictEqual(actor.icon, {
|
|
69
|
+
type: 'Image',
|
|
70
|
+
url: 'https://example.com/avatar.png'
|
|
71
|
+
})
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
test('includes publicKey when provided', () => {
|
|
75
|
+
const publicKey = '-----BEGIN PUBLIC KEY-----\nMIIB...\n-----END PUBLIC KEY-----'
|
|
76
|
+
const actor = createActor({
|
|
77
|
+
id: 'https://example.com/users/alice',
|
|
78
|
+
username: 'alice',
|
|
79
|
+
publicKey
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
assert.strictEqual(actor.publicKey.id, 'https://example.com/users/alice#main-key')
|
|
83
|
+
assert.strictEqual(actor.publicKey.owner, 'https://example.com/users/alice')
|
|
84
|
+
assert.strictEqual(actor.publicKey.publicKeyPem, publicKey)
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
test('includes sharedInbox endpoint when provided', () => {
|
|
88
|
+
const actor = createActor({
|
|
89
|
+
id: 'https://example.com/users/alice',
|
|
90
|
+
username: 'alice',
|
|
91
|
+
sharedInbox: 'https://example.com/inbox'
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
assert.deepStrictEqual(actor.endpoints, {
|
|
95
|
+
sharedInbox: 'https://example.com/inbox'
|
|
96
|
+
})
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
test('throws when id is missing', () => {
|
|
100
|
+
assert.throws(() => {
|
|
101
|
+
createActor({ username: 'alice' })
|
|
102
|
+
}, /id is required/)
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
test('throws when username is missing', () => {
|
|
106
|
+
assert.throws(() => {
|
|
107
|
+
createActor({ id: 'https://example.com/users/alice' })
|
|
108
|
+
}, /username is required/)
|
|
109
|
+
})
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
describe('createMinimalActor', () => {
|
|
113
|
+
test('creates actor with just id and username', () => {
|
|
114
|
+
const actor = createMinimalActor(
|
|
115
|
+
'https://example.com/users/bob',
|
|
116
|
+
'bob'
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
assert.strictEqual(actor.id, 'https://example.com/users/bob')
|
|
120
|
+
assert.strictEqual(actor.preferredUsername, 'bob')
|
|
121
|
+
assert.strictEqual(actor.type, 'Person')
|
|
122
|
+
})
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
describe('parseActor', () => {
|
|
126
|
+
test('parses actor object', () => {
|
|
127
|
+
const input = {
|
|
128
|
+
id: 'https://example.com/users/alice',
|
|
129
|
+
inbox: 'https://example.com/users/alice/inbox'
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const actor = parseActor(input)
|
|
133
|
+
assert.strictEqual(actor.id, input.id)
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
test('parses JSON string', () => {
|
|
137
|
+
const json = '{"id":"https://example.com/users/alice","inbox":"https://example.com/users/alice/inbox"}'
|
|
138
|
+
|
|
139
|
+
const actor = parseActor(json)
|
|
140
|
+
assert.strictEqual(actor.id, 'https://example.com/users/alice')
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
test('throws when id is missing', () => {
|
|
144
|
+
assert.throws(() => {
|
|
145
|
+
parseActor({ inbox: 'https://example.com/inbox' })
|
|
146
|
+
}, /missing id/)
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
test('throws when inbox is missing', () => {
|
|
150
|
+
assert.throws(() => {
|
|
151
|
+
parseActor({ id: 'https://example.com/users/alice' })
|
|
152
|
+
}, /missing inbox/)
|
|
153
|
+
})
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
describe('getActorId', () => {
|
|
157
|
+
test('returns string as-is', () => {
|
|
158
|
+
assert.strictEqual(
|
|
159
|
+
getActorId('https://example.com/users/alice'),
|
|
160
|
+
'https://example.com/users/alice'
|
|
161
|
+
)
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
test('extracts id from object', () => {
|
|
165
|
+
assert.strictEqual(
|
|
166
|
+
getActorId({ id: 'https://example.com/users/alice' }),
|
|
167
|
+
'https://example.com/users/alice'
|
|
168
|
+
)
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
test('extracts actor from activity', () => {
|
|
172
|
+
assert.strictEqual(
|
|
173
|
+
getActorId({ actor: 'https://example.com/users/alice' }),
|
|
174
|
+
'https://example.com/users/alice'
|
|
175
|
+
)
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
test('handles nested actor object', () => {
|
|
179
|
+
assert.strictEqual(
|
|
180
|
+
getActorId({ actor: { id: 'https://example.com/users/alice' } }),
|
|
181
|
+
'https://example.com/users/alice'
|
|
182
|
+
)
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
test('throws when cannot extract id', () => {
|
|
186
|
+
assert.throws(() => {
|
|
187
|
+
getActorId({})
|
|
188
|
+
}, /Cannot extract actor ID/)
|
|
189
|
+
})
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
describe('buildActorUrl', () => {
|
|
193
|
+
test('builds URL with default path', () => {
|
|
194
|
+
assert.strictEqual(
|
|
195
|
+
buildActorUrl('example.com', 'alice'),
|
|
196
|
+
'https://example.com/users/alice'
|
|
197
|
+
)
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
test('builds URL with custom path', () => {
|
|
201
|
+
assert.strictEqual(
|
|
202
|
+
buildActorUrl('example.com', 'alice', '/@{username}'),
|
|
203
|
+
'https://example.com/@alice'
|
|
204
|
+
)
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
test('handles subdomain', () => {
|
|
208
|
+
assert.strictEqual(
|
|
209
|
+
buildActorUrl('social.example.org', 'bob'),
|
|
210
|
+
'https://social.example.org/users/bob'
|
|
211
|
+
)
|
|
212
|
+
})
|
|
213
|
+
})
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Microfed WebFinger Module Tests
|
|
3
|
+
* Run: node --test test/webfinger.test.js
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { test, describe } from 'node:test'
|
|
7
|
+
import assert from 'node:assert'
|
|
8
|
+
import { createResponse, parseResource, getActorUrl } from '../src/webfinger.js'
|
|
9
|
+
|
|
10
|
+
describe('createResponse', () => {
|
|
11
|
+
test('creates valid JRD response', () => {
|
|
12
|
+
const response = createResponse(
|
|
13
|
+
'alice@example.com',
|
|
14
|
+
'https://example.com/users/alice'
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
assert.strictEqual(response.subject, 'acct:alice@example.com')
|
|
18
|
+
assert.ok(Array.isArray(response.links))
|
|
19
|
+
assert.strictEqual(response.links.length, 1)
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
test('includes self link with ActivityPub type', () => {
|
|
23
|
+
const response = createResponse(
|
|
24
|
+
'alice@example.com',
|
|
25
|
+
'https://example.com/users/alice'
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
const selfLink = response.links.find(l => l.rel === 'self')
|
|
29
|
+
assert.ok(selfLink)
|
|
30
|
+
assert.strictEqual(selfLink.type, 'application/activity+json')
|
|
31
|
+
assert.strictEqual(selfLink.href, 'https://example.com/users/alice')
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
test('includes profile page link when provided', () => {
|
|
35
|
+
const response = createResponse(
|
|
36
|
+
'alice@example.com',
|
|
37
|
+
'https://example.com/users/alice',
|
|
38
|
+
{ profileUrl: 'https://example.com/@alice' }
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
const profileLink = response.links.find(l => l.rel === 'http://webfinger.net/rel/profile-page')
|
|
42
|
+
assert.ok(profileLink)
|
|
43
|
+
assert.strictEqual(profileLink.type, 'text/html')
|
|
44
|
+
assert.strictEqual(profileLink.href, 'https://example.com/@alice')
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
test('includes aliases when provided', () => {
|
|
48
|
+
const response = createResponse(
|
|
49
|
+
'alice@example.com',
|
|
50
|
+
'https://example.com/users/alice',
|
|
51
|
+
{ aliases: ['https://example.com/@alice'] }
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
assert.deepStrictEqual(response.aliases, ['https://example.com/@alice'])
|
|
55
|
+
})
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
describe('parseResource', () => {
|
|
59
|
+
test('parses acct: URI', () => {
|
|
60
|
+
const result = parseResource('acct:alice@example.com')
|
|
61
|
+
|
|
62
|
+
assert.deepStrictEqual(result, {
|
|
63
|
+
username: 'alice',
|
|
64
|
+
domain: 'example.com'
|
|
65
|
+
})
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
test('parses acct: URI with subdomain', () => {
|
|
69
|
+
const result = parseResource('acct:bob@social.example.org')
|
|
70
|
+
|
|
71
|
+
assert.deepStrictEqual(result, {
|
|
72
|
+
username: 'bob',
|
|
73
|
+
domain: 'social.example.org'
|
|
74
|
+
})
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
test('parses https URL with /users/ path', () => {
|
|
78
|
+
const result = parseResource('https://example.com/users/alice')
|
|
79
|
+
|
|
80
|
+
assert.deepStrictEqual(result, {
|
|
81
|
+
username: 'alice',
|
|
82
|
+
domain: 'example.com'
|
|
83
|
+
})
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
test('returns null for invalid input', () => {
|
|
87
|
+
assert.strictEqual(parseResource(null), null)
|
|
88
|
+
assert.strictEqual(parseResource(''), null)
|
|
89
|
+
assert.strictEqual(parseResource('invalid'), null)
|
|
90
|
+
assert.strictEqual(parseResource('acct:noatsign'), null)
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
test('returns null for URL without /users/ path', () => {
|
|
94
|
+
const result = parseResource('https://example.com/@alice')
|
|
95
|
+
assert.strictEqual(result, null)
|
|
96
|
+
})
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
describe('getActorUrl', () => {
|
|
100
|
+
test('extracts actor URL from webfinger response', () => {
|
|
101
|
+
const webfinger = {
|
|
102
|
+
subject: 'acct:alice@example.com',
|
|
103
|
+
links: [
|
|
104
|
+
{ rel: 'self', type: 'application/activity+json', href: 'https://example.com/users/alice' }
|
|
105
|
+
]
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
assert.strictEqual(getActorUrl(webfinger), 'https://example.com/users/alice')
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
test('returns null when no self link exists', () => {
|
|
112
|
+
const webfinger = {
|
|
113
|
+
subject: 'acct:alice@example.com',
|
|
114
|
+
links: [
|
|
115
|
+
{ rel: 'other', href: 'https://example.com' }
|
|
116
|
+
]
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
assert.strictEqual(getActorUrl(webfinger), null)
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
test('returns null when links is missing', () => {
|
|
123
|
+
const webfinger = { subject: 'acct:alice@example.com' }
|
|
124
|
+
|
|
125
|
+
assert.strictEqual(getActorUrl(webfinger), null)
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
test('ignores non-ActivityPub self links', () => {
|
|
129
|
+
const webfinger = {
|
|
130
|
+
subject: 'acct:alice@example.com',
|
|
131
|
+
links: [
|
|
132
|
+
{ rel: 'self', type: 'text/html', href: 'https://example.com/@alice' },
|
|
133
|
+
{ rel: 'self', type: 'application/activity+json', href: 'https://example.com/users/alice' }
|
|
134
|
+
]
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
assert.strictEqual(getActorUrl(webfinger), 'https://example.com/users/alice')
|
|
138
|
+
})
|
|
139
|
+
})
|