@atproto/pds 0.5.12 → 0.5.15

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.
Files changed (326) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/dist/api/app/bsky/notification/index.d.ts.map +1 -1
  3. package/dist/api/app/bsky/notification/index.js +2 -0
  4. package/dist/api/app/bsky/notification/index.js.map +1 -1
  5. package/dist/api/app/bsky/notification/unregisterPush.d.ts +4 -0
  6. package/dist/api/app/bsky/notification/unregisterPush.d.ts.map +1 -0
  7. package/dist/api/app/bsky/notification/unregisterPush.js +48 -0
  8. package/dist/api/app/bsky/notification/unregisterPush.js.map +1 -0
  9. package/dist/lexicons/app/bsky/actor/defs.defs.d.ts +6 -0
  10. package/dist/lexicons/app/bsky/actor/defs.defs.d.ts.map +1 -1
  11. package/dist/lexicons/app/bsky/actor/defs.defs.js +1 -0
  12. package/dist/lexicons/app/bsky/actor/defs.defs.js.map +1 -1
  13. package/package.json +42 -38
  14. package/build.templates.cjs +0 -22
  15. package/example.env +0 -42
  16. package/jest.config.cjs +0 -27
  17. package/src/account-manager/account-manager.ts +0 -916
  18. package/src/account-manager/db/index.ts +0 -21
  19. package/src/account-manager/db/migrations/001-init.ts +0 -115
  20. package/src/account-manager/db/migrations/002-account-deactivation.ts +0 -17
  21. package/src/account-manager/db/migrations/003-privileged-app-passwords.ts +0 -12
  22. package/src/account-manager/db/migrations/004-oauth.ts +0 -122
  23. package/src/account-manager/db/migrations/005-oauth-account-management.ts +0 -136
  24. package/src/account-manager/db/migrations/006-oauth-permission-sets.ts +0 -20
  25. package/src/account-manager/db/migrations/007-lexicon-failures-index.ts +0 -14
  26. package/src/account-manager/db/migrations/index.ts +0 -17
  27. package/src/account-manager/db/schema/account-device.ts +0 -14
  28. package/src/account-manager/db/schema/account.ts +0 -16
  29. package/src/account-manager/db/schema/actor.ts +0 -17
  30. package/src/account-manager/db/schema/app-password.ts +0 -13
  31. package/src/account-manager/db/schema/authorization-request.ts +0 -30
  32. package/src/account-manager/db/schema/authorized-client.ts +0 -23
  33. package/src/account-manager/db/schema/device.ts +0 -18
  34. package/src/account-manager/db/schema/email-token.ts +0 -19
  35. package/src/account-manager/db/schema/index.ts +0 -43
  36. package/src/account-manager/db/schema/invite-code.ts +0 -24
  37. package/src/account-manager/db/schema/lexicon.ts +0 -15
  38. package/src/account-manager/db/schema/refresh-token.ts +0 -11
  39. package/src/account-manager/db/schema/repo-root.ts +0 -12
  40. package/src/account-manager/db/schema/token.ts +0 -38
  41. package/src/account-manager/db/schema/used-refresh-token.ts +0 -13
  42. package/src/account-manager/helpers/account-device.ts +0 -63
  43. package/src/account-manager/helpers/account.ts +0 -355
  44. package/src/account-manager/helpers/auth.ts +0 -222
  45. package/src/account-manager/helpers/authorization-request.ts +0 -90
  46. package/src/account-manager/helpers/authorized-client.ts +0 -75
  47. package/src/account-manager/helpers/device.ts +0 -47
  48. package/src/account-manager/helpers/email-token.ts +0 -87
  49. package/src/account-manager/helpers/invite.ts +0 -263
  50. package/src/account-manager/helpers/lexicon.ts +0 -67
  51. package/src/account-manager/helpers/password.ts +0 -136
  52. package/src/account-manager/helpers/repo.ts +0 -24
  53. package/src/account-manager/helpers/scrypt.ts +0 -53
  54. package/src/account-manager/helpers/token.ts +0 -158
  55. package/src/account-manager/helpers/used-refresh-token.ts +0 -30
  56. package/src/account-manager/oauth-store.ts +0 -880
  57. package/src/account-manager/scope-reference-getter.ts +0 -106
  58. package/src/actor-store/actor-store-reader.ts +0 -45
  59. package/src/actor-store/actor-store-resources.ts +0 -8
  60. package/src/actor-store/actor-store-transactor.ts +0 -31
  61. package/src/actor-store/actor-store-writer.ts +0 -17
  62. package/src/actor-store/actor-store.ts +0 -210
  63. package/src/actor-store/blob/reader.ts +0 -160
  64. package/src/actor-store/blob/transactor.ts +0 -383
  65. package/src/actor-store/db/index.ts +0 -20
  66. package/src/actor-store/db/migrations/001-init.ts +0 -105
  67. package/src/actor-store/db/migrations/index.ts +0 -5
  68. package/src/actor-store/db/schema/account-pref.ts +0 -11
  69. package/src/actor-store/db/schema/backlink.ts +0 -9
  70. package/src/actor-store/db/schema/blob.ts +0 -14
  71. package/src/actor-store/db/schema/index.ts +0 -23
  72. package/src/actor-store/db/schema/record-blob.ts +0 -8
  73. package/src/actor-store/db/schema/record.ts +0 -14
  74. package/src/actor-store/db/schema/repo-block.ts +0 -10
  75. package/src/actor-store/db/schema/repo-root.ts +0 -10
  76. package/src/actor-store/migrate.ts +0 -39
  77. package/src/actor-store/preference/reader.ts +0 -48
  78. package/src/actor-store/preference/transactor.ts +0 -55
  79. package/src/actor-store/preference/util.ts +0 -39
  80. package/src/actor-store/record/reader.ts +0 -374
  81. package/src/actor-store/record/transactor.ts +0 -111
  82. package/src/actor-store/repo/reader.ts +0 -31
  83. package/src/actor-store/repo/sql-repo-reader.ts +0 -150
  84. package/src/actor-store/repo/sql-repo-transactor.ts +0 -105
  85. package/src/actor-store/repo/transactor.ts +0 -227
  86. package/src/api/app/bsky/actor/getPreferences.ts +0 -57
  87. package/src/api/app/bsky/actor/getProfile.ts +0 -41
  88. package/src/api/app/bsky/actor/getProfiles.ts +0 -51
  89. package/src/api/app/bsky/actor/index.ts +0 -13
  90. package/src/api/app/bsky/actor/putPreferences.ts +0 -62
  91. package/src/api/app/bsky/feed/getActorLikes.ts +0 -63
  92. package/src/api/app/bsky/feed/getAuthorFeed.ts +0 -84
  93. package/src/api/app/bsky/feed/getFeed.ts +0 -47
  94. package/src/api/app/bsky/feed/getPostThread.ts +0 -251
  95. package/src/api/app/bsky/feed/getTimeline.ts +0 -50
  96. package/src/api/app/bsky/feed/index.ts +0 -15
  97. package/src/api/app/bsky/index.ts +0 -11
  98. package/src/api/app/bsky/notification/index.ts +0 -7
  99. package/src/api/app/bsky/notification/registerPush.ts +0 -71
  100. package/src/api/app/bsky/util/resolver.ts +0 -20
  101. package/src/api/com/atproto/admin/deleteAccount.ts +0 -22
  102. package/src/api/com/atproto/admin/disableAccountInvites.ts +0 -18
  103. package/src/api/com/atproto/admin/disableInviteCodes.ts +0 -21
  104. package/src/api/com/atproto/admin/enableAccountInvites.ts +0 -18
  105. package/src/api/com/atproto/admin/getAccountInfo.ts +0 -32
  106. package/src/api/com/atproto/admin/getAccountInfos.ts +0 -34
  107. package/src/api/com/atproto/admin/getInviteCodes.ts +0 -126
  108. package/src/api/com/atproto/admin/getSubjectStatus.ts +0 -76
  109. package/src/api/com/atproto/admin/index.ts +0 -31
  110. package/src/api/com/atproto/admin/sendEmail.ts +0 -47
  111. package/src/api/com/atproto/admin/updateAccountEmail.ts +0 -32
  112. package/src/api/com/atproto/admin/updateAccountHandle.ts +0 -47
  113. package/src/api/com/atproto/admin/updateAccountPassword.ts +0 -34
  114. package/src/api/com/atproto/admin/updateSubjectStatus.ts +0 -68
  115. package/src/api/com/atproto/admin/util.ts +0 -66
  116. package/src/api/com/atproto/identity/getRecommendedDidCredentials.ts +0 -50
  117. package/src/api/com/atproto/identity/index.ts +0 -17
  118. package/src/api/com/atproto/identity/requestPlcOperationSignature.ts +0 -59
  119. package/src/api/com/atproto/identity/resolveHandle.ts +0 -50
  120. package/src/api/com/atproto/identity/signPlcOperation.ts +0 -85
  121. package/src/api/com/atproto/identity/submitPlcOperation.ts +0 -65
  122. package/src/api/com/atproto/identity/updateHandle.ts +0 -66
  123. package/src/api/com/atproto/index.ts +0 -19
  124. package/src/api/com/atproto/moderation/createReport.ts +0 -41
  125. package/src/api/com/atproto/moderation/index.ts +0 -7
  126. package/src/api/com/atproto/repo/applyWrites.ts +0 -226
  127. package/src/api/com/atproto/repo/createRecord.ts +0 -143
  128. package/src/api/com/atproto/repo/deleteRecord.ts +0 -122
  129. package/src/api/com/atproto/repo/describeRepo.ts +0 -43
  130. package/src/api/com/atproto/repo/getRecord.ts +0 -40
  131. package/src/api/com/atproto/repo/importRepo.ts +0 -101
  132. package/src/api/com/atproto/repo/index.ts +0 -25
  133. package/src/api/com/atproto/repo/listMissingBlobs.ts +0 -29
  134. package/src/api/com/atproto/repo/listRecords.ts +0 -36
  135. package/src/api/com/atproto/repo/putRecord.ts +0 -211
  136. package/src/api/com/atproto/repo/uploadBlob.ts +0 -60
  137. package/src/api/com/atproto/server/activateAccount.ts +0 -37
  138. package/src/api/com/atproto/server/checkAccountStatus.ts +0 -51
  139. package/src/api/com/atproto/server/confirmEmail.ts +0 -39
  140. package/src/api/com/atproto/server/createAccount.ts +0 -327
  141. package/src/api/com/atproto/server/createAppPassword.ts +0 -53
  142. package/src/api/com/atproto/server/createInviteCode.ts +0 -38
  143. package/src/api/com/atproto/server/createInviteCodes.ts +0 -42
  144. package/src/api/com/atproto/server/createSession.ts +0 -97
  145. package/src/api/com/atproto/server/deactivateAccount.ts +0 -41
  146. package/src/api/com/atproto/server/deleteAccount.ts +0 -85
  147. package/src/api/com/atproto/server/deleteSession.ts +0 -23
  148. package/src/api/com/atproto/server/describeServer.ts +0 -29
  149. package/src/api/com/atproto/server/getAccountInviteCodes.ts +0 -142
  150. package/src/api/com/atproto/server/getServiceAuth.ts +0 -111
  151. package/src/api/com/atproto/server/getSession.ts +0 -80
  152. package/src/api/com/atproto/server/index.ts +0 -55
  153. package/src/api/com/atproto/server/listAppPasswords.ts +0 -45
  154. package/src/api/com/atproto/server/refreshSession.ts +0 -72
  155. package/src/api/com/atproto/server/requestAccountDelete.ts +0 -66
  156. package/src/api/com/atproto/server/requestEmailConfirmation.ts +0 -68
  157. package/src/api/com/atproto/server/requestEmailUpdate.ts +0 -86
  158. package/src/api/com/atproto/server/requestPasswordReset.ts +0 -52
  159. package/src/api/com/atproto/server/reserveSigningKey.ts +0 -15
  160. package/src/api/com/atproto/server/resetPassword.ts +0 -50
  161. package/src/api/com/atproto/server/revokeAppPassword.ts +0 -42
  162. package/src/api/com/atproto/server/updateEmail.ts +0 -52
  163. package/src/api/com/atproto/server/util.ts +0 -136
  164. package/src/api/com/atproto/sync/deprecated/getCheckout.ts +0 -27
  165. package/src/api/com/atproto/sync/deprecated/getHead.ts +0 -33
  166. package/src/api/com/atproto/sync/getBlob.ts +0 -61
  167. package/src/api/com/atproto/sync/getBlocks.ts +0 -37
  168. package/src/api/com/atproto/sync/getLatestCommit.ts +0 -33
  169. package/src/api/com/atproto/sync/getRecord.ts +0 -49
  170. package/src/api/com/atproto/sync/getRepo.ts +0 -75
  171. package/src/api/com/atproto/sync/getRepoStatus.ts +0 -34
  172. package/src/api/com/atproto/sync/index.ts +0 -27
  173. package/src/api/com/atproto/sync/listBlobs.ts +0 -33
  174. package/src/api/com/atproto/sync/listRepos.ts +0 -74
  175. package/src/api/com/atproto/sync/subscribeRepos.ts +0 -73
  176. package/src/api/com/atproto/sync/util.ts +0 -37
  177. package/src/api/com/atproto/temp/checkSignupQueue.ts +0 -34
  178. package/src/api/com/atproto/temp/index.ts +0 -7
  179. package/src/api/index.ts +0 -10
  180. package/src/api/proxy.ts +0 -44
  181. package/src/app-view.ts +0 -24
  182. package/src/auth-output.ts +0 -52
  183. package/src/auth-routes.ts +0 -64
  184. package/src/auth-scope.ts +0 -40
  185. package/src/auth-verifier.ts +0 -668
  186. package/src/background.ts +0 -65
  187. package/src/basic-routes.ts +0 -52
  188. package/src/bsky-app-view.ts +0 -33
  189. package/src/config/config.ts +0 -553
  190. package/src/config/env.ts +0 -167
  191. package/src/config/index.ts +0 -3
  192. package/src/config/secrets.ts +0 -54
  193. package/src/context.ts +0 -523
  194. package/src/crawlers.ts +0 -46
  195. package/src/db/cast.ts +0 -52
  196. package/src/db/db.ts +0 -140
  197. package/src/db/index.ts +0 -4
  198. package/src/db/migrator.ts +0 -40
  199. package/src/db/pagination.ts +0 -132
  200. package/src/db/tables/moderation.ts +0 -80
  201. package/src/db/util.ts +0 -108
  202. package/src/did-cache/db/index.ts +0 -21
  203. package/src/did-cache/db/migrations.ts +0 -17
  204. package/src/did-cache/db/schema.ts +0 -9
  205. package/src/did-cache/index.ts +0 -131
  206. package/src/disk-blobstore.ts +0 -172
  207. package/src/error.ts +0 -19
  208. package/src/handle/explicit-slurs.ts +0 -22
  209. package/src/handle/index.ts +0 -49
  210. package/src/handle/reserved.ts +0 -1059
  211. package/src/image/image-url-builder.ts +0 -16
  212. package/src/index.ts +0 -189
  213. package/src/lexicons.ts +0 -4
  214. package/src/logger.ts +0 -35
  215. package/src/mailer/index.ts +0 -111
  216. package/src/mailer/moderation.ts +0 -33
  217. package/src/mailer/templates/confirm-email.d.ts +0 -4
  218. package/src/mailer/templates/confirm-email.hbs +0 -158
  219. package/src/mailer/templates/delete-account.d.ts +0 -4
  220. package/src/mailer/templates/delete-account.hbs +0 -156
  221. package/src/mailer/templates/plc-operation.d.ts +0 -4
  222. package/src/mailer/templates/plc-operation.hbs +0 -153
  223. package/src/mailer/templates/reset-password.d.ts +0 -4
  224. package/src/mailer/templates/reset-password.hbs +0 -154
  225. package/src/mailer/templates/update-email.d.ts +0 -4
  226. package/src/mailer/templates/update-email.hbs +0 -153
  227. package/src/mailer/templates.ts +0 -17
  228. package/src/pipethrough.ts +0 -716
  229. package/src/rate-limits.ts +0 -59
  230. package/src/read-after-write/index.ts +0 -3
  231. package/src/read-after-write/types.ts +0 -35
  232. package/src/read-after-write/util.ts +0 -101
  233. package/src/read-after-write/viewer.ts +0 -290
  234. package/src/redis.ts +0 -25
  235. package/src/repo/index.ts +0 -2
  236. package/src/repo/prepare.ts +0 -266
  237. package/src/repo/types.ts +0 -65
  238. package/src/scripts/README.md +0 -40
  239. package/src/scripts/index.ts +0 -20
  240. package/src/scripts/publish-identity.ts +0 -60
  241. package/src/scripts/rebuild-repo.ts +0 -119
  242. package/src/scripts/rotate-keys.ts +0 -146
  243. package/src/scripts/sequencer-recovery/index.ts +0 -23
  244. package/src/scripts/sequencer-recovery/recoverer.ts +0 -297
  245. package/src/scripts/sequencer-recovery/recovery-db.ts +0 -65
  246. package/src/scripts/sequencer-recovery/repair-repos.ts +0 -49
  247. package/src/scripts/sequencer-recovery/user-queues.ts +0 -44
  248. package/src/scripts/util.ts +0 -7
  249. package/src/sequencer/db/index.ts +0 -21
  250. package/src/sequencer/db/migrations/001-init.ts +0 -35
  251. package/src/sequencer/db/migrations/index.ts +0 -5
  252. package/src/sequencer/db/schema.ts +0 -19
  253. package/src/sequencer/events.ts +0 -199
  254. package/src/sequencer/index.ts +0 -3
  255. package/src/sequencer/outbox.ts +0 -123
  256. package/src/sequencer/sequencer.ts +0 -290
  257. package/src/util/compression.ts +0 -16
  258. package/src/util/debug.ts +0 -10
  259. package/src/util/http.ts +0 -31
  260. package/src/util/types.ts +0 -7
  261. package/src/well-known.ts +0 -30
  262. package/test.env +0 -2
  263. package/tests/__snapshots__/takedown-appeal.test.ts.snap +0 -43
  264. package/tests/_oauth_client_assets_middleware.ts +0 -23
  265. package/tests/_puppeteer.ts +0 -147
  266. package/tests/_types.d.ts +0 -16
  267. package/tests/_util.ts +0 -191
  268. package/tests/account-deactivation.test.ts +0 -204
  269. package/tests/account-deletion.test.ts +0 -300
  270. package/tests/account-manager.test.ts +0 -462
  271. package/tests/account-migration.test.ts +0 -221
  272. package/tests/account-status.test.ts +0 -206
  273. package/tests/account.test.ts +0 -595
  274. package/tests/app-passwords.test.ts +0 -262
  275. package/tests/auth.test.ts +0 -405
  276. package/tests/blob-deletes.test.ts +0 -204
  277. package/tests/create-post.test.ts +0 -79
  278. package/tests/crud.test.ts +0 -1595
  279. package/tests/db.test.ts +0 -170
  280. package/tests/email-confirmation.test.ts +0 -227
  281. package/tests/entryway-mock.ts +0 -323
  282. package/tests/entryway.test.ts +0 -145
  283. package/tests/file-uploads.test.ts +0 -301
  284. package/tests/get-service-auth.test.ts +0 -81
  285. package/tests/handle-validation.test.ts +0 -56
  286. package/tests/handles.test.ts +0 -274
  287. package/tests/invite-codes.test.ts +0 -367
  288. package/tests/invites-admin.test.ts +0 -252
  289. package/tests/moderation.test.ts +0 -280
  290. package/tests/moderator-auth.test.ts +0 -177
  291. package/tests/oauth.test.ts +0 -334
  292. package/tests/plc-operations.test.ts +0 -239
  293. package/tests/preferences.test.ts +0 -352
  294. package/tests/proxied/__snapshots__/admin.test.ts.snap +0 -516
  295. package/tests/proxied/__snapshots__/feedgen.test.ts.snap +0 -215
  296. package/tests/proxied/__snapshots__/views.test.ts.snap +0 -4456
  297. package/tests/proxied/admin.test.ts +0 -340
  298. package/tests/proxied/feedgen.test.ts +0 -66
  299. package/tests/proxied/notif.test.ts +0 -85
  300. package/tests/proxied/procedures.test.ts +0 -175
  301. package/tests/proxied/proxy-catchall.test.ts +0 -256
  302. package/tests/proxied/proxy-header.test.ts +0 -239
  303. package/tests/proxied/proxy-oauth-aud.test.ts +0 -182
  304. package/tests/proxied/read-after-write.test.ts +0 -382
  305. package/tests/proxied/views.test.ts +0 -608
  306. package/tests/races.test.ts +0 -89
  307. package/tests/rate-limits.test.ts +0 -70
  308. package/tests/recovery.test.ts +0 -180
  309. package/tests/seeds/basic.ts +0 -165
  310. package/tests/seeds/follows.ts +0 -57
  311. package/tests/seeds/likes.ts +0 -14
  312. package/tests/seeds/reposts.ts +0 -18
  313. package/tests/seeds/thread.ts +0 -40
  314. package/tests/seeds/users-bulk.ts +0 -246
  315. package/tests/seeds/users.ts +0 -67
  316. package/tests/sequencer.test.ts +0 -251
  317. package/tests/server.test.ts +0 -151
  318. package/tests/sync/invertible-ops.test.ts +0 -99
  319. package/tests/sync/list.test.ts +0 -43
  320. package/tests/sync/subscribe-repos.test.ts +0 -672
  321. package/tests/sync/sync.test.ts +0 -316
  322. package/tests/takedown-appeal.test.ts +0 -166
  323. package/tsconfig.build.json +0 -9
  324. package/tsconfig.build.tsbuildinfo +0 -1
  325. package/tsconfig.json +0 -7
  326. package/tsconfig.tests.json +0 -8
