@atproto/pds 0.5.11 → 0.5.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (322) hide show
  1. package/CHANGELOG.md +46 -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/package.json +42 -38
  10. package/build.templates.cjs +0 -22
  11. package/example.env +0 -42
  12. package/jest.config.cjs +0 -27
  13. package/src/account-manager/account-manager.ts +0 -916
  14. package/src/account-manager/db/index.ts +0 -21
  15. package/src/account-manager/db/migrations/001-init.ts +0 -115
  16. package/src/account-manager/db/migrations/002-account-deactivation.ts +0 -17
  17. package/src/account-manager/db/migrations/003-privileged-app-passwords.ts +0 -12
  18. package/src/account-manager/db/migrations/004-oauth.ts +0 -122
  19. package/src/account-manager/db/migrations/005-oauth-account-management.ts +0 -136
  20. package/src/account-manager/db/migrations/006-oauth-permission-sets.ts +0 -20
  21. package/src/account-manager/db/migrations/007-lexicon-failures-index.ts +0 -14
  22. package/src/account-manager/db/migrations/index.ts +0 -17
  23. package/src/account-manager/db/schema/account-device.ts +0 -14
  24. package/src/account-manager/db/schema/account.ts +0 -16
  25. package/src/account-manager/db/schema/actor.ts +0 -17
  26. package/src/account-manager/db/schema/app-password.ts +0 -13
  27. package/src/account-manager/db/schema/authorization-request.ts +0 -30
  28. package/src/account-manager/db/schema/authorized-client.ts +0 -23
  29. package/src/account-manager/db/schema/device.ts +0 -18
  30. package/src/account-manager/db/schema/email-token.ts +0 -19
  31. package/src/account-manager/db/schema/index.ts +0 -43
  32. package/src/account-manager/db/schema/invite-code.ts +0 -24
  33. package/src/account-manager/db/schema/lexicon.ts +0 -15
  34. package/src/account-manager/db/schema/refresh-token.ts +0 -11
  35. package/src/account-manager/db/schema/repo-root.ts +0 -12
  36. package/src/account-manager/db/schema/token.ts +0 -38
  37. package/src/account-manager/db/schema/used-refresh-token.ts +0 -13
  38. package/src/account-manager/helpers/account-device.ts +0 -63
  39. package/src/account-manager/helpers/account.ts +0 -355
  40. package/src/account-manager/helpers/auth.ts +0 -222
  41. package/src/account-manager/helpers/authorization-request.ts +0 -90
  42. package/src/account-manager/helpers/authorized-client.ts +0 -75
  43. package/src/account-manager/helpers/device.ts +0 -47
  44. package/src/account-manager/helpers/email-token.ts +0 -87
  45. package/src/account-manager/helpers/invite.ts +0 -263
  46. package/src/account-manager/helpers/lexicon.ts +0 -67
  47. package/src/account-manager/helpers/password.ts +0 -136
  48. package/src/account-manager/helpers/repo.ts +0 -24
  49. package/src/account-manager/helpers/scrypt.ts +0 -53
  50. package/src/account-manager/helpers/token.ts +0 -158
  51. package/src/account-manager/helpers/used-refresh-token.ts +0 -30
  52. package/src/account-manager/oauth-store.ts +0 -880
  53. package/src/account-manager/scope-reference-getter.ts +0 -106
  54. package/src/actor-store/actor-store-reader.ts +0 -45
  55. package/src/actor-store/actor-store-resources.ts +0 -8
  56. package/src/actor-store/actor-store-transactor.ts +0 -31
  57. package/src/actor-store/actor-store-writer.ts +0 -17
  58. package/src/actor-store/actor-store.ts +0 -210
  59. package/src/actor-store/blob/reader.ts +0 -160
  60. package/src/actor-store/blob/transactor.ts +0 -383
  61. package/src/actor-store/db/index.ts +0 -20
  62. package/src/actor-store/db/migrations/001-init.ts +0 -105
  63. package/src/actor-store/db/migrations/index.ts +0 -5
  64. package/src/actor-store/db/schema/account-pref.ts +0 -11
  65. package/src/actor-store/db/schema/backlink.ts +0 -9
  66. package/src/actor-store/db/schema/blob.ts +0 -14
  67. package/src/actor-store/db/schema/index.ts +0 -23
  68. package/src/actor-store/db/schema/record-blob.ts +0 -8
  69. package/src/actor-store/db/schema/record.ts +0 -14
  70. package/src/actor-store/db/schema/repo-block.ts +0 -10
  71. package/src/actor-store/db/schema/repo-root.ts +0 -10
  72. package/src/actor-store/migrate.ts +0 -39
  73. package/src/actor-store/preference/reader.ts +0 -48
  74. package/src/actor-store/preference/transactor.ts +0 -55
  75. package/src/actor-store/preference/util.ts +0 -39
  76. package/src/actor-store/record/reader.ts +0 -374
  77. package/src/actor-store/record/transactor.ts +0 -111
  78. package/src/actor-store/repo/reader.ts +0 -31
  79. package/src/actor-store/repo/sql-repo-reader.ts +0 -150
  80. package/src/actor-store/repo/sql-repo-transactor.ts +0 -105
  81. package/src/actor-store/repo/transactor.ts +0 -227
  82. package/src/api/app/bsky/actor/getPreferences.ts +0 -57
  83. package/src/api/app/bsky/actor/getProfile.ts +0 -41
  84. package/src/api/app/bsky/actor/getProfiles.ts +0 -51
  85. package/src/api/app/bsky/actor/index.ts +0 -13
  86. package/src/api/app/bsky/actor/putPreferences.ts +0 -62
  87. package/src/api/app/bsky/feed/getActorLikes.ts +0 -63
  88. package/src/api/app/bsky/feed/getAuthorFeed.ts +0 -84
  89. package/src/api/app/bsky/feed/getFeed.ts +0 -47
  90. package/src/api/app/bsky/feed/getPostThread.ts +0 -251
  91. package/src/api/app/bsky/feed/getTimeline.ts +0 -50
  92. package/src/api/app/bsky/feed/index.ts +0 -15
  93. package/src/api/app/bsky/index.ts +0 -11
  94. package/src/api/app/bsky/notification/index.ts +0 -7
  95. package/src/api/app/bsky/notification/registerPush.ts +0 -71
  96. package/src/api/app/bsky/util/resolver.ts +0 -20
  97. package/src/api/com/atproto/admin/deleteAccount.ts +0 -22
  98. package/src/api/com/atproto/admin/disableAccountInvites.ts +0 -18
  99. package/src/api/com/atproto/admin/disableInviteCodes.ts +0 -21
  100. package/src/api/com/atproto/admin/enableAccountInvites.ts +0 -18
  101. package/src/api/com/atproto/admin/getAccountInfo.ts +0 -32
  102. package/src/api/com/atproto/admin/getAccountInfos.ts +0 -34
  103. package/src/api/com/atproto/admin/getInviteCodes.ts +0 -126
  104. package/src/api/com/atproto/admin/getSubjectStatus.ts +0 -76
  105. package/src/api/com/atproto/admin/index.ts +0 -31
  106. package/src/api/com/atproto/admin/sendEmail.ts +0 -47
  107. package/src/api/com/atproto/admin/updateAccountEmail.ts +0 -32
  108. package/src/api/com/atproto/admin/updateAccountHandle.ts +0 -47
  109. package/src/api/com/atproto/admin/updateAccountPassword.ts +0 -34
  110. package/src/api/com/atproto/admin/updateSubjectStatus.ts +0 -68
  111. package/src/api/com/atproto/admin/util.ts +0 -66
  112. package/src/api/com/atproto/identity/getRecommendedDidCredentials.ts +0 -50
  113. package/src/api/com/atproto/identity/index.ts +0 -17
  114. package/src/api/com/atproto/identity/requestPlcOperationSignature.ts +0 -59
  115. package/src/api/com/atproto/identity/resolveHandle.ts +0 -50
  116. package/src/api/com/atproto/identity/signPlcOperation.ts +0 -85
  117. package/src/api/com/atproto/identity/submitPlcOperation.ts +0 -65
  118. package/src/api/com/atproto/identity/updateHandle.ts +0 -66
  119. package/src/api/com/atproto/index.ts +0 -19
  120. package/src/api/com/atproto/moderation/createReport.ts +0 -41
  121. package/src/api/com/atproto/moderation/index.ts +0 -7
  122. package/src/api/com/atproto/repo/applyWrites.ts +0 -226
  123. package/src/api/com/atproto/repo/createRecord.ts +0 -143
  124. package/src/api/com/atproto/repo/deleteRecord.ts +0 -122
  125. package/src/api/com/atproto/repo/describeRepo.ts +0 -43
  126. package/src/api/com/atproto/repo/getRecord.ts +0 -40
  127. package/src/api/com/atproto/repo/importRepo.ts +0 -101
  128. package/src/api/com/atproto/repo/index.ts +0 -25
  129. package/src/api/com/atproto/repo/listMissingBlobs.ts +0 -29
  130. package/src/api/com/atproto/repo/listRecords.ts +0 -36
  131. package/src/api/com/atproto/repo/putRecord.ts +0 -211
  132. package/src/api/com/atproto/repo/uploadBlob.ts +0 -60
  133. package/src/api/com/atproto/server/activateAccount.ts +0 -37
  134. package/src/api/com/atproto/server/checkAccountStatus.ts +0 -51
  135. package/src/api/com/atproto/server/confirmEmail.ts +0 -39
  136. package/src/api/com/atproto/server/createAccount.ts +0 -327
  137. package/src/api/com/atproto/server/createAppPassword.ts +0 -53
  138. package/src/api/com/atproto/server/createInviteCode.ts +0 -38
  139. package/src/api/com/atproto/server/createInviteCodes.ts +0 -42
  140. package/src/api/com/atproto/server/createSession.ts +0 -97
  141. package/src/api/com/atproto/server/deactivateAccount.ts +0 -41
  142. package/src/api/com/atproto/server/deleteAccount.ts +0 -85
  143. package/src/api/com/atproto/server/deleteSession.ts +0 -23
  144. package/src/api/com/atproto/server/describeServer.ts +0 -29
  145. package/src/api/com/atproto/server/getAccountInviteCodes.ts +0 -142
  146. package/src/api/com/atproto/server/getServiceAuth.ts +0 -111
  147. package/src/api/com/atproto/server/getSession.ts +0 -80
  148. package/src/api/com/atproto/server/index.ts +0 -55
  149. package/src/api/com/atproto/server/listAppPasswords.ts +0 -45
  150. package/src/api/com/atproto/server/refreshSession.ts +0 -72
  151. package/src/api/com/atproto/server/requestAccountDelete.ts +0 -66
  152. package/src/api/com/atproto/server/requestEmailConfirmation.ts +0 -68
  153. package/src/api/com/atproto/server/requestEmailUpdate.ts +0 -86
  154. package/src/api/com/atproto/server/requestPasswordReset.ts +0 -52
  155. package/src/api/com/atproto/server/reserveSigningKey.ts +0 -15
  156. package/src/api/com/atproto/server/resetPassword.ts +0 -50
  157. package/src/api/com/atproto/server/revokeAppPassword.ts +0 -42
  158. package/src/api/com/atproto/server/updateEmail.ts +0 -52
  159. package/src/api/com/atproto/server/util.ts +0 -136
  160. package/src/api/com/atproto/sync/deprecated/getCheckout.ts +0 -27
  161. package/src/api/com/atproto/sync/deprecated/getHead.ts +0 -33
  162. package/src/api/com/atproto/sync/getBlob.ts +0 -61
  163. package/src/api/com/atproto/sync/getBlocks.ts +0 -37
  164. package/src/api/com/atproto/sync/getLatestCommit.ts +0 -33
  165. package/src/api/com/atproto/sync/getRecord.ts +0 -49
  166. package/src/api/com/atproto/sync/getRepo.ts +0 -75
  167. package/src/api/com/atproto/sync/getRepoStatus.ts +0 -34
  168. package/src/api/com/atproto/sync/index.ts +0 -27
  169. package/src/api/com/atproto/sync/listBlobs.ts +0 -33
  170. package/src/api/com/atproto/sync/listRepos.ts +0 -74
  171. package/src/api/com/atproto/sync/subscribeRepos.ts +0 -73
  172. package/src/api/com/atproto/sync/util.ts +0 -37
  173. package/src/api/com/atproto/temp/checkSignupQueue.ts +0 -34
  174. package/src/api/com/atproto/temp/index.ts +0 -7
  175. package/src/api/index.ts +0 -10
  176. package/src/api/proxy.ts +0 -44
  177. package/src/app-view.ts +0 -24
  178. package/src/auth-output.ts +0 -52
  179. package/src/auth-routes.ts +0 -64
  180. package/src/auth-scope.ts +0 -40
  181. package/src/auth-verifier.ts +0 -668
  182. package/src/background.ts +0 -65
  183. package/src/basic-routes.ts +0 -52
  184. package/src/bsky-app-view.ts +0 -33
  185. package/src/config/config.ts +0 -553
  186. package/src/config/env.ts +0 -167
  187. package/src/config/index.ts +0 -3
  188. package/src/config/secrets.ts +0 -54
  189. package/src/context.ts +0 -523
  190. package/src/crawlers.ts +0 -46
  191. package/src/db/cast.ts +0 -52
  192. package/src/db/db.ts +0 -140
  193. package/src/db/index.ts +0 -4
  194. package/src/db/migrator.ts +0 -40
  195. package/src/db/pagination.ts +0 -132
  196. package/src/db/tables/moderation.ts +0 -80
  197. package/src/db/util.ts +0 -108
  198. package/src/did-cache/db/index.ts +0 -21
  199. package/src/did-cache/db/migrations.ts +0 -17
  200. package/src/did-cache/db/schema.ts +0 -9
  201. package/src/did-cache/index.ts +0 -131
  202. package/src/disk-blobstore.ts +0 -172
  203. package/src/error.ts +0 -19
  204. package/src/handle/explicit-slurs.ts +0 -22
  205. package/src/handle/index.ts +0 -49
  206. package/src/handle/reserved.ts +0 -1059
  207. package/src/image/image-url-builder.ts +0 -16
  208. package/src/index.ts +0 -189
  209. package/src/lexicons.ts +0 -4
  210. package/src/logger.ts +0 -35
  211. package/src/mailer/index.ts +0 -111
  212. package/src/mailer/moderation.ts +0 -33
  213. package/src/mailer/templates/confirm-email.d.ts +0 -4
  214. package/src/mailer/templates/confirm-email.hbs +0 -158
  215. package/src/mailer/templates/delete-account.d.ts +0 -4
  216. package/src/mailer/templates/delete-account.hbs +0 -156
  217. package/src/mailer/templates/plc-operation.d.ts +0 -4
  218. package/src/mailer/templates/plc-operation.hbs +0 -153
  219. package/src/mailer/templates/reset-password.d.ts +0 -4
  220. package/src/mailer/templates/reset-password.hbs +0 -154
  221. package/src/mailer/templates/update-email.d.ts +0 -4
  222. package/src/mailer/templates/update-email.hbs +0 -153
  223. package/src/mailer/templates.ts +0 -17
  224. package/src/pipethrough.ts +0 -716
  225. package/src/rate-limits.ts +0 -59
  226. package/src/read-after-write/index.ts +0 -3
  227. package/src/read-after-write/types.ts +0 -35
  228. package/src/read-after-write/util.ts +0 -101
  229. package/src/read-after-write/viewer.ts +0 -290
  230. package/src/redis.ts +0 -25
  231. package/src/repo/index.ts +0 -2
  232. package/src/repo/prepare.ts +0 -266
  233. package/src/repo/types.ts +0 -65
  234. package/src/scripts/README.md +0 -40
  235. package/src/scripts/index.ts +0 -20
  236. package/src/scripts/publish-identity.ts +0 -60
  237. package/src/scripts/rebuild-repo.ts +0 -119
  238. package/src/scripts/rotate-keys.ts +0 -146
  239. package/src/scripts/sequencer-recovery/index.ts +0 -23
  240. package/src/scripts/sequencer-recovery/recoverer.ts +0 -297
  241. package/src/scripts/sequencer-recovery/recovery-db.ts +0 -65
  242. package/src/scripts/sequencer-recovery/repair-repos.ts +0 -49
  243. package/src/scripts/sequencer-recovery/user-queues.ts +0 -44
  244. package/src/scripts/util.ts +0 -7
  245. package/src/sequencer/db/index.ts +0 -21
  246. package/src/sequencer/db/migrations/001-init.ts +0 -35
  247. package/src/sequencer/db/migrations/index.ts +0 -5
  248. package/src/sequencer/db/schema.ts +0 -19
  249. package/src/sequencer/events.ts +0 -199
  250. package/src/sequencer/index.ts +0 -3
  251. package/src/sequencer/outbox.ts +0 -123
  252. package/src/sequencer/sequencer.ts +0 -290
  253. package/src/util/compression.ts +0 -16
  254. package/src/util/debug.ts +0 -10
  255. package/src/util/http.ts +0 -31
  256. package/src/util/types.ts +0 -7
  257. package/src/well-known.ts +0 -30
  258. package/test.env +0 -2
  259. package/tests/__snapshots__/takedown-appeal.test.ts.snap +0 -43
  260. package/tests/_oauth_client_assets_middleware.ts +0 -23
  261. package/tests/_puppeteer.ts +0 -147
  262. package/tests/_types.d.ts +0 -16
  263. package/tests/_util.ts +0 -191
  264. package/tests/account-deactivation.test.ts +0 -204
  265. package/tests/account-deletion.test.ts +0 -300
  266. package/tests/account-manager.test.ts +0 -462
  267. package/tests/account-migration.test.ts +0 -221
  268. package/tests/account-status.test.ts +0 -206
  269. package/tests/account.test.ts +0 -595
  270. package/tests/app-passwords.test.ts +0 -262
  271. package/tests/auth.test.ts +0 -405
  272. package/tests/blob-deletes.test.ts +0 -204
  273. package/tests/create-post.test.ts +0 -79
  274. package/tests/crud.test.ts +0 -1595
  275. package/tests/db.test.ts +0 -170
  276. package/tests/email-confirmation.test.ts +0 -227
  277. package/tests/entryway-mock.ts +0 -323
  278. package/tests/entryway.test.ts +0 -145
  279. package/tests/file-uploads.test.ts +0 -301
  280. package/tests/get-service-auth.test.ts +0 -81
  281. package/tests/handle-validation.test.ts +0 -56
  282. package/tests/handles.test.ts +0 -274
  283. package/tests/invite-codes.test.ts +0 -367
  284. package/tests/invites-admin.test.ts +0 -252
  285. package/tests/moderation.test.ts +0 -280
  286. package/tests/moderator-auth.test.ts +0 -177
  287. package/tests/oauth.test.ts +0 -334
  288. package/tests/plc-operations.test.ts +0 -239
  289. package/tests/preferences.test.ts +0 -352
  290. package/tests/proxied/__snapshots__/admin.test.ts.snap +0 -516
  291. package/tests/proxied/__snapshots__/feedgen.test.ts.snap +0 -215
  292. package/tests/proxied/__snapshots__/views.test.ts.snap +0 -4456
  293. package/tests/proxied/admin.test.ts +0 -340
  294. package/tests/proxied/feedgen.test.ts +0 -66
  295. package/tests/proxied/notif.test.ts +0 -85
  296. package/tests/proxied/procedures.test.ts +0 -175
  297. package/tests/proxied/proxy-catchall.test.ts +0 -256
  298. package/tests/proxied/proxy-header.test.ts +0 -239
  299. package/tests/proxied/proxy-oauth-aud.test.ts +0 -182
  300. package/tests/proxied/read-after-write.test.ts +0 -382
  301. package/tests/proxied/views.test.ts +0 -608
  302. package/tests/races.test.ts +0 -89
  303. package/tests/rate-limits.test.ts +0 -70
  304. package/tests/recovery.test.ts +0 -180
  305. package/tests/seeds/basic.ts +0 -165
  306. package/tests/seeds/follows.ts +0 -57
  307. package/tests/seeds/likes.ts +0 -14
  308. package/tests/seeds/reposts.ts +0 -18
  309. package/tests/seeds/thread.ts +0 -40
  310. package/tests/seeds/users-bulk.ts +0 -246
  311. package/tests/seeds/users.ts +0 -67
  312. package/tests/sequencer.test.ts +0 -251
  313. package/tests/server.test.ts +0 -151
  314. package/tests/sync/invertible-ops.test.ts +0 -99
  315. package/tests/sync/list.test.ts +0 -43
  316. package/tests/sync/subscribe-repos.test.ts +0 -672
  317. package/tests/sync/sync.test.ts +0 -316
  318. package/tests/takedown-appeal.test.ts +0 -166
  319. package/tsconfig.build.json +0 -9
  320. package/tsconfig.build.tsbuildinfo +0 -1
  321. package/tsconfig.json +0 -7
  322. 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
- }