@atproto/dev-env 0.0.0-spaces-alpha-20260818163953
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/CHANGELOG.md +3424 -0
- package/LICENSE.txt +7 -0
- package/README.md +39 -0
- package/bin.js +7 -0
- package/dist/bin-multi-pds.d.ts +2 -0
- package/dist/bin-multi-pds.d.ts.map +1 -0
- package/dist/bin-multi-pds.js +130 -0
- package/dist/bin-multi-pds.js.map +1 -0
- package/dist/bin.d.ts +2 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +56 -0
- package/dist/bin.js.map +1 -0
- package/dist/bsky.d.ts +27 -0
- package/dist/bsky.d.ts.map +1 -0
- package/dist/bsky.js +165 -0
- package/dist/bsky.js.map +1 -0
- package/dist/bsync.d.ts +22 -0
- package/dist/bsync.d.ts.map +1 -0
- package/dist/bsync.js +54 -0
- package/dist/bsync.js.map +1 -0
- package/dist/const.d.ts +4 -0
- package/dist/const.d.ts.map +1 -0
- package/dist/const.js +4 -0
- package/dist/const.js.map +1 -0
- package/dist/env.d.ts +2 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +10 -0
- package/dist/env.js.map +1 -0
- package/dist/feed-gen.d.ts +15 -0
- package/dist/feed-gen.d.ts.map +1 -0
- package/dist/feed-gen.js +73 -0
- package/dist/feed-gen.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/introspect.d.ts +22 -0
- package/dist/introspect.d.ts.map +1 -0
- package/dist/introspect.js +65 -0
- package/dist/introspect.js.map +1 -0
- package/dist/mock/data.d.ts +3 -0
- package/dist/mock/data.d.ts.map +1 -0
- package/dist/mock/data.js +46 -0
- package/dist/mock/data.js.map +1 -0
- package/dist/mock/img/blur-hash-avatar-b64.d.ts +3 -0
- package/dist/mock/img/blur-hash-avatar-b64.d.ts.map +1 -0
- package/dist/mock/img/blur-hash-avatar-b64.js +2 -0
- package/dist/mock/img/blur-hash-avatar-b64.js.map +1 -0
- package/dist/mock/img/labeled-img-b64.d.ts +3 -0
- package/dist/mock/img/labeled-img-b64.d.ts.map +1 -0
- package/dist/mock/img/labeled-img-b64.js +2 -0
- package/dist/mock/img/labeled-img-b64.js.map +1 -0
- package/dist/mock/index.d.ts +3 -0
- package/dist/mock/index.d.ts.map +1 -0
- package/dist/mock/index.js +574 -0
- package/dist/mock/index.js.map +1 -0
- package/dist/moderator-client.d.ts +58 -0
- package/dist/moderator-client.d.ts.map +1 -0
- package/dist/moderator-client.js +110 -0
- package/dist/moderator-client.js.map +1 -0
- package/dist/network-no-appview.d.ts +21 -0
- package/dist/network-no-appview.d.ts.map +1 -0
- package/dist/network-no-appview.js +74 -0
- package/dist/network-no-appview.js.map +1 -0
- package/dist/network.d.ts +32 -0
- package/dist/network.d.ts.map +1 -0
- package/dist/network.js +190 -0
- package/dist/network.js.map +1 -0
- package/dist/ozone.d.ts +31 -0
- package/dist/ozone.d.ts.map +1 -0
- package/dist/ozone.js +254 -0
- package/dist/ozone.js.map +1 -0
- package/dist/pds.d.ts +23 -0
- package/dist/pds.d.ts.map +1 -0
- package/dist/pds.js +97 -0
- package/dist/pds.js.map +1 -0
- package/dist/plc.d.ts +14 -0
- package/dist/plc.d.ts.map +1 -0
- package/dist/plc.js +28 -0
- package/dist/plc.js.map +1 -0
- package/dist/seed/author-feed.d.ts +4 -0
- package/dist/seed/author-feed.d.ts.map +1 -0
- package/dist/seed/author-feed.js +50 -0
- package/dist/seed/author-feed.js.map +1 -0
- package/dist/seed/basic.d.ts +16 -0
- package/dist/seed/basic.d.ts.map +1 -0
- package/dist/seed/basic.js +113 -0
- package/dist/seed/basic.js.map +1 -0
- package/dist/seed/client.d.ts +151 -0
- package/dist/seed/client.d.ts.map +1 -0
- package/dist/seed/client.js +365 -0
- package/dist/seed/client.js.map +1 -0
- package/dist/seed/follows.d.ts +4 -0
- package/dist/seed/follows.d.ts.map +1 -0
- package/dist/seed/follows.js +55 -0
- package/dist/seed/follows.js.map +1 -0
- package/dist/seed/index.d.ts +11 -0
- package/dist/seed/index.d.ts.map +1 -0
- package/dist/seed/index.js +11 -0
- package/dist/seed/index.js.map +1 -0
- package/dist/seed/likes.d.ts +4 -0
- package/dist/seed/likes.d.ts.map +1 -0
- package/dist/seed/likes.js +37 -0
- package/dist/seed/likes.js.map +1 -0
- package/dist/seed/quotes.d.ts +4 -0
- package/dist/seed/quotes.d.ts.map +1 -0
- package/dist/seed/quotes.js +21 -0
- package/dist/seed/quotes.js.map +1 -0
- package/dist/seed/reposts.d.ts +4 -0
- package/dist/seed/reposts.d.ts.map +1 -0
- package/dist/seed/reposts.js +17 -0
- package/dist/seed/reposts.js.map +1 -0
- package/dist/seed/thread-v2.d.ts +227 -0
- package/dist/seed/thread-v2.d.ts.map +1 -0
- package/dist/seed/thread-v2.js +755 -0
- package/dist/seed/thread-v2.js.map +1 -0
- package/dist/seed/users-bulk.d.ts +3 -0
- package/dist/seed/users-bulk.d.ts.map +1 -0
- package/dist/seed/users-bulk.js +224 -0
- package/dist/seed/users-bulk.js.map +1 -0
- package/dist/seed/users.d.ts +3 -0
- package/dist/seed/users.d.ts.map +1 -0
- package/dist/seed/users.js +45 -0
- package/dist/seed/users.js.map +1 -0
- package/dist/seed/verifications.d.ts +5 -0
- package/dist/seed/verifications.d.ts.map +1 -0
- package/dist/seed/verifications.js +164 -0
- package/dist/seed/verifications.js.map +1 -0
- package/dist/service-profile-lexicon.d.ts +11 -0
- package/dist/service-profile-lexicon.d.ts.map +1 -0
- package/dist/service-profile-lexicon.js +115 -0
- package/dist/service-profile-lexicon.js.map +1 -0
- package/dist/service-profile-ozone.d.ts +18 -0
- package/dist/service-profile-ozone.d.ts.map +1 -0
- package/dist/service-profile-ozone.js +333 -0
- package/dist/service-profile-ozone.js.map +1 -0
- package/dist/service-profile.d.ts +24 -0
- package/dist/service-profile.d.ts.map +1 -0
- package/dist/service-profile.js +61 -0
- package/dist/service-profile.js.map +1 -0
- package/dist/types.d.ts +61 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/util.d.ts +14 -0
- package/dist/util.d.ts.map +1 -0
- package/dist/util.js +86 -0
- package/dist/util.js.map +1 -0
- package/package.json +69 -0
- package/src/bin-multi-pds.ts +166 -0
- package/src/bin.ts +64 -0
- package/src/bsky.ts +198 -0
- package/src/bsync.ts +66 -0
- package/src/const.ts +3 -0
- package/src/env.ts +9 -0
- package/src/feed-gen.ts +96 -0
- package/src/index.ts +17 -0
- package/src/introspect.ts +91 -0
- package/src/mock/data.ts +46 -0
- package/src/mock/img/blur-hash-avatar-b64.ts +1 -0
- package/src/mock/img/labeled-img-b64.ts +1 -0
- package/src/mock/index.ts +702 -0
- package/src/moderator-client.ts +267 -0
- package/src/network-no-appview.ts +89 -0
- package/src/network.ts +243 -0
- package/src/ozone.ts +287 -0
- package/src/pds.ts +118 -0
- package/src/plc.ts +33 -0
- package/src/seed/author-feed.ts +98 -0
- package/src/seed/basic.ts +189 -0
- package/src/seed/client.ts +676 -0
- package/src/seed/follows.ts +57 -0
- package/src/seed/index.ts +11 -0
- package/src/seed/likes.ts +43 -0
- package/src/seed/quotes.ts +53 -0
- package/src/seed/reposts.ts +18 -0
- package/src/seed/thread-v2.ts +925 -0
- package/src/seed/users-bulk.ts +229 -0
- package/src/seed/users.ts +64 -0
- package/src/seed/verifications.ts +254 -0
- package/src/service-profile-lexicon.ts +127 -0
- package/src/service-profile-ozone.ts +375 -0
- package/src/service-profile.ts +95 -0
- package/src/types.ts +68 -0
- package/src/util.ts +118 -0
- package/tsconfig.build.json +25 -0
- package/tsconfig.json +4 -0
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type $Typed,
|
|
3
|
+
type AtpAgent,
|
|
4
|
+
type ChatBskyConvoDefs,
|
|
5
|
+
type ComAtprotoAdminDefs,
|
|
6
|
+
type ComAtprotoRepoStrongRef,
|
|
7
|
+
type ToolsOzoneModerationDefs,
|
|
8
|
+
type ToolsOzoneModerationEmitEvent as EmitModerationEvent,
|
|
9
|
+
ToolsOzoneModerationGetReporterStats as _GetReporterStats, // includes types for getReporterStats()
|
|
10
|
+
type ToolsOzoneModerationQueryEvents as QueryModerationEvents,
|
|
11
|
+
type ToolsOzoneModerationQueryStatuses as QueryModerationStatuses,
|
|
12
|
+
type ToolsOzoneReportQueryReports as QueryModerationReports,
|
|
13
|
+
type ToolsOzoneSettingRemoveOptions,
|
|
14
|
+
type ToolsOzoneSettingUpsertOption,
|
|
15
|
+
} from '@atproto/api'
|
|
16
|
+
import type { TestOzone } from './ozone.js'
|
|
17
|
+
|
|
18
|
+
type TakeActionInput = EmitModerationEvent.InputSchema
|
|
19
|
+
type QueryStatusesParams = QueryModerationStatuses.QueryParams
|
|
20
|
+
type QueryEventsParams = QueryModerationEvents.QueryParams
|
|
21
|
+
type QueryReportsParams = QueryModerationReports.QueryParams
|
|
22
|
+
type ModLevel = 'admin' | 'moderator' | 'triage'
|
|
23
|
+
|
|
24
|
+
export class ModeratorClient {
|
|
25
|
+
agent: AtpAgent
|
|
26
|
+
constructor(public ozone: TestOzone) {
|
|
27
|
+
this.agent = ozone.getAgent()
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async getEvent(id: number, role?: ModLevel) {
|
|
31
|
+
const result = await this.agent.tools.ozone.moderation.getEvent(
|
|
32
|
+
{ id },
|
|
33
|
+
{
|
|
34
|
+
headers: await this.ozone.modHeaders(
|
|
35
|
+
'tools.ozone.moderation.getEvent',
|
|
36
|
+
role,
|
|
37
|
+
),
|
|
38
|
+
},
|
|
39
|
+
)
|
|
40
|
+
return result.data
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async queryStatuses(input: QueryStatusesParams, role?: ModLevel) {
|
|
44
|
+
const result = await this.agent.tools.ozone.moderation.queryStatuses(
|
|
45
|
+
input,
|
|
46
|
+
{
|
|
47
|
+
headers: await this.ozone.modHeaders(
|
|
48
|
+
'tools.ozone.moderation.queryStatuses',
|
|
49
|
+
role,
|
|
50
|
+
),
|
|
51
|
+
},
|
|
52
|
+
)
|
|
53
|
+
return result.data
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async getReporterStats(dids: string[]) {
|
|
57
|
+
const result = await this.agent.tools.ozone.moderation.getReporterStats(
|
|
58
|
+
{ dids },
|
|
59
|
+
{
|
|
60
|
+
headers: await this.ozone.modHeaders(
|
|
61
|
+
'tools.ozone.moderation.getReporterStats',
|
|
62
|
+
'admin',
|
|
63
|
+
),
|
|
64
|
+
},
|
|
65
|
+
)
|
|
66
|
+
return result.data
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async queryEvents(input: QueryEventsParams, role?: ModLevel) {
|
|
70
|
+
const result = await this.agent.tools.ozone.moderation.queryEvents(input, {
|
|
71
|
+
headers: await this.ozone.modHeaders(
|
|
72
|
+
'tools.ozone.moderation.queryEvents',
|
|
73
|
+
role,
|
|
74
|
+
),
|
|
75
|
+
})
|
|
76
|
+
return result.data
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async queryReports(input: QueryReportsParams, role?: ModLevel) {
|
|
80
|
+
const result = await this.agent.tools.ozone.report.queryReports(input, {
|
|
81
|
+
headers: await this.ozone.modHeaders(
|
|
82
|
+
'tools.ozone.report.queryReports',
|
|
83
|
+
role,
|
|
84
|
+
),
|
|
85
|
+
})
|
|
86
|
+
return result.data
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async emitEvent(
|
|
90
|
+
opts: {
|
|
91
|
+
event: TakeActionInput['event']
|
|
92
|
+
subject:
|
|
93
|
+
| $Typed<ComAtprotoAdminDefs.RepoRef>
|
|
94
|
+
| $Typed<ComAtprotoRepoStrongRef.Main>
|
|
95
|
+
| $Typed<ChatBskyConvoDefs.MessageRef>
|
|
96
|
+
| $Typed<ChatBskyConvoDefs.ConvoRef>
|
|
97
|
+
| { $type: string }
|
|
98
|
+
subjectBlobCids?: TakeActionInput['subjectBlobCids']
|
|
99
|
+
reason?: string
|
|
100
|
+
createdBy?: string
|
|
101
|
+
meta?: unknown
|
|
102
|
+
modTool?: ToolsOzoneModerationDefs.ModTool
|
|
103
|
+
externalId?: string
|
|
104
|
+
reportAction?: TakeActionInput['reportAction']
|
|
105
|
+
},
|
|
106
|
+
role?: ModLevel,
|
|
107
|
+
) {
|
|
108
|
+
const {
|
|
109
|
+
event,
|
|
110
|
+
subject,
|
|
111
|
+
subjectBlobCids,
|
|
112
|
+
createdBy = 'did:example:admin',
|
|
113
|
+
modTool,
|
|
114
|
+
externalId,
|
|
115
|
+
reportAction,
|
|
116
|
+
} = opts
|
|
117
|
+
const result = await this.agent.tools.ozone.moderation.emitEvent(
|
|
118
|
+
{
|
|
119
|
+
event,
|
|
120
|
+
subject,
|
|
121
|
+
subjectBlobCids,
|
|
122
|
+
createdBy,
|
|
123
|
+
modTool,
|
|
124
|
+
externalId,
|
|
125
|
+
reportAction,
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
encoding: 'application/json',
|
|
129
|
+
headers: await this.ozone.modHeaders(
|
|
130
|
+
'tools.ozone.moderation.emitEvent',
|
|
131
|
+
role,
|
|
132
|
+
),
|
|
133
|
+
},
|
|
134
|
+
)
|
|
135
|
+
return result.data
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async reverseAction(
|
|
139
|
+
opts: {
|
|
140
|
+
id: number
|
|
141
|
+
subject: TakeActionInput['subject']
|
|
142
|
+
reason?: string
|
|
143
|
+
createdBy?: string
|
|
144
|
+
modTool?: ToolsOzoneModerationDefs.ModTool
|
|
145
|
+
},
|
|
146
|
+
role?: ModLevel,
|
|
147
|
+
) {
|
|
148
|
+
const {
|
|
149
|
+
subject,
|
|
150
|
+
reason = 'X',
|
|
151
|
+
createdBy = 'did:example:admin',
|
|
152
|
+
modTool,
|
|
153
|
+
} = opts
|
|
154
|
+
const result = await this.agent.tools.ozone.moderation.emitEvent(
|
|
155
|
+
{
|
|
156
|
+
subject,
|
|
157
|
+
event: {
|
|
158
|
+
$type: 'tools.ozone.moderation.defs#modEventReverseTakedown',
|
|
159
|
+
comment: reason,
|
|
160
|
+
},
|
|
161
|
+
createdBy,
|
|
162
|
+
modTool,
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
encoding: 'application/json',
|
|
166
|
+
headers: await this.ozone.modHeaders(
|
|
167
|
+
'tools.ozone.moderation.emitEvent',
|
|
168
|
+
role,
|
|
169
|
+
),
|
|
170
|
+
},
|
|
171
|
+
)
|
|
172
|
+
return result.data
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
async performTakedown(
|
|
176
|
+
opts: {
|
|
177
|
+
subject:
|
|
178
|
+
| $Typed<ComAtprotoAdminDefs.RepoRef>
|
|
179
|
+
| $Typed<ComAtprotoRepoStrongRef.Main>
|
|
180
|
+
| $Typed<ChatBskyConvoDefs.MessageRef>
|
|
181
|
+
| $Typed<ChatBskyConvoDefs.ConvoRef>
|
|
182
|
+
| { $type: string }
|
|
183
|
+
subjectBlobCids?: TakeActionInput['subjectBlobCids']
|
|
184
|
+
durationInHours?: number
|
|
185
|
+
acknowledgeAccountSubjects?: boolean
|
|
186
|
+
reason?: string
|
|
187
|
+
policies?: string[]
|
|
188
|
+
},
|
|
189
|
+
role?: ModLevel,
|
|
190
|
+
) {
|
|
191
|
+
const { durationInHours, acknowledgeAccountSubjects, policies, ...rest } =
|
|
192
|
+
opts
|
|
193
|
+
return this.emitEvent(
|
|
194
|
+
{
|
|
195
|
+
event: {
|
|
196
|
+
$type: 'tools.ozone.moderation.defs#modEventTakedown',
|
|
197
|
+
acknowledgeAccountSubjects,
|
|
198
|
+
durationInHours,
|
|
199
|
+
policies,
|
|
200
|
+
},
|
|
201
|
+
...rest,
|
|
202
|
+
},
|
|
203
|
+
role,
|
|
204
|
+
)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
async performReverseTakedown(
|
|
208
|
+
opts: {
|
|
209
|
+
subject: TakeActionInput['subject']
|
|
210
|
+
subjectBlobCids?: TakeActionInput['subjectBlobCids']
|
|
211
|
+
reason?: string
|
|
212
|
+
},
|
|
213
|
+
role?: ModLevel,
|
|
214
|
+
) {
|
|
215
|
+
return this.emitEvent(
|
|
216
|
+
{
|
|
217
|
+
event: {
|
|
218
|
+
$type: 'tools.ozone.moderation.defs#modEventReverseTakedown',
|
|
219
|
+
},
|
|
220
|
+
...opts,
|
|
221
|
+
},
|
|
222
|
+
role,
|
|
223
|
+
)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
async upsertSettingOption(
|
|
227
|
+
setting: ToolsOzoneSettingUpsertOption.InputSchema,
|
|
228
|
+
callerRole: 'admin' | 'moderator' | 'triage' = 'admin',
|
|
229
|
+
) {
|
|
230
|
+
const { data } = await this.agent.tools.ozone.setting.upsertOption(
|
|
231
|
+
setting,
|
|
232
|
+
{
|
|
233
|
+
encoding: 'application/json',
|
|
234
|
+
headers: await this.ozone.modHeaders(
|
|
235
|
+
'tools.ozone.setting.upsertOption',
|
|
236
|
+
callerRole,
|
|
237
|
+
),
|
|
238
|
+
},
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
return data
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
async removeSettingOptions(
|
|
245
|
+
params: ToolsOzoneSettingRemoveOptions.InputSchema,
|
|
246
|
+
callerRole: 'admin' | 'moderator' | 'triage' = 'admin',
|
|
247
|
+
) {
|
|
248
|
+
const { data } = await this.agent.tools.ozone.setting.removeOptions(
|
|
249
|
+
params,
|
|
250
|
+
{
|
|
251
|
+
encoding: 'application/json',
|
|
252
|
+
headers: await this.ozone.modHeaders(
|
|
253
|
+
'tools.ozone.setting.removeOptions',
|
|
254
|
+
callerRole,
|
|
255
|
+
),
|
|
256
|
+
},
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
return data
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
async computeStats() {
|
|
263
|
+
const db = this.ozone.ctx.db
|
|
264
|
+
const statsService = this.ozone.ctx.reportStatsService(db)
|
|
265
|
+
await statsService.materializeAll({ force: true })
|
|
266
|
+
}
|
|
267
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import getPort from 'get-port'
|
|
2
|
+
import type { SkeletonHandler } from '@atproto/pds'
|
|
3
|
+
import { TestFeedGen } from './feed-gen.js'
|
|
4
|
+
import { TestPds } from './pds.js'
|
|
5
|
+
import { TestPlc } from './plc.js'
|
|
6
|
+
import { SeedClient } from './seed/client.js'
|
|
7
|
+
import { LexiconAuthorityProfile } from './service-profile-lexicon.js'
|
|
8
|
+
import type { TestServerParams } from './types.js'
|
|
9
|
+
import { mockNetworkUtilities } from './util.js'
|
|
10
|
+
|
|
11
|
+
export class TestNetworkNoAppView {
|
|
12
|
+
feedGens: TestFeedGen[] = []
|
|
13
|
+
constructor(
|
|
14
|
+
public plc: TestPlc,
|
|
15
|
+
public pds: TestPds,
|
|
16
|
+
public extraPdses: TestPds[] = [],
|
|
17
|
+
public lexiconAuthority?: LexiconAuthorityProfile,
|
|
18
|
+
) {}
|
|
19
|
+
|
|
20
|
+
static async create(
|
|
21
|
+
params: Partial<TestServerParams> = {},
|
|
22
|
+
): Promise<TestNetworkNoAppView> {
|
|
23
|
+
const plc = await TestPlc.create(params.plc ?? {})
|
|
24
|
+
const pds = await TestPds.create({
|
|
25
|
+
didPlcUrl: plc.url,
|
|
26
|
+
...params.pds,
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
const extraPdsCount = params.extraPdses ?? 0
|
|
30
|
+
const extraPdses: TestPds[] = []
|
|
31
|
+
for (let i = 0; i < extraPdsCount; i++) {
|
|
32
|
+
// Extra PDSes get their own non-overlapping handle domain (.test2, .test3, ...)
|
|
33
|
+
// to avoid colliding with the primary PDS's .test.
|
|
34
|
+
const domain = `.test${i + 2}`
|
|
35
|
+
const extra = await TestPds.create({
|
|
36
|
+
didPlcUrl: plc.url,
|
|
37
|
+
...params.pds,
|
|
38
|
+
// Override after spreading so each extra PDS gets a unique port and
|
|
39
|
+
// its own handle domain (rather than inheriting the primary's).
|
|
40
|
+
port: await getPort(),
|
|
41
|
+
serviceHandleDomains: [domain],
|
|
42
|
+
})
|
|
43
|
+
extraPdses.push(extra)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
mockNetworkUtilities([pds, ...extraPdses])
|
|
47
|
+
|
|
48
|
+
let lexiconAuthority: LexiconAuthorityProfile | undefined
|
|
49
|
+
if (params.lexiconAuthority) {
|
|
50
|
+
// The authority account has to live on a PDS, but a PDS wants the authority
|
|
51
|
+
// DID in its config — so it's created after boot and the DID assigned here.
|
|
52
|
+
// The resolver hook reads `cfg.lexicon.didAuthority` per call rather than at
|
|
53
|
+
// construction (see pds `context.ts`), so assigning it now takes effect.
|
|
54
|
+
lexiconAuthority = await LexiconAuthorityProfile.create(pds)
|
|
55
|
+
for (const each of [pds, ...extraPdses]) {
|
|
56
|
+
each.ctx.cfg.lexicon.didAuthority = lexiconAuthority.did
|
|
57
|
+
}
|
|
58
|
+
await lexiconAuthority.createRecords()
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return new TestNetworkNoAppView(plc, pds, extraPdses, lexiconAuthority)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async createFeedGen(
|
|
65
|
+
feeds: Record<string, SkeletonHandler>,
|
|
66
|
+
): Promise<TestFeedGen> {
|
|
67
|
+
const fg = await TestFeedGen.create(this.plc.url, feeds)
|
|
68
|
+
this.feedGens.push(fg)
|
|
69
|
+
return fg
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
getSeedClient(): SeedClient<typeof this> {
|
|
73
|
+
const agent = this.pds.getAgent()
|
|
74
|
+
const client = this.pds.getClient()
|
|
75
|
+
return new SeedClient(this, agent, client)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async processAll() {
|
|
79
|
+
await this.pds.processAll()
|
|
80
|
+
await Promise.all(this.extraPdses.map((p) => p.processAll()))
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async close() {
|
|
84
|
+
await Promise.all(this.feedGens.map((fg) => fg.close()))
|
|
85
|
+
await this.pds.close()
|
|
86
|
+
await Promise.all(this.extraPdses.map((p) => p.close()))
|
|
87
|
+
await this.plc.close()
|
|
88
|
+
}
|
|
89
|
+
}
|
package/src/network.ts
ADDED
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import assert from 'node:assert'
|
|
2
|
+
import getPort from 'get-port'
|
|
3
|
+
import * as uint8arrays from 'uint8arrays'
|
|
4
|
+
import { allFulfilled, wait } from '@atproto/common-web'
|
|
5
|
+
import { createServiceJwt } from '@atproto/xrpc-server'
|
|
6
|
+
import { TestBsky } from './bsky.js'
|
|
7
|
+
import { TestBsync } from './bsync.js'
|
|
8
|
+
import { EXAMPLE_LABELER } from './const.js'
|
|
9
|
+
import { IntrospectServer } from './introspect.js'
|
|
10
|
+
import { TestNetworkNoAppView } from './network-no-appview.js'
|
|
11
|
+
import { TestOzone } from './ozone.js'
|
|
12
|
+
import { TestPds } from './pds.js'
|
|
13
|
+
import { TestPlc } from './plc.js'
|
|
14
|
+
import { LexiconAuthorityProfile } from './service-profile-lexicon.js'
|
|
15
|
+
import { OzoneServiceProfile } from './service-profile-ozone.js'
|
|
16
|
+
import type { TestServerParams } from './types.js'
|
|
17
|
+
import { mockNetworkUtilities } from './util.js'
|
|
18
|
+
|
|
19
|
+
const ADMIN_USERNAME = 'admin'
|
|
20
|
+
const ADMIN_PASSWORD = 'admin-pass'
|
|
21
|
+
|
|
22
|
+
export class TestNetwork extends TestNetworkNoAppView {
|
|
23
|
+
constructor(
|
|
24
|
+
public plc: TestPlc,
|
|
25
|
+
public pds: TestPds,
|
|
26
|
+
public bsync: TestBsync,
|
|
27
|
+
public bsky: TestBsky,
|
|
28
|
+
public ozone: TestOzone,
|
|
29
|
+
public introspect?: IntrospectServer,
|
|
30
|
+
) {
|
|
31
|
+
super(plc, pds)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
static async create(
|
|
35
|
+
params: Partial<TestServerParams> = {},
|
|
36
|
+
): Promise<TestNetwork> {
|
|
37
|
+
const redisHost = process.env.REDIS_HOST
|
|
38
|
+
const dbPostgresUrl = params.dbPostgresUrl || process.env.DB_POSTGRES_URL
|
|
39
|
+
assert(dbPostgresUrl, 'Missing postgres url for tests')
|
|
40
|
+
assert(redisHost, 'Missing redis host for tests')
|
|
41
|
+
const dbPostgresSchema =
|
|
42
|
+
params.dbPostgresSchema || process.env.DB_POSTGRES_SCHEMA
|
|
43
|
+
|
|
44
|
+
const plc = await TestPlc.create(params.plc ?? {})
|
|
45
|
+
|
|
46
|
+
const bskyPort = params.bsky?.port ?? (await getPort())
|
|
47
|
+
const pdsPort = params.pds?.port ?? (await getPort())
|
|
48
|
+
const ozonePort = params.ozone?.port ?? (await getPort())
|
|
49
|
+
|
|
50
|
+
const thirdPartyPds = await TestPds.create({
|
|
51
|
+
didPlcUrl: plc.url,
|
|
52
|
+
...params.pds,
|
|
53
|
+
inviteRequired: false,
|
|
54
|
+
port: await getPort(),
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
const ozoneUrl = `http://localhost:${ozonePort}`
|
|
58
|
+
|
|
59
|
+
// @TODO (?) rework the ServiceProfile to live on a separate PDS instead of
|
|
60
|
+
// requiring to migrate to the main PDS
|
|
61
|
+
const ozoneServiceProfile = await OzoneServiceProfile.create(
|
|
62
|
+
thirdPartyPds,
|
|
63
|
+
ozoneUrl,
|
|
64
|
+
)
|
|
65
|
+
const lexiconAuthorityProfile =
|
|
66
|
+
await LexiconAuthorityProfile.create(thirdPartyPds)
|
|
67
|
+
|
|
68
|
+
const bsyncApiKey = 'bsync-api-key'
|
|
69
|
+
const bsync = await TestBsync.create({
|
|
70
|
+
apiKeys: [bsyncApiKey],
|
|
71
|
+
dbUrl: dbPostgresUrl,
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
const bsky = await TestBsky.create({
|
|
75
|
+
port: bskyPort,
|
|
76
|
+
plcUrl: plc.url,
|
|
77
|
+
pdsPort,
|
|
78
|
+
bsyncApiKey,
|
|
79
|
+
bsyncUrl: bsync.url,
|
|
80
|
+
rolodexUrl: process.env.BSKY_ROLODEX_URL,
|
|
81
|
+
rolodexIgnoreBadTls: true,
|
|
82
|
+
repoProvider: `ws://localhost:${pdsPort}`,
|
|
83
|
+
dbPostgresSchema: `appview_${dbPostgresSchema}`,
|
|
84
|
+
dbPostgresUrl,
|
|
85
|
+
redisHost,
|
|
86
|
+
modServiceDid: ozoneServiceProfile.did,
|
|
87
|
+
labelsFromIssuerDids: [ozoneServiceProfile.did, EXAMPLE_LABELER],
|
|
88
|
+
// Using a static private key results in a static DID, which is useful for e2e tests with the social-app repo.
|
|
89
|
+
privateKey:
|
|
90
|
+
'3f916c70dc69e4c5e83877f013325b11ecac31742e6a42f5c4fb240d0703d9d5=',
|
|
91
|
+
...params.bsky,
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
const pds = await TestPds.create({
|
|
95
|
+
port: pdsPort,
|
|
96
|
+
didPlcUrl: plc.url,
|
|
97
|
+
bskyAppViewUrl: bsky.url,
|
|
98
|
+
bskyAppViewDid: bsky.ctx.cfg.serverDid,
|
|
99
|
+
modServiceUrl: ozoneUrl,
|
|
100
|
+
modServiceDid: ozoneServiceProfile.did,
|
|
101
|
+
lexiconDidAuthority: lexiconAuthorityProfile.did,
|
|
102
|
+
...params.pds,
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
// mock before any events start flowing from pds so that we don't miss e.g. any handle resolutions.
|
|
106
|
+
mockNetworkUtilities(pds, bsky)
|
|
107
|
+
|
|
108
|
+
const ozone = await TestOzone.create({
|
|
109
|
+
port: ozonePort,
|
|
110
|
+
plcUrl: plc.url,
|
|
111
|
+
signingKey: ozoneServiceProfile.key,
|
|
112
|
+
serverDid: ozoneServiceProfile.did,
|
|
113
|
+
dbPostgresSchema: `ozone_${dbPostgresSchema || 'db'}`,
|
|
114
|
+
dbPostgresUrl,
|
|
115
|
+
appviewUrl: bsky.url,
|
|
116
|
+
appviewDid: bsky.ctx.cfg.serverDid,
|
|
117
|
+
appviewPushEvents: true,
|
|
118
|
+
pdsUrl: pds.url,
|
|
119
|
+
pdsDid: pds.ctx.cfg.service.did,
|
|
120
|
+
verifierDid: ozoneServiceProfile.did,
|
|
121
|
+
verifierUrl: pds.url,
|
|
122
|
+
verifierPassword: 'temp',
|
|
123
|
+
...params.ozone,
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
await ozoneServiceProfile.migrateTo(pds)
|
|
127
|
+
await ozoneServiceProfile.createRecords()
|
|
128
|
+
|
|
129
|
+
await lexiconAuthorityProfile.migrateTo(pds)
|
|
130
|
+
await lexiconAuthorityProfile.createRecords()
|
|
131
|
+
|
|
132
|
+
await ozone.addAdminDid(ozoneServiceProfile.did)
|
|
133
|
+
await ozone.createPolicies()
|
|
134
|
+
|
|
135
|
+
await thirdPartyPds.processAll()
|
|
136
|
+
await pds.processAll()
|
|
137
|
+
await ozone.processAll()
|
|
138
|
+
await bsky.sub.processAll()
|
|
139
|
+
await bsky.bsyncSub.processAll(await bsync.getStreamHeads())
|
|
140
|
+
await thirdPartyPds.close()
|
|
141
|
+
|
|
142
|
+
// Weird but if we do this before pds.processAll() somehow appview loses this user and tests in different parts fail because appview doesn't return this user in various contexts anymore
|
|
143
|
+
const ozoneVerifierPassword =
|
|
144
|
+
await ozoneServiceProfile.createAppPasswordForVerification()
|
|
145
|
+
if (ozone.daemon.ctx.cfg.verifier) {
|
|
146
|
+
ozone.daemon.ctx.cfg.verifier.password = ozoneVerifierPassword
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
let introspect: IntrospectServer | undefined = undefined
|
|
150
|
+
if (params.introspect?.port) {
|
|
151
|
+
introspect = await IntrospectServer.start(
|
|
152
|
+
params.introspect.port,
|
|
153
|
+
plc,
|
|
154
|
+
pds,
|
|
155
|
+
bsync,
|
|
156
|
+
bsky,
|
|
157
|
+
ozone,
|
|
158
|
+
)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return new TestNetwork(plc, pds, bsync, bsky, ozone, introspect)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async processFullSubscription(timeout = 5000) {
|
|
165
|
+
const sub = this.bsky.sub
|
|
166
|
+
// If the subscription is not running, there is no point in
|
|
167
|
+
// waiting for it to process events
|
|
168
|
+
if (!sub.running) return
|
|
169
|
+
const start = Date.now()
|
|
170
|
+
const lastSeq = await this.pds.ctx.sequencer.curr()
|
|
171
|
+
if (!lastSeq) return
|
|
172
|
+
while (Date.now() - start < timeout) {
|
|
173
|
+
await sub.processAll()
|
|
174
|
+
const runnerCursor = await sub.getCursor()
|
|
175
|
+
// if subscription claims to be done, ensure we are at the most recent cursor from PDS, else wait to process again
|
|
176
|
+
// (the subscription may claim to be finished before the PDS has even emitted it's event)
|
|
177
|
+
if (runnerCursor && runnerCursor >= lastSeq) {
|
|
178
|
+
return
|
|
179
|
+
}
|
|
180
|
+
await wait(5)
|
|
181
|
+
}
|
|
182
|
+
throw new Error(`Sequence was not processed within ${timeout}ms`)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async processAll(timeout?: number) {
|
|
186
|
+
await this.pds.processAll()
|
|
187
|
+
await this.ozone.processAll()
|
|
188
|
+
await this.bsky.sub.processAll()
|
|
189
|
+
await this.bsky.bsyncSub.processAll(
|
|
190
|
+
await this.bsync.getStreamHeads(),
|
|
191
|
+
timeout,
|
|
192
|
+
)
|
|
193
|
+
await this.processFullSubscription(timeout)
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
async serviceHeaders(did: string, lxm: string, aud?: string) {
|
|
197
|
+
const keypair = await this.pds.ctx.actorStore.keypair(did)
|
|
198
|
+
const jwt = await createServiceJwt({
|
|
199
|
+
iss: did,
|
|
200
|
+
aud: aud ?? this.bsky.ctx.cfg.serverDid,
|
|
201
|
+
lxm,
|
|
202
|
+
keypair,
|
|
203
|
+
})
|
|
204
|
+
return { authorization: `Bearer ${jwt}` }
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
async adminHeaders({
|
|
208
|
+
username = ADMIN_USERNAME,
|
|
209
|
+
password = ADMIN_PASSWORD,
|
|
210
|
+
}: {
|
|
211
|
+
username?: string
|
|
212
|
+
password?: string
|
|
213
|
+
}) {
|
|
214
|
+
return {
|
|
215
|
+
authorization:
|
|
216
|
+
'Basic ' +
|
|
217
|
+
uint8arrays.toString(
|
|
218
|
+
uint8arrays.fromString(`${username}:${password}`, 'utf8'),
|
|
219
|
+
'base64pad',
|
|
220
|
+
),
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
async close() {
|
|
225
|
+
try {
|
|
226
|
+
await this.processAll()
|
|
227
|
+
} finally {
|
|
228
|
+
await allFulfilled([
|
|
229
|
+
...this.feedGens.map(async (fg) => fg.close()),
|
|
230
|
+
this.ozone.close(),
|
|
231
|
+
this.bsky.close(),
|
|
232
|
+
this.bsync.close(),
|
|
233
|
+
this.pds.close(),
|
|
234
|
+
this.plc.close(),
|
|
235
|
+
this.introspect?.close(),
|
|
236
|
+
])
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
async [Symbol.asyncDispose]() {
|
|
241
|
+
await this.close()
|
|
242
|
+
}
|
|
243
|
+
}
|