@@ -1,1595 +0,0 @@
1
- import assert from 'node:assert'
2
- import fs from 'node:fs/promises'
3
- import {
4
- AppBskyEmbedImages,
5
- AppBskyFeedPost,
6
- AppBskyFeedPostRecord,
7
- AtpAgent,
8
- BlobRef,
9
- } from '@atproto/api'
10
- import { TID, cidForCbor, ui8ToArrayBuffer } from '@atproto/common'
11
- import { TestNetworkNoAppView } from '@atproto/dev-env'
12
- import { BlobNotFoundError } from '@atproto/repo'
13
- import { AtUri } from '@atproto/syntax'
14
- import { AppContext } from '../src/context.js'
15
- import { forSnapshot, paginateAll } from './_util.js'
16
-
17
- describe('crud operations', () => {
18
- let network: TestNetworkNoAppView
19
- let ctx: AppContext
20
- let agent: AtpAgent
21
- let aliceAgent: AtpAgent
22
- let bobAgent: AtpAgent
23
-
24
- beforeAll(async () => {
25
- network = await TestNetworkNoAppView.create({
26
- dbPostgresSchema: 'crud',
27
- })
28
- // @ts-expect-error Error due to circular dependency with the dev-env package
29
- ctx = network.pds.ctx
30
- agent = network.pds.getAgent()
31
- aliceAgent = network.pds.getAgent()
32
- bobAgent = network.pds.getAgent()
33
-
34
- await aliceAgent.createAccount({
35
- email: 'alice@test.com',
36
- handle: 'alice.test',
37
- password: 'alice-pass',
38
- })
39
-
40
- await bobAgent.createAccount({
41
- email: 'bob@test.com',
42
- handle: 'bob.test',
43
- password: 'bob-pass',
44
- })
45
-
46
- expect(bobAgent.assertDid).not.toBe(aliceAgent.assertDid)
47
- })
48
-
49
- afterAll(async () => {
50
- await network?.close()
51
- })
52
-
53
- it('registers users', async () => {
54
- const res = await agent.com.atproto.server.createAccount({
55
- handle: 'user1.test',
56
- email: 'user1@test.com',
57
- password: 'password',
58
- })
59
- expect(res.data.handle).toBe('user1.test')
60
- expect(res.data.accessJwt).toBeDefined()
61
- })
62
-
63
- it('describes repo', async () => {
64
- const description = await agent.com.atproto.repo.describeRepo({
65
- repo: aliceAgent.assertDid,
66
- })
67
- expect(description.data.handle).toBe('alice.test')
68
- expect(description.data.did).toBe(aliceAgent.assertDid)
69
- const description2 = await agent.com.atproto.repo.describeRepo({
70
- repo: bobAgent.assertDid,
71
- })
72
- expect(description2.data.handle).toBe('bob.test')
73
- expect(description2.data.did).toBe(bobAgent.assertDid)
74
- })
75
-
76
- it('creates records', async () => {
77
- const res = await aliceAgent.com.atproto.repo.createRecord({
78
- repo: aliceAgent.assertDid,
79
- collection: 'app.bsky.feed.post',
80
- record: {
81
- $type: 'app.bsky.feed.post',
82
- text: 'Hello, world!',
83
- createdAt: new Date().toISOString(),
84
- },
85
- })
86
- const uri = new AtUri(res.data.uri)
87
- expect(res.data.uri).toBe(
88
- `at://${aliceAgent.assertDid}/app.bsky.feed.post/${uri.rkey}`,
89
- )
90
-
91
- const res1 = await agent.com.atproto.repo.listRecords({
92
- repo: aliceAgent.assertDid,
93
- collection: 'app.bsky.feed.post',
94
- })
95
- expect(res1.data.records.length).toBe(1)
96
- expect(res1.data.records[0].uri).toBe(uri.toString())
97
- const { value } = res1.data.records[0]
98
- assert(AppBskyFeedPost.isMain(value))
99
- expect(value.text).toBe('Hello, world!')
100
-
101
- const res2 = await agent.app.bsky.feed.post.list({
102
- repo: aliceAgent.assertDid,
103
- })
104
- expect(res2.records.length).toBe(1)
105
- expect(res2.records[0].uri).toBe(uri.toString())
106
- expect(res2.records[0].value.text).toBe('Hello, world!')
107
-
108
- const res3 = await agent.com.atproto.repo.getRecord({
109
- repo: aliceAgent.assertDid,
110
- collection: 'app.bsky.feed.post',
111
- rkey: uri.rkey,
112
- })
113
- expect(res3.data.uri).toBe(uri.toString())
114
- assert(AppBskyFeedPost.isMain(res3.data.value))
115
- expect(res3.data.value.text).toBe('Hello, world!')
116
-
117
- const res4 = await agent.app.bsky.feed.post.get({
118
- repo: aliceAgent.assertDid,
119
- rkey: uri.rkey,
120
- })
121
- expect(res4.uri).toBe(uri.toString())
122
- expect(res4.value.text).toBe('Hello, world!')
123
-
124
- await aliceAgent.com.atproto.repo.deleteRecord({
125
- repo: aliceAgent.assertDid,
126
- collection: 'app.bsky.feed.post',
127
- rkey: uri.rkey,
128
- })
129
- const res5 = await agent.com.atproto.repo.listRecords({
130
- repo: aliceAgent.assertDid,
131
- collection: 'app.bsky.feed.post',
132
- })
133
- expect(res5.data.records.length).toBe(0)
134
- })
135
-
136
- it('CRUDs records with the semantic sugars', async () => {
137
- const res1 = await aliceAgent.app.bsky.feed.post.create(
138
- { repo: aliceAgent.assertDid },
139
- {
140
- $type: 'app.bsky.feed.post',
141
- text: 'Hello, world!',
142
- createdAt: new Date().toISOString(),
143
- },
144
- )
145
- const uri = new AtUri(res1.uri)
146
-
147
- const res2 = await agent.app.bsky.feed.post.list({
148
- repo: aliceAgent.assertDid,
149
- })
150
- expect(res2.records.length).toBe(1)
151
-
152
- await aliceAgent.app.bsky.feed.post.delete({
153
- repo: aliceAgent.assertDid,
154
- rkey: uri.rkey,
155
- })
156
-
157
- const res3 = await agent.app.bsky.feed.post.list({
158
- repo: aliceAgent.assertDid,
159
- })
160
- expect(res3.records.length).toBe(0)
161
- })
162
-
163
- it('attaches images to a post', async () => {
164
- const file = await fs.readFile('../dev-env/assets/key-landscape-small.jpg')
165
- const uploadedRes = await aliceAgent.com.atproto.repo.uploadBlob(file, {
166
- encoding: 'image/jpeg',
167
- })
168
- const uploaded = uploadedRes.data.blob
169
- // Expect blobstore not to have image yet
170
- await expect(
171
- ctx.blobstore(aliceAgent.assertDid).getBytes(uploaded.ref),
172
- ).rejects.toThrow(BlobNotFoundError)
173
- // Associate image with post, image should be placed in blobstore
174
- const res = await aliceAgent.app.bsky.feed.post.create(
175
- { repo: aliceAgent.assertDid },
176
- {
177
- $type: 'app.bsky.feed.post',
178
- text: "Here's a key!",
179
- createdAt: new Date().toISOString(),
180
- embed: {
181
- $type: 'app.bsky.embed.images',
182
- images: [{ image: uploaded, alt: '' }],
183
- },
184
- },
185
- )
186
- // Ensure image is on post record
187
- const postUri = new AtUri(res.uri)
188
- const post = await aliceAgent.app.bsky.feed.post.get({
189
- rkey: postUri.rkey,
190
- repo: aliceAgent.assertDid,
191
- })
192
- assert(AppBskyEmbedImages.isMain(post.value.embed))
193
- const images = post.value.embed.images
194
- expect(images.length).toEqual(1)
195
- expect(uploaded.ref.equals(images[0].image.ref)).toBeTruthy()
196
- // Ensure that the uploaded image is now in the blobstore, i.e. doesn't throw BlobNotFoundError
197
- await ctx.blobstore(aliceAgent.assertDid).getBytes(uploaded.ref)
198
- // Cleanup
199
- await aliceAgent.app.bsky.feed.post.delete({
200
- rkey: postUri.rkey,
201
- repo: aliceAgent.assertDid,
202
- })
203
- })
204
-
205
- it('creates records with the correct key described by the schema', async () => {
206
- const res1 = await aliceAgent.app.bsky.actor.profile.create(
207
- { repo: aliceAgent.assertDid },
208
- {
209
- displayName: 'alice',
210
- createdAt: new Date().toISOString(),
211
- },
212
- )
213
- const uri = new AtUri(res1.uri)
214
- expect(uri.rkey).toBe('self')
215
- })
216
-
217
- describe('paginates', () => {
218
- let uri1: AtUri
219
- let uri2: AtUri
220
- let uri3: AtUri
221
- let uri4: AtUri
222
- let uri5: AtUri
223
-
224
- beforeAll(async () => {
225
- const createPost = async (text: string) => {
226
- const res = await aliceAgent.app.bsky.feed.post.create(
227
- { repo: aliceAgent.assertDid },
228
- {
229
- $type: 'app.bsky.feed.post',
230
- text,
231
- createdAt: new Date().toISOString(),
232
- },
233
- )
234
- return new AtUri(res.uri)
235
- }
236
- uri1 = await createPost('Post 1')
237
- uri2 = await createPost('Post 2')
238
- uri3 = await createPost('Post 3')
239
- uri4 = await createPost('Post 4')
240
- uri5 = await createPost('Post 5')
241
- })
242
-
243
- afterAll(async () => {
244
- for (const uri of [uri1, uri2, uri3, uri4, uri5]) {
245
- await aliceAgent.app.bsky.feed.post.delete({
246
- repo: aliceAgent.assertDid,
247
- rkey: uri.rkey,
248
- })
249
- }
250
- })
251
-
252
- it('in forwards order', async () => {
253
- const paginator = async (cursor?: string) => {
254
- return agent.app.bsky.feed.post.list({
255
- repo: aliceAgent.assertDid,
256
- cursor,
257
- limit: 2,
258
- })
259
- }
260
-
261
- const paginatedAll = await paginateAll(paginator)
262
- paginatedAll.forEach((res) =>
263
- expect(res.records.length).toBeLessThanOrEqual(2),
264
- )
265
-
266
- const full = await agent.app.bsky.feed.post.list({
267
- repo: aliceAgent.assertDid,
268
- })
269
-
270
- expect(full.records.length).toEqual(5)
271
- expect(paginatedAll.flatMap((res) => res.records)).toEqual(full.records)
272
- })
273
-
274
- it('in reverse order', async () => {
275
- const results = (
276
- results: Awaited<ReturnType<AppBskyFeedPostRecord['list']>>[],
277
- ) => results.flatMap((res) => res.records)
278
- const paginator = async (cursor?: string) => {
279
- const res = await agent.app.bsky.feed.post.list({
280
- repo: aliceAgent.assertDid,
281
- reverse: true,
282
- cursor,
283
- limit: 2,
284
- })
285
- return res
286
- }
287
-
288
- const paginatedAll = await paginateAll(paginator)
289
- paginatedAll.forEach((res) =>
290
- expect(res.records.length).toBeLessThanOrEqual(2),
291
- )
292
-
293
- const full = await agent.app.bsky.feed.post.list({
294
- repo: aliceAgent.assertDid,
295
- reverse: true,
296
- })
297
-
298
- expect(full.records.length).toEqual(5)
299
- expect(results(paginatedAll)).toEqual(results([full]))
300
- })
301
-
302
- it('reverses', async () => {
303
- const forwards = await agent.app.bsky.feed.post.list({
304
- repo: aliceAgent.assertDid,
305
- })
306
- const reverse = await agent.app.bsky.feed.post.list({
307
- repo: aliceAgent.assertDid,
308
- reverse: true,
309
- })
310
- expect(forwards.cursor).toEqual(uri1.rkey)
311
- expect(reverse.cursor).toEqual(uri5.rkey)
312
- expect(forwards.records.length).toEqual(5)
313
- expect(reverse.records.length).toEqual(5)
314
- expect(forwards.records.reverse()).toEqual(reverse.records)
315
- })
316
- })
317
-
318
- describe('deleteRecord', () => {
319
- it('deletes a record if it exists', async () => {
320
- const { repo } = aliceAgent.com.atproto
321
- const { data: post } = await repo.createRecord({
322
- repo: aliceAgent.assertDid,
323
- collection: 'app.bsky.feed.post',
324
- record: { text: 'post', createdAt: new Date().toISOString() },
325
- })
326
- const uri = new AtUri(post.uri)
327
- await repo.deleteRecord({
328
- repo: uri.host,
329
- collection: uri.collection,
330
- rkey: uri.rkey,
331
- })
332
- const checkPost = repo.getRecord({
333
- repo: uri.host,
334
- collection: uri.collection,
335
- rkey: uri.rkey,
336
- })
337
- await expect(checkPost).rejects.toThrow('Could not locate record')
338
- })
339
-
340
- it("no-ops if record doesn't exist", async () => {
341
- const { repo } = aliceAgent.com.atproto
342
- const { data: post } = await repo.createRecord({
343
- repo: aliceAgent.assertDid,
344
- collection: 'app.bsky.feed.post',
345
- record: { text: 'post', createdAt: new Date().toISOString() },
346
- })
347
- const uri = new AtUri(post.uri)
348
- await repo.deleteRecord({
349
- repo: uri.host,
350
- collection: uri.collection,
351
- rkey: uri.rkey,
352
- })
353
- const checkPost = repo.getRecord({
354
- repo: uri.host,
355
- collection: uri.collection,
356
- rkey: uri.rkey,
357
- })
358
- await expect(checkPost).rejects.toThrow('Could not locate record')
359
- const attemptDelete = repo.deleteRecord({
360
- repo: uri.host,
361
- collection: uri.collection,
362
- rkey: uri.rkey,
363
- })
364
- await expect(attemptDelete).resolves.toBeDefined()
365
- })
366
-
367
- it('does not delete the underlying block if it is referenced elsewhere', async () => {
368
- const { repo } = aliceAgent.com.atproto
369
- const record = { text: 'post', createdAt: new Date().toISOString() }
370
- const { data: post1 } = await repo.createRecord({
371
- repo: aliceAgent.assertDid,
372
- collection: 'app.bsky.feed.post',
373
- record,
374
- })
375
- const { data: post2 } = await repo.createRecord({
376
- repo: aliceAgent.assertDid,
377
- collection: 'app.bsky.feed.post',
378
- record,
379
- })
380
- const uri1 = new AtUri(post1.uri)
381
- await repo.deleteRecord({
382
- repo: uri1.host,
383
- collection: uri1.collection,
384
- rkey: uri1.rkey,
385
- })
386
- const uri2 = new AtUri(post2.uri)
387
- const checkPost2 = await repo.getRecord({
388
- repo: uri2.host,
389
- collection: uri2.collection,
390
- rkey: uri2.rkey,
391
- })
392
- expect(checkPost2).toBeDefined()
393
- expect(checkPost2.data.value).toMatchObject(record)
394
- })
395
- })
396
-
397
- describe('putRecord', () => {
398
- const profilePath = {
399
- collection: 'app.bsky.actor.profile',
400
- rkey: 'self',
401
- }
402
-
403
- it("creates a new record if it doesn't already exist", async () => {
404
- const { repo } = bobAgent.com.atproto
405
- const exists = repo.getRecord({
406
- ...profilePath,
407
- repo: bobAgent.assertDid,
408
- })
409
- await expect(exists).rejects.toThrow('Could not locate record')
410
-
411
- const { data: put } = await repo.putRecord({
412
- ...profilePath,
413
- repo: bobAgent.assertDid,
414
- record: {
415
- displayName: 'Robert',
416
- },
417
- })
418
- expect(put.uri).toEqual(
419
- `at://${bobAgent.assertDid}/app.bsky.actor.profile/self`,
420
- )
421
-
422
- const { data: profile } = await repo.getRecord({
423
- ...profilePath,
424
- repo: bobAgent.assertDid,
425
- })
426
- expect(profile.value).toEqual({
427
- $type: 'app.bsky.actor.profile',
428
- displayName: 'Robert',
429
- })
430
- })
431
-
432
- it('updates a record if it already exists', async () => {
433
- const { repo } = bobAgent.com.atproto
434
- const { data: put } = await repo.putRecord({
435
- ...profilePath,
436
- repo: bobAgent.assertDid,
437
- record: {
438
- displayName: 'Robert',
439
- description: 'Dog lover',
440
- },
441
- })
442
- expect(put.uri).toEqual(
443
- `at://${bobAgent.assertDid}/app.bsky.actor.profile/self`,
444
- )
445
-
446
- const { data: profile } = await repo.getRecord({
447
- ...profilePath,
448
- repo: bobAgent.assertDid,
449
- })
450
- expect(profile.value).toEqual({
451
- $type: 'app.bsky.actor.profile',
452
- displayName: 'Robert',
453
- description: 'Dog lover',
454
- })
455
- })
456
-
457
- it('still works if repo is specified by handle', async () => {
458
- await bobAgent.com.atproto.repo.putRecord({
459
- repo: 'bob.test',
460
- collection: 'app.bsky.graph.follow',
461
- rkey: TID.nextStr(),
462
- record: {
463
- subject: aliceAgent.assertDid,
464
- createdAt: new Date().toISOString(),
465
- },
466
- })
467
- })
468
-
469
- it('does not produce commit on no-op update', async () => {
470
- const { repo } = bobAgent.com.atproto
471
- const rootRes1 = await bobAgent.com.atproto.sync.getLatestCommit({
472
- did: bobAgent.assertDid,
473
- })
474
- const { data: put } = await repo.putRecord({
475
- ...profilePath,
476
- repo: bobAgent.assertDid,
477
- record: {
478
- displayName: 'Robert',
479
- description: 'Dog lover',
480
- },
481
- })
482
- expect(put.uri).toEqual(
483
- `at://${bobAgent.assertDid}/app.bsky.actor.profile/self`,
484
- )
485
-
486
- const rootRes2 = await bobAgent.com.atproto.sync.getLatestCommit({
487
- did: bobAgent.assertDid,
488
- })
489
-
490
- expect(rootRes2.data.cid).toEqual(rootRes1.data.cid)
491
- expect(rootRes2.data.rev).toEqual(rootRes1.data.rev)
492
- })
493
-
494
- it('fails on user mismatch', async () => {
495
- const { repo } = aliceAgent.com.atproto
496
- const put = repo.putRecord({
497
- repo: bobAgent.assertDid,
498
- collection: 'app.bsky.graph.follow',
499
- rkey: TID.nextStr(),
500
- record: {
501
- subject: aliceAgent.assertDid,
502
- createdAt: new Date().toISOString(),
503
- },
504
- })
505
- await expect(put).rejects.toThrow('Authentication Required')
506
- })
507
-
508
- it('fails on invalid record', async () => {
509
- const { repo } = bobAgent.com.atproto
510
- const put = repo.putRecord({
511
- ...profilePath,
512
- repo: bobAgent.assertDid,
513
- record: {
514
- displayName: 'Robert',
515
- description: 3.141,
516
- },
517
- })
518
- await expect(put).rejects.toMatchObject({
519
- error: 'InvalidRequest',
520
- message: expect.stringContaining('description'),
521
- // Invalid app.bsky.actor.profile record: Record/description must be a string
522
- // Expected one of null, boolean, integer, string, cid, bytes, array or object value type at $.record.description (got float)
523
- })
524
- const { data: profile } = await repo.getRecord({
525
- ...profilePath,
526
- repo: bobAgent.assertDid,
527
- })
528
- expect(profile.value).toEqual({
529
- $type: 'app.bsky.actor.profile',
530
- displayName: 'Robert',
531
- description: 'Dog lover',
532
- })
533
- })
534
-
535
- // @TODO remove after migrating legacy blobs
536
- it('updates a legacy blob ref when updating profile', async () => {
537
- const { repo } = bobAgent.api.com.atproto
538
- const file = await fs.readFile('../dev-env/assets/key-portrait-small.jpg')
539
- const uploadedRes = await repo.uploadBlob(file, {
540
- encoding: 'image/jpeg',
541
- })
542
-
543
- await repo.putRecord({
544
- ...profilePath,
545
- repo: bobAgent.accountDid,
546
- record: {
547
- displayName: 'Robert',
548
- avatar: BlobRef.fromJsonRef({
549
- mimeType: uploadedRes.data.blob.mimeType,
550
- cid: uploadedRes.data.blob.ref.toString(),
551
- }),
552
- },
553
- })
554
-
555
- const got = await repo.getRecord({
556
- ...profilePath,
557
- repo: bobAgent.accountDid,
558
- })
559
- const gotAvatar = got.data.value['avatar'] as BlobRef
560
- expect(gotAvatar.original).toEqual(uploadedRes.data.blob.original)
561
- })
562
- })
563
-
564
- // Validation
565
- // --------------
566
-
567
- it('defaults an undefined $type on records', async () => {
568
- const res = await aliceAgent.com.atproto.repo.createRecord({
569
- repo: aliceAgent.assertDid,
570
- collection: 'app.bsky.feed.post',
571
- record: {
572
- text: 'blah',
573
- createdAt: new Date().toISOString(),
574
- },
575
- })
576
- const uri = new AtUri(res.data.uri)
577
- const got = await agent.com.atproto.repo.getRecord({
578
- repo: aliceAgent.assertDid,
579
- collection: uri.collection,
580
- rkey: uri.rkey,
581
- })
582
- expect(got.data.value['$type']).toBe(uri.collection)
583
- })
584
-
585
- it('requires the schema to be known if explicitly validating', async () => {
586
- const prom = aliceAgent.com.atproto.repo.createRecord({
587
- validate: true,
588
- repo: aliceAgent.assertDid,
589
- collection: 'com.example.foobar',
590
- record: { $type: 'com.example.foobar' },
591
- })
592
- await expect(prom).rejects.toMatchObject({
593
- message: expect.stringContaining('com.example.foobar'),
594
- // Lexicon not found: lex:com.example.foobar
595
- // Unknown lexicon type: com.example.foobar
596
- })
597
- })
598
-
599
- it('does not require the schema to be known if not explicitly validating', async () => {
600
- const prom = await aliceAgent.com.atproto.repo.createRecord({
601
- // validate not set
602
- repo: aliceAgent.assertDid,
603
- collection: 'com.example.foobar',
604
- record: { $type: 'com.example.foobar' },
605
- })
606
- expect(prom.data.validationStatus).toBe('unknown')
607
- })
608
-
609
- it('requires the $type to match the schema', async () => {
610
- await expect(
611
- aliceAgent.com.atproto.repo.createRecord({
612
- repo: aliceAgent.assertDid,
613
- collection: 'app.bsky.feed.post',
614
- record: { $type: 'app.bsky.feed.like' },
615
- }),
616
- ).rejects.toThrow(
617
- 'Invalid $type: expected app.bsky.feed.post, got app.bsky.feed.like',
618
- )
619
- })
620
-
621
- it('requires valid rkey', async () => {
622
- await expect(
623
- aliceAgent.com.atproto.repo.createRecord({
624
- repo: aliceAgent.assertDid,
625
- collection: 'app.bsky.feed.generator',
626
- record: {
627
- $type: 'app.bsky.feed.generator',
628
- did: 'did:web:dummy.example.com',
629
- displayName: 'dummy',
630
- createdAt: new Date().toISOString(),
631
- },
632
- rkey: '..',
633
- }),
634
- ).rejects.toMatchObject({
635
- error: 'InvalidRequest',
636
- message: expect.stringContaining('rkey'),
637
- // Input/rkey must be a valid Record Key
638
- // Invalid record key at $.rkey (got \"..\")
639
- })
640
- })
641
-
642
- it('validates the record on write', async () => {
643
- await expect(
644
- aliceAgent.com.atproto.repo.createRecord({
645
- repo: aliceAgent.assertDid,
646
- collection: 'app.bsky.feed.post',
647
- record: { $type: 'app.bsky.feed.post' },
648
- }),
649
- ).rejects.toMatchObject({
650
- error: 'InvalidRequest',
651
- message: expect.stringContaining('Invalid app.bsky.feed.post record'),
652
- // Invalid app.bsky.feed.post record: Record must have the property "text"
653
- // Invalid app.bsky.feed.post record: Missing required key \"text\" at $.record
654
- })
655
- })
656
-
657
- it('validates datetimes rigorously', async () => {
658
- const postRecord = {
659
- $type: 'app.bsky.feed.post',
660
- text: 'test',
661
- createdAt: '0000-00-12T23:20:50.123Z',
662
- }
663
- await expect(
664
- aliceAgent.com.atproto.repo.createRecord({
665
- repo: aliceAgent.assertDid,
666
- collection: 'app.bsky.feed.post',
667
- record: postRecord,
668
- }),
669
- ).rejects.toMatchObject({
670
- error: 'InvalidRequest',
671
- message: expect.stringContaining('Invalid app.bsky.feed.post record'),
672
- // Invalid app.bsky.feed.post record: createdAt must be an valid atproto datetime (both RFC-3339 and ISO-8601)
673
- })
674
- })
675
-
676
- describe('unvalidated writes', () => {
677
- it('disallows creation of unknown lexicons when validate is set to true', async () => {
678
- const attempt = aliceAgent.com.atproto.repo.createRecord({
679
- validate: true,
680
- repo: aliceAgent.assertDid,
681
- collection: 'com.example.record',
682
- record: {
683
- blah: 'thing',
684
- },
685
- })
686
- await expect(attempt).rejects.toMatchObject({
687
- error: 'InvalidRequest',
688
- message: expect.stringContaining('com.example.record'),
689
- // Lexicon not found: lex:com.example.record
690
- // Unknown lexicon type: com.example.record
691
- })
692
- })
693
-
694
- it('allows creation of unknown lexicons when validate is not set to true', async () => {
695
- // validate: default
696
- const res1 = await aliceAgent.com.atproto.repo.createRecord({
697
- repo: aliceAgent.assertDid,
698
- collection: 'com.example.record',
699
- record: {
700
- blah: 'thing1',
701
- },
702
- })
703
- expect(res1.data.validationStatus).toBe('unknown')
704
- const record1 = await ctx.actorStore.read(aliceAgent.assertDid, (store) =>
705
- store.record.getRecord(new AtUri(res1.data.uri), res1.data.cid),
706
- )
707
- expect(record1?.value).toEqual({
708
- $type: 'com.example.record',
709
- blah: 'thing1',
710
- })
711
- // validate: false
712
- const res2 = await aliceAgent.com.atproto.repo.createRecord({
713
- validate: false,
714
- repo: aliceAgent.assertDid,
715
- collection: 'com.example.record',
716
- record: {
717
- blah: 'thing2',
718
- },
719
- })
720
- expect(res2.data.validationStatus).toBeUndefined()
721
- const record2 = await ctx.actorStore.read(aliceAgent.assertDid, (store) =>
722
- store.record.getRecord(new AtUri(res2.data.uri), res2.data.cid),
723
- )
724
- expect(record2?.value).toEqual({
725
- $type: 'com.example.record',
726
- blah: 'thing2',
727
- })
728
- })
729
-
730
- it('allows update of unknown lexicons when validate is set to false', async () => {
731
- const createRes = await aliceAgent.com.atproto.repo.createRecord({
732
- validate: false,
733
- repo: aliceAgent.assertDid,
734
- collection: 'com.example.record',
735
- record: {
736
- blah: 'thing',
737
- },
738
- })
739
- const uri = new AtUri(createRes.data.uri)
740
- // validate: default
741
- const updateRes1 = await aliceAgent.com.atproto.repo.putRecord({
742
- repo: aliceAgent.assertDid,
743
- collection: 'com.example.record',
744
- rkey: uri.rkey,
745
- record: {
746
- blah: 'something else',
747
- },
748
- })
749
- const record1 = await ctx.actorStore.read(aliceAgent.assertDid, (store) =>
750
- store.record.getRecord(uri, updateRes1.data.cid),
751
- )
752
- expect(record1?.value).toEqual({
753
- $type: 'com.example.record',
754
- blah: 'something else',
755
- })
756
- // validate: false
757
- const updateRes2 = await aliceAgent.com.atproto.repo.putRecord({
758
- validate: false,
759
- repo: aliceAgent.assertDid,
760
- collection: 'com.example.record',
761
- rkey: uri.rkey,
762
- record: {
763
- blah: 'something else',
764
- },
765
- })
766
- const record2 = await ctx.actorStore.read(aliceAgent.assertDid, (store) =>
767
- store.record.getRecord(uri, updateRes2.data.cid),
768
- )
769
- expect(record2?.value).toEqual({
770
- $type: 'com.example.record',
771
- blah: 'something else',
772
- })
773
- })
774
-
775
- it('applyWrites returns results with validation status', async () => {
776
- const existing1 = await aliceAgent.com.atproto.repo.createRecord({
777
- validate: false,
778
- repo: aliceAgent.assertDid,
779
- collection: 'com.example.record',
780
- record: {
781
- blah: 'thing1',
782
- },
783
- })
784
- const existing1Uri = new AtUri(existing1.data.uri)
785
- const existing2 = await aliceAgent.com.atproto.repo.createRecord({
786
- validate: false,
787
- repo: aliceAgent.assertDid,
788
- collection: 'com.example.record',
789
- record: {
790
- blah: 'thing2',
791
- },
792
- })
793
- const existing2Uri = new AtUri(existing2.data.uri)
794
- const {
795
- data: { results },
796
- } = await aliceAgent.com.atproto.repo.applyWrites({
797
- repo: aliceAgent.assertDid,
798
- writes: [
799
- {
800
- $type: 'com.atproto.repo.applyWrites#create',
801
- collection: 'app.bsky.feed.post',
802
- value: {
803
- $type: 'app.bsky.feed.post',
804
- text: '👋',
805
- createdAt: new Date().toISOString(),
806
- },
807
- },
808
- {
809
- $type: 'com.atproto.repo.applyWrites#update',
810
- collection: existing1Uri.collectionSafe,
811
- rkey: existing1Uri.rkey,
812
- value: {},
813
- },
814
- {
815
- $type: `com.atproto.repo.applyWrites#delete`,
816
- collection: existing2Uri.collectionSafe,
817
- rkey: existing2Uri.rkey,
818
- },
819
- ],
820
- })
821
- expect(forSnapshot(results)).toEqual([
822
- {
823
- $type: `com.atproto.repo.applyWrites#createResult`,
824
- cid: 'cids(0)',
825
- uri: 'record(0)',
826
- validationStatus: 'valid',
827
- },
828
- {
829
- $type: `com.atproto.repo.applyWrites#updateResult`,
830
- cid: 'cids(1)',
831
- uri: 'record(1)',
832
- validationStatus: 'unknown',
833
- },
834
- { $type: `com.atproto.repo.applyWrites#deleteResult` },
835
- ])
836
- })
837
-
838
- it('correctly associates images with unknown record types', async () => {
839
- const file = await fs.readFile('../dev-env/assets/key-portrait-small.jpg')
840
- const uploadedRes = await aliceAgent.com.atproto.repo.uploadBlob(file, {
841
- encoding: 'image/jpeg',
842
- })
843
-
844
- const res = await aliceAgent.com.atproto.repo.createRecord({
845
- repo: aliceAgent.assertDid,
846
- collection: 'com.example.record',
847
- record: {
848
- blah: 'thing',
849
- image: uploadedRes.data.blob,
850
- },
851
- validate: false,
852
- })
853
- const record = await ctx.actorStore.read(aliceAgent.assertDid, (store) =>
854
- store.record.getRecord(new AtUri(res.data.uri), res.data.cid),
855
- )
856
- assert(record)
857
- expect(record.value).toMatchObject({
858
- $type: 'com.example.record',
859
- blah: 'thing',
860
- })
861
- const recordBlobs = await ctx.actorStore.read(
862
- aliceAgent.assertDid,
863
- (store) => store.repo.blob.getBlobsForRecord(record.uri),
864
- )
865
- expect(recordBlobs.length).toBe(1)
866
- expect(recordBlobs.at(0)).toBe(uploadedRes.data.blob.ref.toString())
867
- })
868
-
869
- it('enforces record type constraint even when unvalidated', async () => {
870
- const attempt = aliceAgent.com.atproto.repo.createRecord({
871
- repo: aliceAgent.assertDid,
872
- collection: 'com.example.record',
873
- record: {
874
- $type: 'com.example.other',
875
- blah: 'thing',
876
- },
877
- })
878
- await expect(attempt).rejects.toThrow(
879
- 'Invalid $type: expected com.example.record, got com.example.other',
880
- )
881
- })
882
-
883
- it('enforces blob ref format even when unvalidated', async () => {
884
- const file = await fs.readFile('../dev-env/assets/key-portrait-small.jpg')
885
- const uploadedRes = await aliceAgent.com.atproto.repo.uploadBlob(file, {
886
- encoding: 'image/jpeg',
887
- })
888
-
889
- await expect(
890
- aliceAgent.com.atproto.repo.createRecord({
891
- repo: aliceAgent.assertDid,
892
- collection: 'com.example.record',
893
- record: {
894
- blah: 'thing',
895
- image: {
896
- cid: uploadedRes.data.blob.ref.toString(),
897
- mimeType: uploadedRes.data.blob.mimeType,
898
- },
899
- },
900
- validate: false,
901
- }),
902
- ).rejects.toMatchObject({
903
- error: 'InvalidRequest',
904
- message: expect.stringContaining('Legacy blobs are not allowed'),
905
- })
906
- })
907
- })
908
-
909
- describe('compare-and-swap', () => {
910
- let recordCount = 0 // Ensures unique cids
911
- const postRecord = () => ({
912
- text: `post (${++recordCount})`,
913
- createdAt: new Date().toISOString(),
914
- })
915
- const profileRecord = () => ({
916
- displayName: `ali (${++recordCount})`,
917
- })
918
-
919
- it('createRecord succeeds on proper commit cas', async () => {
920
- const { repo, sync } = aliceAgent.com.atproto
921
- const { data: commit } = await sync.getLatestCommit({
922
- did: aliceAgent.assertDid,
923
- })
924
- const { data: post } = await repo.createRecord({
925
- repo: aliceAgent.assertDid,
926
- collection: 'app.bsky.feed.post',
927
- swapCommit: commit.cid,
928
- record: postRecord(),
929
- })
930
- const uri = new AtUri(post.uri)
931
- const checkPost = repo.getRecord({
932
- repo: uri.host,
933
- collection: uri.collection,
934
- rkey: uri.rkey,
935
- })
936
- await expect(checkPost).resolves.toBeDefined()
937
- })
938
-
939
- it('createRecord fails on bad commit cas', async () => {
940
- const { repo, sync } = aliceAgent.com.atproto
941
- const { data: staleCommit } = await sync.getLatestCommit({
942
- did: aliceAgent.assertDid,
943
- })
944
- // Update repo, change head
945
- await repo.createRecord({
946
- repo: aliceAgent.assertDid,
947
- collection: 'app.bsky.feed.post',
948
- record: postRecord(),
949
- })
950
- const attemptCreate = repo.createRecord({
951
- repo: aliceAgent.assertDid,
952
- collection: 'app.bsky.feed.post',
953
- swapCommit: staleCommit.cid,
954
- record: postRecord(),
955
- })
956
- await expect(attemptCreate).rejects.toMatchObject({
957
- error: 'InvalidSwap',
958
- })
959
- })
960
-
961
- it('deleteRecord succeeds on proper commit cas', async () => {
962
- const { repo, sync } = aliceAgent.com.atproto
963
- const { data: post } = await repo.createRecord({
964
- repo: aliceAgent.assertDid,
965
- collection: 'app.bsky.feed.post',
966
- record: postRecord(),
967
- })
968
- const { data: commit } = await sync.getLatestCommit({
969
- did: aliceAgent.assertDid,
970
- })
971
- const uri = new AtUri(post.uri)
972
- await repo.deleteRecord({
973
- repo: uri.host,
974
- collection: uri.collection,
975
- rkey: uri.rkey,
976
- swapCommit: commit.cid,
977
- })
978
- const checkPost = repo.getRecord({
979
- repo: uri.host,
980
- collection: uri.collection,
981
- rkey: uri.rkey,
982
- })
983
- await expect(checkPost).rejects.toThrow('Could not locate record')
984
- })
985
-
986
- it('deleteRecord fails on bad commit cas', async () => {
987
- const { repo, sync } = aliceAgent.com.atproto
988
- const { data: staleCommit } = await sync.getLatestCommit({
989
- did: aliceAgent.assertDid,
990
- })
991
- const { data: post } = await repo.createRecord({
992
- repo: aliceAgent.assertDid,
993
- collection: 'app.bsky.feed.post',
994
- record: postRecord(),
995
- })
996
- const uri = new AtUri(post.uri)
997
- const attemptDelete = repo.deleteRecord({
998
- repo: uri.host,
999
- collection: uri.collection,
1000
- rkey: uri.rkey,
1001
- swapCommit: staleCommit.cid,
1002
- })
1003
- await expect(attemptDelete).rejects.toMatchObject({
1004
- error: 'InvalidSwap',
1005
- })
1006
- const checkPost = repo.getRecord({
1007
- repo: uri.host,
1008
- collection: uri.collection,
1009
- rkey: uri.rkey,
1010
- })
1011
- await expect(checkPost).resolves.toBeDefined()
1012
- })
1013
-
1014
- it('deleteRecord succeeds on proper record cas', async () => {
1015
- const { repo } = aliceAgent.com.atproto
1016
- const { data: post } = await repo.createRecord({
1017
- repo: aliceAgent.assertDid,
1018
- collection: 'app.bsky.feed.post',
1019
- record: postRecord(),
1020
- })
1021
- const uri = new AtUri(post.uri)
1022
- await repo.deleteRecord({
1023
- repo: uri.host,
1024
- collection: uri.collection,
1025
- rkey: uri.rkey,
1026
- swapRecord: post.cid,
1027
- })
1028
- const checkPost = repo.getRecord({
1029
- repo: uri.host,
1030
- collection: uri.collection,
1031
- rkey: uri.rkey,
1032
- })
1033
- await expect(checkPost).rejects.toThrow('Could not locate record')
1034
- })
1035
-
1036
- it('deleteRecord fails on bad record cas', async () => {
1037
- const { repo } = aliceAgent.com.atproto
1038
- const { data: post } = await repo.createRecord({
1039
- repo: aliceAgent.assertDid,
1040
- collection: 'app.bsky.feed.post',
1041
- record: postRecord(),
1042
- })
1043
- const uri = new AtUri(post.uri)
1044
- const attemptDelete = repo.deleteRecord({
1045
- repo: uri.host,
1046
- collection: uri.collection,
1047
- rkey: uri.rkey,
1048
- swapRecord: (await cidForCbor({})).toString(),
1049
- })
1050
- await expect(attemptDelete).rejects.toMatchObject({
1051
- error: 'InvalidSwap',
1052
- })
1053
- const checkPost = repo.getRecord({
1054
- repo: uri.host,
1055
- collection: uri.collection,
1056
- rkey: uri.rkey,
1057
- })
1058
- await expect(checkPost).resolves.toBeDefined()
1059
- })
1060
-
1061
- it('putRecord succeeds on proper commit cas', async () => {
1062
- const { repo, sync } = aliceAgent.com.atproto
1063
- const { data: commit } = await sync.getLatestCommit({
1064
- did: aliceAgent.assertDid,
1065
- })
1066
- const { data: profile } = await repo.putRecord({
1067
- repo: aliceAgent.assertDid,
1068
- collection: 'app.bsky.actor.profile',
1069
- rkey: 'self',
1070
- swapCommit: commit.cid,
1071
- record: profileRecord(),
1072
- })
1073
- const { data: checkProfile } = await repo.getRecord({
1074
- repo: aliceAgent.assertDid,
1075
- collection: 'app.bsky.actor.profile',
1076
- rkey: 'self',
1077
- })
1078
- expect(checkProfile.cid).toEqual(profile.cid)
1079
- })
1080
-
1081
- it('putRecord fails on bad commit cas', async () => {
1082
- const { repo, sync } = aliceAgent.com.atproto
1083
- const { data: staleCommit } = await sync.getLatestCommit({
1084
- did: aliceAgent.assertDid,
1085
- })
1086
- // Update repo, change head
1087
- await repo.createRecord({
1088
- repo: aliceAgent.assertDid,
1089
- collection: 'app.bsky.feed.post',
1090
- record: postRecord(),
1091
- })
1092
- const attemptPut = repo.putRecord({
1093
- repo: aliceAgent.assertDid,
1094
- collection: 'app.bsky.actor.profile',
1095
- rkey: 'self',
1096
- swapCommit: staleCommit.cid,
1097
- record: profileRecord(),
1098
- })
1099
- await expect(attemptPut).rejects.toMatchObject({ error: 'InvalidSwap' })
1100
- })
1101
-
1102
- it('putRecord succeeds on proper record cas', async () => {
1103
- const { repo } = aliceAgent.com.atproto
1104
- // Start with missing profile record, to test swapRecord=null
1105
- await repo.deleteRecord({
1106
- repo: aliceAgent.assertDid,
1107
- collection: 'app.bsky.actor.profile',
1108
- rkey: 'self',
1109
- })
1110
- // Test swapRecord w/ null (ensures create)
1111
- const { data: profile1 } = await repo.putRecord({
1112
- repo: aliceAgent.assertDid,
1113
- collection: 'app.bsky.actor.profile',
1114
- rkey: 'self',
1115
- swapRecord: null,
1116
- record: profileRecord(),
1117
- })
1118
- const { data: checkProfile1 } = await repo.getRecord({
1119
- repo: aliceAgent.assertDid,
1120
- collection: 'app.bsky.actor.profile',
1121
- rkey: 'self',
1122
- })
1123
- expect(checkProfile1.cid).toEqual(profile1.cid)
1124
- // Test swapRecord w/ cid (ensures update)
1125
- const { data: profile2 } = await repo.putRecord({
1126
- repo: aliceAgent.assertDid,
1127
- collection: 'app.bsky.actor.profile',
1128
- rkey: 'self',
1129
- swapRecord: profile1.cid,
1130
- record: profileRecord(),
1131
- })
1132
- const { data: checkProfile2 } = await repo.getRecord({
1133
- repo: aliceAgent.assertDid,
1134
- collection: 'app.bsky.actor.profile',
1135
- rkey: 'self',
1136
- })
1137
- expect(checkProfile2.cid).toEqual(profile2.cid)
1138
- })
1139
-
1140
- it('putRecord fails on bad record cas', async () => {
1141
- const { repo } = aliceAgent.com.atproto
1142
- // Test swapRecord w/ null (ensures create)
1143
- const attemptPut1 = repo.putRecord({
1144
- repo: aliceAgent.assertDid,
1145
- collection: 'app.bsky.actor.profile',
1146
- rkey: 'self',
1147
- swapRecord: null,
1148
- record: profileRecord(),
1149
- })
1150
- await expect(attemptPut1).rejects.toMatchObject({ error: 'InvalidSwap' })
1151
- // Test swapRecord w/ cid (ensures update)
1152
- const attemptPut2 = repo.putRecord({
1153
- repo: aliceAgent.assertDid,
1154
- collection: 'app.bsky.actor.profile',
1155
- rkey: 'self',
1156
- swapRecord: (await cidForCbor({})).toString(),
1157
- record: profileRecord(),
1158
- })
1159
- await expect(attemptPut2).rejects.toMatchObject({ error: 'InvalidSwap' })
1160
- })
1161
-
1162
- it('applyWrites succeeds on proper commit cas', async () => {
1163
- const { repo, sync } = aliceAgent.com.atproto
1164
- const { data: commit } = await sync.getLatestCommit({
1165
- did: aliceAgent.assertDid,
1166
- })
1167
- await repo.applyWrites({
1168
- repo: aliceAgent.assertDid,
1169
- swapCommit: commit.cid,
1170
- writes: [
1171
- {
1172
- $type: `com.atproto.repo.applyWrites#create`,
1173
- collection: 'app.bsky.feed.post',
1174
- value: { $type: 'app.bsky.feed.post', ...postRecord() },
1175
- },
1176
- ],
1177
- })
1178
- })
1179
-
1180
- it('applyWrites fails on bad commit cas', async () => {
1181
- const { repo, sync } = aliceAgent.com.atproto
1182
- const { data: staleCommit } = await sync.getLatestCommit({
1183
- did: aliceAgent.assertDid,
1184
- })
1185
- // Update repo, change head
1186
- await repo.createRecord({
1187
- repo: aliceAgent.assertDid,
1188
- collection: 'app.bsky.feed.post',
1189
- record: postRecord(),
1190
- })
1191
- const attemptApplyWrite = repo.applyWrites({
1192
- repo: aliceAgent.assertDid,
1193
- swapCommit: staleCommit.cid,
1194
- writes: [
1195
- {
1196
- $type: `com.atproto.repo.applyWrites#create`,
1197
- collection: 'app.bsky.feed.post',
1198
- value: { $type: 'app.bsky.feed.post', ...postRecord() },
1199
- },
1200
- ],
1201
- })
1202
- await expect(attemptApplyWrite).rejects.toMatchObject({
1203
- error: 'InvalidSwap',
1204
- })
1205
- })
1206
-
1207
- it("writes fail on values that can't reliably transform between cbor to lex", async () => {
1208
- const passthroughBody = (data: unknown) =>
1209
- ui8ToArrayBuffer(new TextEncoder().encode(JSON.stringify(data)))
1210
-
1211
- const result = aliceAgent.call(
1212
- 'com.atproto.repo.createRecord',
1213
- {},
1214
- passthroughBody({
1215
- repo: aliceAgent.assertDid,
1216
- collection: 'app.bsky.feed.post',
1217
- record: {
1218
- text: 'x',
1219
- createdAt: new Date().toISOString(),
1220
- deepObject: createDeepObject(4000),
1221
- },
1222
- }),
1223
- {
1224
- encoding: 'application/json',
1225
- },
1226
- )
1227
-
1228
- await expect(result).rejects.toMatchObject({
1229
- status: 400,
1230
- error: 'InvalidRequest',
1231
- })
1232
- })
1233
- })
1234
-
1235
- it('prevents duplicate likes', async () => {
1236
- const now = new Date().toISOString()
1237
- const uriA = AtUri.make(
1238
- bobAgent.assertDid,
1239
- 'app.bsky.feed.post',
1240
- TID.nextStr(),
1241
- )
1242
- const cidA = await cidForCbor({ post: 'a' })
1243
- const uriB = AtUri.make(
1244
- bobAgent.assertDid,
1245
- 'app.bsky.feed.post',
1246
- TID.nextStr(),
1247
- )
1248
- const cidB = await cidForCbor({ post: 'b' })
1249
-
1250
- const { data: like1 } = await aliceAgent.com.atproto.repo.createRecord({
1251
- repo: aliceAgent.assertDid,
1252
- collection: 'app.bsky.feed.like',
1253
- record: {
1254
- $type: 'app.bsky.feed.like',
1255
- subject: { uri: uriA.toString(), cid: cidA.toString() },
1256
- createdAt: now,
1257
- },
1258
- })
1259
- const { data: like2 } = await aliceAgent.com.atproto.repo.createRecord({
1260
- repo: aliceAgent.assertDid,
1261
- collection: 'app.bsky.feed.like',
1262
- record: {
1263
- $type: 'app.bsky.feed.like',
1264
- subject: { uri: uriB.toString(), cid: cidB.toString() },
1265
- createdAt: now,
1266
- },
1267
- })
1268
- const { data: like3 } = await aliceAgent.com.atproto.repo.createRecord({
1269
- repo: aliceAgent.assertDid,
1270
- collection: 'app.bsky.feed.like',
1271
- record: {
1272
- $type: 'app.bsky.feed.like',
1273
- subject: { uri: uriA.toString(), cid: cidA.toString() },
1274
- createdAt: now,
1275
- },
1276
- })
1277
-
1278
- const getLike1 = aliceAgent.com.atproto.repo.getRecord({
1279
- repo: aliceAgent.assertDid,
1280
- collection: 'app.bsky.feed.like',
1281
- rkey: new AtUri(like1.uri).rkey,
1282
- })
1283
-
1284
- await expect(getLike1).rejects.toThrow('Could not locate record:')
1285
-
1286
- const getLike2 = aliceAgent.com.atproto.repo.getRecord({
1287
- repo: aliceAgent.assertDid,
1288
- collection: 'app.bsky.feed.like',
1289
- rkey: new AtUri(like2.uri).rkey,
1290
- })
1291
-
1292
- await expect(getLike2).resolves.toBeDefined()
1293
-
1294
- const getLike3 = aliceAgent.com.atproto.repo.getRecord({
1295
- repo: aliceAgent.assertDid,
1296
- collection: 'app.bsky.feed.like',
1297
- rkey: new AtUri(like3.uri).rkey,
1298
- })
1299
-
1300
- await expect(getLike3).resolves.toBeDefined()
1301
- })
1302
-
1303
- it('prevents duplicate reposts', async () => {
1304
- const now = new Date().toISOString()
1305
- const uriA = AtUri.make(
1306
- bobAgent.assertDid,
1307
- 'app.bsky.feed.post',
1308
- TID.nextStr(),
1309
- )
1310
- const cidA = await cidForCbor({ post: 'a' })
1311
- const uriB = AtUri.make(
1312
- bobAgent.assertDid,
1313
- 'app.bsky.feed.post',
1314
- TID.nextStr(),
1315
- )
1316
- const cidB = await cidForCbor({ post: 'b' })
1317
-
1318
- const { data: repost1 } = await aliceAgent.com.atproto.repo.createRecord({
1319
- repo: aliceAgent.assertDid,
1320
- collection: 'app.bsky.feed.repost',
1321
- record: {
1322
- $type: 'app.bsky.feed.repost',
1323
- subject: { uri: uriA.toString(), cid: cidA.toString() },
1324
- createdAt: now,
1325
- },
1326
- })
1327
- const { data: repost2 } = await aliceAgent.com.atproto.repo.createRecord({
1328
- repo: aliceAgent.assertDid,
1329
- collection: 'app.bsky.feed.repost',
1330
- record: {
1331
- $type: 'app.bsky.feed.repost',
1332
- subject: { uri: uriB.toString(), cid: cidB.toString() },
1333
- createdAt: now,
1334
- },
1335
- })
1336
- const { data: repost3 } = await aliceAgent.com.atproto.repo.createRecord({
1337
- repo: aliceAgent.assertDid,
1338
- collection: 'app.bsky.feed.repost',
1339
- record: {
1340
- $type: 'app.bsky.feed.repost',
1341
- subject: { uri: uriA.toString(), cid: cidA.toString() },
1342
- createdAt: now,
1343
- },
1344
- })
1345
-
1346
- const getRepost1 = aliceAgent.com.atproto.repo.getRecord({
1347
- repo: aliceAgent.assertDid,
1348
- collection: 'app.bsky.feed.repost',
1349
- rkey: new AtUri(repost1.uri).rkey,
1350
- })
1351
-
1352
- await expect(getRepost1).rejects.toThrow('Could not locate record:')
1353
-
1354
- const getRepost2 = aliceAgent.com.atproto.repo.getRecord({
1355
- repo: aliceAgent.assertDid,
1356
- collection: 'app.bsky.feed.repost',
1357
- rkey: new AtUri(repost2.uri).rkey,
1358
- })
1359
-
1360
- await expect(getRepost2).resolves.toBeDefined()
1361
-
1362
- const getRepost3 = aliceAgent.com.atproto.repo.getRecord({
1363
- repo: aliceAgent.assertDid,
1364
- collection: 'app.bsky.feed.repost',
1365
- rkey: new AtUri(repost3.uri).rkey,
1366
- })
1367
-
1368
- await expect(getRepost3).resolves.toBeDefined()
1369
- })
1370
-
1371
- it('prevents duplicate blocks', async () => {
1372
- const now = new Date().toISOString()
1373
-
1374
- const { data: block1 } = await aliceAgent.com.atproto.repo.createRecord({
1375
- repo: aliceAgent.assertDid,
1376
- collection: 'app.bsky.graph.block',
1377
- record: {
1378
- $type: 'app.bsky.graph.block',
1379
- subject: bobAgent.assertDid,
1380
- createdAt: now,
1381
- },
1382
- })
1383
-
1384
- const { data: block2 } = await bobAgent.com.atproto.repo.createRecord({
1385
- repo: bobAgent.assertDid,
1386
- collection: 'app.bsky.graph.block',
1387
- record: {
1388
- $type: 'app.bsky.graph.block',
1389
- subject: aliceAgent.assertDid,
1390
- createdAt: now,
1391
- },
1392
- })
1393
-
1394
- const { data: block3 } = await aliceAgent.com.atproto.repo.createRecord({
1395
- repo: aliceAgent.assertDid,
1396
- collection: 'app.bsky.graph.block',
1397
- record: {
1398
- $type: 'app.bsky.graph.block',
1399
- subject: bobAgent.assertDid,
1400
- createdAt: now,
1401
- },
1402
- })
1403
-
1404
- const getBlock1 = aliceAgent.com.atproto.repo.getRecord({
1405
- repo: aliceAgent.assertDid,
1406
- collection: 'app.bsky.graph.block',
1407
- rkey: new AtUri(block1.uri).rkey,
1408
- })
1409
-
1410
- await expect(getBlock1).rejects.toThrow('Could not locate record:')
1411
-
1412
- const getBlock2 = aliceAgent.com.atproto.repo.getRecord({
1413
- repo: bobAgent.assertDid,
1414
- collection: 'app.bsky.graph.block',
1415
- rkey: new AtUri(block2.uri).rkey,
1416
- })
1417
-
1418
- await expect(getBlock2).resolves.toBeDefined()
1419
-
1420
- const getBlock3 = aliceAgent.com.atproto.repo.getRecord({
1421
- repo: aliceAgent.assertDid,
1422
- collection: 'app.bsky.graph.block',
1423
- rkey: new AtUri(block3.uri).rkey,
1424
- })
1425
-
1426
- await expect(getBlock3).resolves.toBeDefined()
1427
- })
1428
-
1429
- it('prevents duplicate follows', async () => {
1430
- const now = new Date().toISOString()
1431
-
1432
- const { data: follow1 } = await aliceAgent.com.atproto.repo.createRecord({
1433
- repo: aliceAgent.assertDid,
1434
- collection: 'app.bsky.graph.follow',
1435
- record: {
1436
- $type: 'app.bsky.graph.follow',
1437
- subject: bobAgent.assertDid,
1438
- createdAt: now,
1439
- },
1440
- })
1441
- const { data: follow2 } = await bobAgent.com.atproto.repo.createRecord({
1442
- repo: bobAgent.assertDid,
1443
- collection: 'app.bsky.graph.follow',
1444
- record: {
1445
- $type: 'app.bsky.graph.follow',
1446
- subject: aliceAgent.assertDid,
1447
- createdAt: now,
1448
- },
1449
- })
1450
- const { data: follow3 } = await aliceAgent.com.atproto.repo.createRecord({
1451
- repo: aliceAgent.assertDid,
1452
- collection: 'app.bsky.graph.follow',
1453
- record: {
1454
- $type: 'app.bsky.graph.follow',
1455
- subject: bobAgent.assertDid,
1456
- createdAt: now,
1457
- },
1458
- })
1459
-
1460
- const getFollow1 = aliceAgent.com.atproto.repo.getRecord({
1461
- repo: aliceAgent.assertDid,
1462
- collection: 'app.bsky.graph.follow',
1463
- rkey: new AtUri(follow1.uri).rkey,
1464
- })
1465
-
1466
- await expect(getFollow1).rejects.toThrow('Could not locate record:')
1467
-
1468
- const getFollow2 = aliceAgent.com.atproto.repo.getRecord({
1469
- repo: bobAgent.assertDid,
1470
- collection: 'app.bsky.graph.follow',
1471
- rkey: new AtUri(follow2.uri).rkey,
1472
- })
1473
-
1474
- await expect(getFollow2).resolves.toBeDefined()
1475
-
1476
- const getFollow3 = aliceAgent.com.atproto.repo.getRecord({
1477
- repo: aliceAgent.assertDid,
1478
- collection: 'app.bsky.graph.follow',
1479
- rkey: new AtUri(follow3.uri).rkey,
1480
- })
1481
-
1482
- await expect(getFollow3).resolves.toBeDefined()
1483
- })
1484
-
1485
- // Moderation
1486
- // --------------
1487
-
1488
- it("doesn't serve taken-down record", async () => {
1489
- const created = await aliceAgent.app.bsky.feed.post.create(
1490
- { repo: aliceAgent.assertDid },
1491
- {
1492
- $type: 'app.bsky.feed.post',
1493
- text: 'Hello, world!',
1494
- createdAt: new Date().toISOString(),
1495
- },
1496
- )
1497
- const postUri = new AtUri(created.uri)
1498
- const post = await agent.app.bsky.feed.post.get({
1499
- repo: aliceAgent.assertDid,
1500
- rkey: postUri.rkey,
1501
- })
1502
- const posts = await agent.app.bsky.feed.post.list({
1503
- repo: aliceAgent.assertDid,
1504
- })
1505
- expect(posts.records.map((r) => r.uri)).toContain(post.uri)
1506
-
1507
- const subject = {
1508
- $type: 'com.atproto.repo.strongRef',
1509
- uri: created.uri,
1510
- cid: created.cid,
1511
- }
1512
- await agent.com.atproto.admin.updateSubjectStatus(
1513
- {
1514
- subject,
1515
- takedown: { applied: true },
1516
- },
1517
- {
1518
- encoding: 'application/json',
1519
- headers: { authorization: network.pds.adminAuth() },
1520
- },
1521
- )
1522
-
1523
- const postTakedownPromise = agent.app.bsky.feed.post.get({
1524
- repo: aliceAgent.assertDid,
1525
- rkey: postUri.rkey,
1526
- })
1527
- await expect(postTakedownPromise).rejects.toThrow('Could not locate record')
1528
- const postsTakedown = await agent.app.bsky.feed.post.list({
1529
- repo: aliceAgent.assertDid,
1530
- })
1531
- expect(postsTakedown.records.map((r) => r.uri)).not.toContain(post.uri)
1532
-
1533
- // Cleanup
1534
- await agent.com.atproto.admin.updateSubjectStatus(
1535
- {
1536
- subject,
1537
- takedown: { applied: false },
1538
- },
1539
- {
1540
- encoding: 'application/json',
1541
- headers: { authorization: network.pds.adminAuth() },
1542
- },
1543
- )
1544
- })
1545
-
1546
- it("doesn't serve taken-down actor", async () => {
1547
- const posts = await agent.app.bsky.feed.post.list({
1548
- repo: aliceAgent.assertDid,
1549
- })
1550
- expect(posts.records.length).toBeGreaterThan(0)
1551
-
1552
- const subject = {
1553
- $type: 'com.atproto.admin.defs#repoRef',
1554
- did: aliceAgent.assertDid,
1555
- }
1556
-
1557
- await agent.com.atproto.admin.updateSubjectStatus(
1558
- {
1559
- subject,
1560
- takedown: { applied: true },
1561
- },
1562
- {
1563
- encoding: 'application/json',
1564
- headers: { authorization: network.pds.adminAuth() },
1565
- },
1566
- )
1567
-
1568
- const tryListPosts = agent.app.bsky.feed.post.list({
1569
- repo: aliceAgent.assertDid,
1570
- })
1571
- await expect(tryListPosts).rejects.toThrow(/Could not find repo/)
1572
-
1573
- // Cleanup
1574
- await agent.com.atproto.admin.updateSubjectStatus(
1575
- {
1576
- subject,
1577
- takedown: { applied: false },
1578
- },
1579
- {
1580
- encoding: 'application/json',
1581
- headers: { authorization: network.pds.adminAuth() },
1582
- },
1583
- )
1584
- })
1585
- })
1586
-
1587
- function createDeepObject(depth: number) {
1588
- const obj: any = {}
1589
- let iter = obj
1590
- for (let i = 0; i < depth; ++i) {
1591
- iter.x = {}
1592
- iter = iter.x
1593
- }
1594
- return obj
1595
- }