fedbox 0.0.4 → 0.0.6
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/lib/server.js +22 -2
- package/package.json +4 -4
- package/public/profile.js +151 -0
- package/test/actions.test.js +78 -0
- package/test/store.test.js +233 -0
package/lib/server.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import { createServer } from 'http'
|
|
8
8
|
import { readFileSync, existsSync } from 'fs'
|
|
9
|
+
import { join, extname } from 'path'
|
|
9
10
|
import { profile, auth, webfinger, outbox } from 'microfed'
|
|
10
11
|
import {
|
|
11
12
|
initStore,
|
|
@@ -383,7 +384,7 @@ async function handleRequest(req, res) {
|
|
|
383
384
|
version: '2.1',
|
|
384
385
|
software: {
|
|
385
386
|
name: 'fedbox',
|
|
386
|
-
version: '0.0.
|
|
387
|
+
version: '0.0.6',
|
|
387
388
|
repository: 'https://github.com/micro-fed/fedbox'
|
|
388
389
|
},
|
|
389
390
|
protocols: ['activitypub'],
|
|
@@ -523,6 +524,25 @@ async function handleRequest(req, res) {
|
|
|
523
524
|
return res.end(renderHome())
|
|
524
525
|
}
|
|
525
526
|
|
|
527
|
+
// Static files from /public/
|
|
528
|
+
if (path.startsWith('/public/')) {
|
|
529
|
+
const filePath = join(process.cwd(), path)
|
|
530
|
+
if (existsSync(filePath)) {
|
|
531
|
+
const ext = extname(filePath)
|
|
532
|
+
const mimeTypes = {
|
|
533
|
+
'.js': 'application/javascript',
|
|
534
|
+
'.css': 'text/css',
|
|
535
|
+
'.html': 'text/html',
|
|
536
|
+
'.json': 'application/json',
|
|
537
|
+
'.png': 'image/png',
|
|
538
|
+
'.jpg': 'image/jpeg',
|
|
539
|
+
'.svg': 'image/svg+xml'
|
|
540
|
+
}
|
|
541
|
+
res.setHeader('Content-Type', mimeTypes[ext] || 'text/plain')
|
|
542
|
+
return res.end(readFileSync(filePath))
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
526
546
|
res.writeHead(404)
|
|
527
547
|
res.end('Not found')
|
|
528
548
|
}
|
|
@@ -576,7 +596,7 @@ function renderProfile() {
|
|
|
576
596
|
<title>${config.displayName} (@${config.username}@${getDomain()})</title>
|
|
577
597
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
578
598
|
<link rel="alternate" type="application/activity+json" href="${profileUrl}">
|
|
579
|
-
<script type="application/ld+json">
|
|
599
|
+
<script type="application/ld+json" id="profile">
|
|
580
600
|
${JSON.stringify(actor, null, 2)}
|
|
581
601
|
</script>
|
|
582
602
|
<style>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fedbox",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.6",
|
|
4
4
|
"description": "Zero to Fediverse in 60 seconds",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/server.js",
|
|
@@ -9,11 +9,11 @@
|
|
|
9
9
|
},
|
|
10
10
|
"scripts": {
|
|
11
11
|
"start": "node lib/server.js",
|
|
12
|
-
"test": "node --test test
|
|
12
|
+
"test": "node --test 'test/*.test.js'"
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"
|
|
16
|
-
"
|
|
15
|
+
"better-sqlite3": "^11.0.0",
|
|
16
|
+
"microfed": "^0.0.14"
|
|
17
17
|
},
|
|
18
18
|
"keywords": [
|
|
19
19
|
"activitypub",
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Microfed Profile Component
|
|
3
|
+
* Reads JSON-LD data island and renders a profile card
|
|
4
|
+
*
|
|
5
|
+
* Usage: <script src="profile.js"></script>
|
|
6
|
+
* Requires: <script type="application/ld+json" id="profile">...</script>
|
|
7
|
+
*/
|
|
8
|
+
(function() {
|
|
9
|
+
'use strict'
|
|
10
|
+
|
|
11
|
+
function init() {
|
|
12
|
+
// Find JSON-LD data island
|
|
13
|
+
const script = document.querySelector('script[type="application/ld+json"]#profile') ||
|
|
14
|
+
document.querySelector('script[type="application/ld+json"]')
|
|
15
|
+
|
|
16
|
+
if (!script) {
|
|
17
|
+
console.warn('Microfed Profile: No JSON-LD data island found')
|
|
18
|
+
return
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let actor
|
|
22
|
+
try {
|
|
23
|
+
actor = JSON.parse(script.textContent)
|
|
24
|
+
} catch (e) {
|
|
25
|
+
console.error('Microfed Profile: Invalid JSON-LD', e)
|
|
26
|
+
return
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Find or create container
|
|
30
|
+
let container = document.getElementById('microfed-profile')
|
|
31
|
+
if (!container) {
|
|
32
|
+
container = document.createElement('div')
|
|
33
|
+
container.id = 'microfed-profile'
|
|
34
|
+
script.parentNode.insertBefore(container, script.nextSibling)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
render(container, actor)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function render(container, actor) {
|
|
41
|
+
const name = actor.name || actor.preferredUsername || 'Unknown'
|
|
42
|
+
const username = actor.preferredUsername || ''
|
|
43
|
+
const summary = actor.summary || ''
|
|
44
|
+
const icon = actor.icon?.url || actor.icon || ''
|
|
45
|
+
const followers = actor.followers || ''
|
|
46
|
+
const following = actor.following || ''
|
|
47
|
+
|
|
48
|
+
container.innerHTML = `
|
|
49
|
+
<style>
|
|
50
|
+
.mf-profile {
|
|
51
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
52
|
+
max-width: 400px;
|
|
53
|
+
border: 1px solid #e1e4e8;
|
|
54
|
+
border-radius: 8px;
|
|
55
|
+
padding: 20px;
|
|
56
|
+
background: #fff;
|
|
57
|
+
}
|
|
58
|
+
.mf-profile-header {
|
|
59
|
+
display: flex;
|
|
60
|
+
align-items: center;
|
|
61
|
+
gap: 16px;
|
|
62
|
+
margin-bottom: 12px;
|
|
63
|
+
}
|
|
64
|
+
.mf-profile-avatar {
|
|
65
|
+
width: 64px;
|
|
66
|
+
height: 64px;
|
|
67
|
+
border-radius: 50%;
|
|
68
|
+
object-fit: cover;
|
|
69
|
+
background: #e1e4e8;
|
|
70
|
+
}
|
|
71
|
+
.mf-profile-avatar-placeholder {
|
|
72
|
+
width: 64px;
|
|
73
|
+
height: 64px;
|
|
74
|
+
border-radius: 50%;
|
|
75
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
76
|
+
display: flex;
|
|
77
|
+
align-items: center;
|
|
78
|
+
justify-content: center;
|
|
79
|
+
color: white;
|
|
80
|
+
font-size: 24px;
|
|
81
|
+
font-weight: bold;
|
|
82
|
+
}
|
|
83
|
+
.mf-profile-name {
|
|
84
|
+
font-size: 1.25rem;
|
|
85
|
+
font-weight: 600;
|
|
86
|
+
margin: 0;
|
|
87
|
+
color: #24292e;
|
|
88
|
+
}
|
|
89
|
+
.mf-profile-username {
|
|
90
|
+
font-size: 0.875rem;
|
|
91
|
+
color: #586069;
|
|
92
|
+
margin: 2px 0 0 0;
|
|
93
|
+
}
|
|
94
|
+
.mf-profile-summary {
|
|
95
|
+
color: #24292e;
|
|
96
|
+
line-height: 1.5;
|
|
97
|
+
margin: 12px 0;
|
|
98
|
+
}
|
|
99
|
+
.mf-profile-stats {
|
|
100
|
+
display: flex;
|
|
101
|
+
gap: 16px;
|
|
102
|
+
margin-top: 12px;
|
|
103
|
+
padding-top: 12px;
|
|
104
|
+
border-top: 1px solid #e1e4e8;
|
|
105
|
+
}
|
|
106
|
+
.mf-profile-stat {
|
|
107
|
+
color: #586069;
|
|
108
|
+
font-size: 0.875rem;
|
|
109
|
+
text-decoration: none;
|
|
110
|
+
}
|
|
111
|
+
.mf-profile-stat:hover {
|
|
112
|
+
color: #0366d6;
|
|
113
|
+
}
|
|
114
|
+
.mf-profile-stat strong {
|
|
115
|
+
color: #24292e;
|
|
116
|
+
}
|
|
117
|
+
</style>
|
|
118
|
+
<div class="mf-profile">
|
|
119
|
+
<div class="mf-profile-header">
|
|
120
|
+
${icon
|
|
121
|
+
? `<img class="mf-profile-avatar" src="${escapeHtml(icon)}" alt="${escapeHtml(name)}">`
|
|
122
|
+
: `<div class="mf-profile-avatar-placeholder">${escapeHtml(name.charAt(0).toUpperCase())}</div>`
|
|
123
|
+
}
|
|
124
|
+
<div>
|
|
125
|
+
<h2 class="mf-profile-name">${escapeHtml(name)}</h2>
|
|
126
|
+
${username ? `<p class="mf-profile-username">@${escapeHtml(username)}</p>` : ''}
|
|
127
|
+
</div>
|
|
128
|
+
</div>
|
|
129
|
+
${summary ? `<div class="mf-profile-summary">${summary}</div>` : ''}
|
|
130
|
+
<div class="mf-profile-stats">
|
|
131
|
+
${followers ? `<a class="mf-profile-stat" href="${escapeHtml(followers)}"><strong>Followers</strong></a>` : ''}
|
|
132
|
+
${following ? `<a class="mf-profile-stat" href="${escapeHtml(following)}"><strong>Following</strong></a>` : ''}
|
|
133
|
+
</div>
|
|
134
|
+
</div>
|
|
135
|
+
`
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function escapeHtml(str) {
|
|
139
|
+
if (!str) return ''
|
|
140
|
+
const div = document.createElement('div')
|
|
141
|
+
div.textContent = str
|
|
142
|
+
return div.innerHTML
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Initialize when DOM is ready
|
|
146
|
+
if (document.readyState === 'loading') {
|
|
147
|
+
document.addEventListener('DOMContentLoaded', init)
|
|
148
|
+
} else {
|
|
149
|
+
init()
|
|
150
|
+
}
|
|
151
|
+
})()
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Actions tests
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { test, describe, before, after } from 'node:test'
|
|
6
|
+
import assert from 'node:assert'
|
|
7
|
+
import { writeFileSync, unlinkSync, existsSync, mkdirSync, rmSync } from 'fs'
|
|
8
|
+
import { generateKeypair } from 'microfed/auth'
|
|
9
|
+
|
|
10
|
+
function setupConfig(domain = null) {
|
|
11
|
+
const { publicKey, privateKey } = generateKeypair()
|
|
12
|
+
const config = {
|
|
13
|
+
username: 'alice',
|
|
14
|
+
displayName: 'Alice',
|
|
15
|
+
summary: 'Test user',
|
|
16
|
+
port: 3000,
|
|
17
|
+
publicKey,
|
|
18
|
+
privateKey
|
|
19
|
+
}
|
|
20
|
+
if (domain) config.domain = domain
|
|
21
|
+
writeFileSync('fedbox.json', JSON.stringify(config, null, 2))
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function cleanup() {
|
|
25
|
+
if (existsSync('fedbox.json')) unlinkSync('fedbox.json')
|
|
26
|
+
if (existsSync('data/fedbox.db')) unlinkSync('data/fedbox.db')
|
|
27
|
+
try { rmSync('data', { recursive: true }) } catch {}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
describe('Actions', () => {
|
|
31
|
+
before(() => {
|
|
32
|
+
cleanup()
|
|
33
|
+
if (!existsSync('data')) mkdirSync('data')
|
|
34
|
+
setupConfig()
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
after(() => {
|
|
38
|
+
cleanup()
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
test('post creates note with Solid-compatible noteId', async () => {
|
|
42
|
+
const { post } = await import('../lib/actions.js')
|
|
43
|
+
const result = await post('Hello world!')
|
|
44
|
+
|
|
45
|
+
// noteId should be /alice/posts/xxx (not /alice#me/posts/xxx)
|
|
46
|
+
assert.ok(result.noteId.includes('/alice/posts/'), 'noteId should include /alice/posts/')
|
|
47
|
+
assert.ok(!result.noteId.includes('#me/posts'), 'noteId should not have #me before /posts')
|
|
48
|
+
assert.ok(!result.noteId.includes('#me'), 'noteId should not contain #me at all')
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
test('post returns delivery results', async () => {
|
|
52
|
+
const { post } = await import('../lib/actions.js')
|
|
53
|
+
const result = await post('Test delivery')
|
|
54
|
+
|
|
55
|
+
assert.ok('delivered' in result, 'result should have delivered stats')
|
|
56
|
+
assert.ok('success' in result.delivered)
|
|
57
|
+
assert.ok('failed' in result.delivered)
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
test('myPosts returns saved posts', async () => {
|
|
61
|
+
const { myPosts } = await import('../lib/actions.js')
|
|
62
|
+
const posts = myPosts(10)
|
|
63
|
+
// Should have posts from previous tests
|
|
64
|
+
assert.ok(posts.length >= 1, 'should have at least 1 post')
|
|
65
|
+
assert.ok(posts[0].content, 'post should have content')
|
|
66
|
+
assert.ok(posts[0].id, 'post should have id')
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
test('myPosts posts have Solid-compatible URLs', async () => {
|
|
70
|
+
const { myPosts } = await import('../lib/actions.js')
|
|
71
|
+
const posts = myPosts(10)
|
|
72
|
+
|
|
73
|
+
for (const post of posts) {
|
|
74
|
+
assert.ok(post.id.includes('/alice/posts/'), 'post id should include /alice/posts/')
|
|
75
|
+
assert.ok(!post.id.includes('#me'), 'post id should not contain #me')
|
|
76
|
+
}
|
|
77
|
+
})
|
|
78
|
+
})
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Store tests
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { test, describe, beforeEach, afterEach } from 'node:test'
|
|
6
|
+
import assert from 'node:assert'
|
|
7
|
+
import { unlinkSync, existsSync, mkdirSync } from 'fs'
|
|
8
|
+
import {
|
|
9
|
+
initStore,
|
|
10
|
+
getStore,
|
|
11
|
+
addFollower,
|
|
12
|
+
removeFollower,
|
|
13
|
+
getFollowers,
|
|
14
|
+
getFollowerCount,
|
|
15
|
+
addFollowing,
|
|
16
|
+
acceptFollowing,
|
|
17
|
+
getFollowing,
|
|
18
|
+
getFollowingCount,
|
|
19
|
+
savePost,
|
|
20
|
+
getPosts,
|
|
21
|
+
getPost,
|
|
22
|
+
cacheActor,
|
|
23
|
+
getCachedActor,
|
|
24
|
+
saveActivity,
|
|
25
|
+
getActivities
|
|
26
|
+
} from '../lib/store.js'
|
|
27
|
+
|
|
28
|
+
const TEST_DB = 'test/test.db'
|
|
29
|
+
|
|
30
|
+
function cleanup() {
|
|
31
|
+
if (existsSync(TEST_DB)) {
|
|
32
|
+
unlinkSync(TEST_DB)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
describe('Store', () => {
|
|
37
|
+
beforeEach(() => {
|
|
38
|
+
cleanup()
|
|
39
|
+
if (!existsSync('test')) mkdirSync('test', { recursive: true })
|
|
40
|
+
initStore(TEST_DB)
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
afterEach(() => {
|
|
44
|
+
const db = getStore()
|
|
45
|
+
db.close()
|
|
46
|
+
cleanup()
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
test('initStore creates database', () => {
|
|
50
|
+
assert.ok(existsSync(TEST_DB))
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
test('getStore returns database instance', () => {
|
|
54
|
+
const db = getStore()
|
|
55
|
+
assert.ok(db)
|
|
56
|
+
})
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
describe('Followers', () => {
|
|
60
|
+
beforeEach(() => {
|
|
61
|
+
cleanup()
|
|
62
|
+
initStore(TEST_DB)
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
afterEach(() => {
|
|
66
|
+
const db = getStore()
|
|
67
|
+
db.close()
|
|
68
|
+
cleanup()
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
test('addFollower and getFollowers', () => {
|
|
72
|
+
addFollower('https://example.com/user/alice', 'https://example.com/user/alice/inbox')
|
|
73
|
+
const followers = getFollowers()
|
|
74
|
+
assert.strictEqual(followers.length, 1)
|
|
75
|
+
assert.strictEqual(followers[0].actor, 'https://example.com/user/alice')
|
|
76
|
+
assert.strictEqual(followers[0].inbox, 'https://example.com/user/alice/inbox')
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
test('getFollowerCount', () => {
|
|
80
|
+
assert.strictEqual(getFollowerCount(), 0)
|
|
81
|
+
addFollower('https://example.com/user/alice', 'https://example.com/inbox')
|
|
82
|
+
assert.strictEqual(getFollowerCount(), 1)
|
|
83
|
+
addFollower('https://example.com/user/bob', 'https://example.com/inbox')
|
|
84
|
+
assert.strictEqual(getFollowerCount(), 2)
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
test('removeFollower', () => {
|
|
88
|
+
addFollower('https://example.com/user/alice', 'https://example.com/inbox')
|
|
89
|
+
assert.strictEqual(getFollowerCount(), 1)
|
|
90
|
+
removeFollower('https://example.com/user/alice')
|
|
91
|
+
assert.strictEqual(getFollowerCount(), 0)
|
|
92
|
+
})
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
describe('Following', () => {
|
|
96
|
+
beforeEach(() => {
|
|
97
|
+
cleanup()
|
|
98
|
+
initStore(TEST_DB)
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
afterEach(() => {
|
|
102
|
+
const db = getStore()
|
|
103
|
+
db.close()
|
|
104
|
+
cleanup()
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
test('addFollowing with pending status', () => {
|
|
108
|
+
addFollowing('https://example.com/user/bob', false)
|
|
109
|
+
assert.strictEqual(getFollowingCount(), 0) // Not accepted yet
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
test('acceptFollowing', () => {
|
|
113
|
+
addFollowing('https://example.com/user/bob', false)
|
|
114
|
+
assert.strictEqual(getFollowingCount(), 0)
|
|
115
|
+
acceptFollowing('https://example.com/user/bob')
|
|
116
|
+
assert.strictEqual(getFollowingCount(), 1)
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
test('getFollowing returns only accepted', () => {
|
|
120
|
+
addFollowing('https://example.com/user/bob', false)
|
|
121
|
+
addFollowing('https://example.com/user/charlie', true)
|
|
122
|
+
const following = getFollowing()
|
|
123
|
+
assert.strictEqual(following.length, 1)
|
|
124
|
+
assert.strictEqual(following[0].actor, 'https://example.com/user/charlie')
|
|
125
|
+
})
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
describe('Posts', () => {
|
|
129
|
+
beforeEach(() => {
|
|
130
|
+
cleanup()
|
|
131
|
+
initStore(TEST_DB)
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
afterEach(() => {
|
|
135
|
+
const db = getStore()
|
|
136
|
+
db.close()
|
|
137
|
+
cleanup()
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
test('savePost and getPost', () => {
|
|
141
|
+
const id = 'https://example.com/alice/posts/123'
|
|
142
|
+
savePost(id, 'Hello world!')
|
|
143
|
+
const post = getPost(id)
|
|
144
|
+
assert.ok(post)
|
|
145
|
+
assert.strictEqual(post.id, id)
|
|
146
|
+
assert.strictEqual(post.content, 'Hello world!')
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
test('savePost with inReplyTo', () => {
|
|
150
|
+
const id = 'https://example.com/alice/posts/456'
|
|
151
|
+
const replyTo = 'https://other.com/posts/789'
|
|
152
|
+
savePost(id, 'This is a reply', replyTo)
|
|
153
|
+
const post = getPost(id)
|
|
154
|
+
assert.strictEqual(post.in_reply_to, replyTo)
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
test('getPosts returns posts', () => {
|
|
158
|
+
savePost('https://example.com/posts/1', 'First')
|
|
159
|
+
savePost('https://example.com/posts/2', 'Second')
|
|
160
|
+
savePost('https://example.com/posts/3', 'Third')
|
|
161
|
+
const posts = getPosts(10)
|
|
162
|
+
assert.strictEqual(posts.length, 3)
|
|
163
|
+
// All posts should be present
|
|
164
|
+
const contents = posts.map(p => p.content)
|
|
165
|
+
assert.ok(contents.includes('First'))
|
|
166
|
+
assert.ok(contents.includes('Second'))
|
|
167
|
+
assert.ok(contents.includes('Third'))
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
test('getPosts respects limit', () => {
|
|
171
|
+
for (let i = 0; i < 10; i++) {
|
|
172
|
+
savePost(`https://example.com/posts/${i}`, `Post ${i}`)
|
|
173
|
+
}
|
|
174
|
+
const posts = getPosts(5)
|
|
175
|
+
assert.strictEqual(posts.length, 5)
|
|
176
|
+
})
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
describe('Actor Cache', () => {
|
|
180
|
+
beforeEach(() => {
|
|
181
|
+
cleanup()
|
|
182
|
+
initStore(TEST_DB)
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
afterEach(() => {
|
|
186
|
+
const db = getStore()
|
|
187
|
+
db.close()
|
|
188
|
+
cleanup()
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
test('cacheActor and getCachedActor', () => {
|
|
192
|
+
const actor = {
|
|
193
|
+
id: 'https://example.com/user/alice',
|
|
194
|
+
type: 'Person',
|
|
195
|
+
preferredUsername: 'alice'
|
|
196
|
+
}
|
|
197
|
+
cacheActor(actor)
|
|
198
|
+
const cached = getCachedActor(actor.id)
|
|
199
|
+
assert.deepStrictEqual(cached, actor)
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
test('getCachedActor returns null for unknown', () => {
|
|
203
|
+
const cached = getCachedActor('https://example.com/unknown')
|
|
204
|
+
assert.strictEqual(cached, null)
|
|
205
|
+
})
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
describe('Activities', () => {
|
|
209
|
+
beforeEach(() => {
|
|
210
|
+
cleanup()
|
|
211
|
+
initStore(TEST_DB)
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
afterEach(() => {
|
|
215
|
+
const db = getStore()
|
|
216
|
+
db.close()
|
|
217
|
+
cleanup()
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
test('saveActivity and getActivities', () => {
|
|
221
|
+
const activity = {
|
|
222
|
+
id: 'https://example.com/activity/1',
|
|
223
|
+
type: 'Create',
|
|
224
|
+
actor: 'https://example.com/user/alice',
|
|
225
|
+
object: { type: 'Note', content: 'Hello' }
|
|
226
|
+
}
|
|
227
|
+
saveActivity(activity)
|
|
228
|
+
const activities = getActivities(10)
|
|
229
|
+
assert.strictEqual(activities.length, 1)
|
|
230
|
+
assert.strictEqual(activities[0].type, 'Create')
|
|
231
|
+
assert.deepStrictEqual(activities[0].raw, activity)
|
|
232
|
+
})
|
|
233
|
+
})
|