@atproto/bsky 0.0.11 → 0.0.12

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 (155) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/api/com/atproto/admin/util.d.ts +5 -0
  3. package/dist/context.d.ts +6 -1
  4. package/dist/db/index.js +51 -2
  5. package/dist/db/index.js.map +3 -3
  6. package/dist/db/migrations/20230929T192920807Z-record-cursor-indexes.d.ts +3 -0
  7. package/dist/db/migrations/index.d.ts +1 -0
  8. package/dist/did-cache.d.ts +2 -2
  9. package/dist/index.d.ts +2 -0
  10. package/dist/index.js +1381 -530
  11. package/dist/index.js.map +3 -3
  12. package/dist/lexicon/index.d.ts +8 -0
  13. package/dist/lexicon/lexicons.d.ts +231 -3
  14. package/dist/lexicon/types/app/bsky/actor/defs.d.ts +1 -0
  15. package/dist/lexicon/types/com/atproto/admin/defs.d.ts +28 -0
  16. package/dist/lexicon/types/com/atproto/admin/getAccountInfo.d.ts +29 -0
  17. package/dist/lexicon/types/com/atproto/admin/getSubjectStatus.d.ts +39 -0
  18. package/dist/lexicon/types/com/atproto/admin/searchRepos.d.ts +0 -1
  19. package/dist/lexicon/types/com/atproto/admin/updateSubjectStatus.d.ts +46 -0
  20. package/dist/lexicon/types/com/atproto/server/createAccount.d.ts +2 -0
  21. package/dist/lexicon/types/com/atproto/server/createSession.d.ts +1 -0
  22. package/dist/lexicon/types/com/atproto/server/refreshSession.d.ts +1 -0
  23. package/dist/lexicon/types/com/atproto/server/reserveSigningKey.d.ts +30 -0
  24. package/dist/lexicon/types/com/atproto/sync/listRepos.d.ts +1 -0
  25. package/dist/services/actor/types.d.ts +1 -0
  26. package/dist/services/graph/index.d.ts +2 -0
  27. package/dist/services/moderation/index.d.ts +13 -3
  28. package/package.json +13 -14
  29. package/src/api/app/bsky/feed/getAuthorFeed.ts +2 -2
  30. package/src/api/app/bsky/feed/getPostThread.ts +2 -2
  31. package/src/api/app/bsky/notification/listNotifications.ts +33 -22
  32. package/src/api/com/atproto/admin/getModerationAction.ts +28 -2
  33. package/src/api/com/atproto/admin/getModerationReport.ts +27 -2
  34. package/src/api/com/atproto/admin/getRecord.ts +14 -2
  35. package/src/api/com/atproto/admin/getRepo.ts +13 -2
  36. package/src/api/com/atproto/admin/reverseModerationAction.ts +31 -5
  37. package/src/api/com/atproto/admin/searchRepos.ts +2 -5
  38. package/src/api/com/atproto/admin/takeModerationAction.ts +41 -7
  39. package/src/api/com/atproto/admin/util.ts +50 -0
  40. package/src/api/well-known.ts +8 -0
  41. package/src/auth.ts +12 -5
  42. package/src/auto-moderator/index.ts +1 -0
  43. package/src/context.ts +25 -1
  44. package/src/db/migrations/20230929T192920807Z-record-cursor-indexes.ts +40 -0
  45. package/src/db/migrations/index.ts +1 -0
  46. package/src/did-cache.ts +29 -14
  47. package/src/feed-gen/with-friends.ts +2 -2
  48. package/src/index.ts +4 -1
  49. package/src/indexer/subscription.ts +1 -21
  50. package/src/lexicon/index.ts +48 -0
  51. package/src/lexicon/lexicons.ts +246 -4
  52. package/src/lexicon/types/app/bsky/actor/defs.ts +1 -0
  53. package/src/lexicon/types/com/atproto/admin/defs.ts +61 -0
  54. package/src/lexicon/types/com/atproto/admin/getAccountInfo.ts +41 -0
  55. package/src/lexicon/types/com/atproto/admin/getSubjectStatus.ts +54 -0
  56. package/src/lexicon/types/com/atproto/admin/searchRepos.ts +0 -1
  57. package/src/lexicon/types/com/atproto/admin/updateSubjectStatus.ts +61 -0
  58. package/src/lexicon/types/com/atproto/server/createAccount.ts +2 -0
  59. package/src/lexicon/types/com/atproto/server/createSession.ts +1 -0
  60. package/src/lexicon/types/com/atproto/server/refreshSession.ts +1 -0
  61. package/src/lexicon/types/com/atproto/server/reserveSigningKey.ts +44 -0
  62. package/src/lexicon/types/com/atproto/sync/listRepos.ts +1 -0
  63. package/src/logger.ts +8 -0
  64. package/src/services/actor/index.ts +7 -1
  65. package/src/services/actor/types.ts +1 -0
  66. package/src/services/actor/views.ts +26 -8
  67. package/src/services/graph/index.ts +26 -7
  68. package/src/services/indexing/index.ts +15 -17
  69. package/src/services/moderation/index.ts +94 -14
  70. package/src/services/moderation/views.ts +1 -0
  71. package/tests/__snapshots__/feed-generation.test.ts.snap +12 -12
  72. package/tests/__snapshots__/indexing.test.ts.snap +4 -4
  73. package/tests/admin/__snapshots__/get-moderation-action.test.ts.snap +172 -0
  74. package/tests/admin/__snapshots__/get-moderation-actions.test.ts.snap +178 -0
  75. package/tests/admin/__snapshots__/get-moderation-report.test.ts.snap +177 -0
  76. package/tests/admin/__snapshots__/get-moderation-reports.test.ts.snap +307 -0
  77. package/tests/admin/__snapshots__/get-record.test.ts.snap +275 -0
  78. package/tests/admin/__snapshots__/get-repo.test.ts.snap +103 -0
  79. package/tests/admin/get-moderation-action.test.ts +100 -0
  80. package/tests/admin/get-moderation-actions.test.ts +164 -0
  81. package/tests/admin/get-moderation-report.test.ts +100 -0
  82. package/tests/admin/get-moderation-reports.test.ts +332 -0
  83. package/tests/admin/get-record.test.ts +115 -0
  84. package/tests/admin/get-repo.test.ts +101 -0
  85. package/tests/{moderation.test.ts → admin/moderation.test.ts} +107 -9
  86. package/tests/admin/repo-search.test.ts +124 -0
  87. package/tests/algos/hot-classic.test.ts +3 -5
  88. package/tests/algos/whats-hot.test.ts +3 -5
  89. package/tests/algos/with-friends.test.ts +2 -4
  90. package/tests/auth.test.ts +64 -0
  91. package/tests/auto-moderator/fuzzy-matcher.test.ts +2 -3
  92. package/tests/auto-moderator/labeler.test.ts +5 -7
  93. package/tests/auto-moderator/takedowns.test.ts +11 -12
  94. package/tests/blob-resolver.test.ts +1 -3
  95. package/tests/did-cache.test.ts +2 -5
  96. package/tests/feed-generation.test.ts +8 -6
  97. package/tests/handle-invalidation.test.ts +2 -3
  98. package/tests/image/server.test.ts +1 -4
  99. package/tests/image/sharp.test.ts +1 -1
  100. package/tests/indexing.test.ts +4 -4
  101. package/tests/notification-server.test.ts +2 -3
  102. package/tests/pipeline/backpressure.test.ts +2 -3
  103. package/tests/pipeline/reingest.test.ts +7 -4
  104. package/tests/pipeline/repartition.test.ts +2 -3
  105. package/tests/reprocessing.test.ts +2 -6
  106. package/tests/seeds/basic.ts +4 -4
  107. package/tests/seeds/follows.ts +1 -1
  108. package/tests/seeds/likes.ts +1 -1
  109. package/tests/seeds/reposts.ts +1 -1
  110. package/tests/seeds/users-bulk.ts +1 -1
  111. package/tests/seeds/users.ts +1 -1
  112. package/tests/server.test.ts +1 -3
  113. package/tests/subscription/repo.test.ts +2 -4
  114. package/tests/views/__snapshots__/author-feed.test.ts.snap +24 -24
  115. package/tests/views/__snapshots__/block-lists.test.ts.snap +42 -7
  116. package/tests/views/__snapshots__/blocks.test.ts.snap +2 -2
  117. package/tests/views/__snapshots__/list-feed.test.ts.snap +6 -6
  118. package/tests/views/__snapshots__/mute-lists.test.ts.snap +15 -4
  119. package/tests/views/__snapshots__/mutes.test.ts.snap +2 -2
  120. package/tests/views/__snapshots__/notifications.test.ts.snap +2 -2
  121. package/tests/views/__snapshots__/posts.test.ts.snap +8 -8
  122. package/tests/views/__snapshots__/thread.test.ts.snap +10 -10
  123. package/tests/views/__snapshots__/timeline.test.ts.snap +58 -58
  124. package/tests/views/actor-likes.test.ts +2 -3
  125. package/tests/views/actor-search.test.ts +5 -5
  126. package/tests/views/admin/repo-search.test.ts +2 -4
  127. package/tests/views/author-feed.test.ts +2 -4
  128. package/tests/views/block-lists.test.ts +34 -7
  129. package/tests/views/blocks.test.ts +6 -3
  130. package/tests/views/follows.test.ts +2 -4
  131. package/tests/views/likes.test.ts +2 -5
  132. package/tests/views/list-feed.test.ts +2 -4
  133. package/tests/views/mute-lists.test.ts +23 -5
  134. package/tests/views/mutes.test.ts +2 -5
  135. package/tests/views/notifications.test.ts +2 -4
  136. package/tests/views/posts.test.ts +2 -5
  137. package/tests/views/profile.test.ts +4 -5
  138. package/tests/views/reposts.test.ts +2 -4
  139. package/tests/views/suggested-follows.test.ts +2 -3
  140. package/tests/views/suggestions.test.ts +2 -4
  141. package/tests/views/thread.test.ts +2 -4
  142. package/tests/views/threadgating.test.ts +2 -3
  143. package/tests/views/timeline.test.ts +2 -4
  144. package/dist/env.d.ts +0 -1
  145. package/example.dev.env +0 -5
  146. package/src/env.ts +0 -9
  147. package/tests/seeds/client.ts +0 -466
  148. /package/tests/{__snapshots__ → admin/__snapshots__}/moderation.test.ts.snap +0 -0
  149. /package/tests/{image/fixtures → sample-img}/at.png +0 -0
  150. /package/tests/{image/fixtures → sample-img}/hd-key.jpg +0 -0
  151. /package/tests/{image/fixtures → sample-img}/key-alt.jpg +0 -0
  152. /package/tests/{image/fixtures → sample-img}/key-landscape-large.jpg +0 -0
  153. /package/tests/{image/fixtures → sample-img}/key-landscape-small.jpg +0 -0
  154. /package/tests/{image/fixtures → sample-img}/key-portrait-large.jpg +0 -0
  155. /package/tests/{image/fixtures → sample-img}/key-portrait-small.jpg +0 -0
@@ -0,0 +1,115 @@
1
+ import { SeedClient, TestNetwork } from '@atproto/dev-env'
2
+ import AtpAgent from '@atproto/api'
3
+ import { AtUri } from '@atproto/syntax'
4
+ import {
5
+ ACKNOWLEDGE,
6
+ TAKEDOWN,
7
+ } from '@atproto/api/src/client/types/com/atproto/admin/defs'
8
+ import {
9
+ REASONOTHER,
10
+ REASONSPAM,
11
+ } from '../../src/lexicon/types/com/atproto/moderation/defs'
12
+ import { forSnapshot } from '../_util'
13
+ import basicSeed from '../seeds/basic'
14
+
15
+ describe('admin get record view', () => {
16
+ let network: TestNetwork
17
+ let agent: AtpAgent
18
+ let sc: SeedClient
19
+
20
+ beforeAll(async () => {
21
+ network = await TestNetwork.create({
22
+ dbPostgresSchema: 'views_admin_get_record',
23
+ })
24
+ agent = network.pds.getClient()
25
+ sc = network.getSeedClient()
26
+ await basicSeed(sc)
27
+ })
28
+
29
+ afterAll(async () => {
30
+ await network.close()
31
+ })
32
+
33
+ beforeAll(async () => {
34
+ const acknowledge = await sc.takeModerationAction({
35
+ action: ACKNOWLEDGE,
36
+ subject: {
37
+ $type: 'com.atproto.repo.strongRef',
38
+ uri: sc.posts[sc.dids.alice][0].ref.uriStr,
39
+ cid: sc.posts[sc.dids.alice][0].ref.cidStr,
40
+ },
41
+ })
42
+ await sc.createReport({
43
+ reportedBy: sc.dids.bob,
44
+ reasonType: REASONSPAM,
45
+ subject: {
46
+ $type: 'com.atproto.repo.strongRef',
47
+ uri: sc.posts[sc.dids.alice][0].ref.uriStr,
48
+ cid: sc.posts[sc.dids.alice][0].ref.cidStr,
49
+ },
50
+ })
51
+ await sc.createReport({
52
+ reportedBy: sc.dids.carol,
53
+ reasonType: REASONOTHER,
54
+ reason: 'defamation',
55
+ subject: {
56
+ $type: 'com.atproto.repo.strongRef',
57
+ uri: sc.posts[sc.dids.alice][0].ref.uriStr,
58
+ cid: sc.posts[sc.dids.alice][0].ref.cidStr,
59
+ },
60
+ })
61
+ await sc.reverseModerationAction({ id: acknowledge.id })
62
+ await sc.takeModerationAction({
63
+ action: TAKEDOWN,
64
+ subject: {
65
+ $type: 'com.atproto.repo.strongRef',
66
+ uri: sc.posts[sc.dids.alice][0].ref.uriStr,
67
+ cid: sc.posts[sc.dids.alice][0].ref.cidStr,
68
+ },
69
+ })
70
+ })
71
+
72
+ it('gets a record by uri, even when taken down.', async () => {
73
+ const result = await agent.api.com.atproto.admin.getRecord(
74
+ { uri: sc.posts[sc.dids.alice][0].ref.uriStr },
75
+ { headers: network.pds.adminAuthHeaders() },
76
+ )
77
+ expect(forSnapshot(result.data)).toMatchSnapshot()
78
+ })
79
+
80
+ it('gets a record by uri and cid.', async () => {
81
+ const result = await agent.api.com.atproto.admin.getRecord(
82
+ {
83
+ uri: sc.posts[sc.dids.alice][0].ref.uriStr,
84
+ cid: sc.posts[sc.dids.alice][0].ref.cidStr,
85
+ },
86
+ { headers: network.pds.adminAuthHeaders() },
87
+ )
88
+ expect(forSnapshot(result.data)).toMatchSnapshot()
89
+ })
90
+
91
+ it('fails when record does not exist.', async () => {
92
+ const promise = agent.api.com.atproto.admin.getRecord(
93
+ {
94
+ uri: AtUri.make(
95
+ sc.dids.alice,
96
+ 'app.bsky.feed.post',
97
+ 'badrkey',
98
+ ).toString(),
99
+ },
100
+ { headers: network.pds.adminAuthHeaders() },
101
+ )
102
+ await expect(promise).rejects.toThrow('Record not found')
103
+ })
104
+
105
+ it('fails when record cid does not exist.', async () => {
106
+ const promise = agent.api.com.atproto.admin.getRecord(
107
+ {
108
+ uri: sc.posts[sc.dids.alice][0].ref.uriStr,
109
+ cid: sc.posts[sc.dids.alice][1].ref.cidStr, // Mismatching cid
110
+ },
111
+ { headers: network.pds.adminAuthHeaders() },
112
+ )
113
+ await expect(promise).rejects.toThrow('Record not found')
114
+ })
115
+ })
@@ -0,0 +1,101 @@
1
+ import { SeedClient, TestNetwork } from '@atproto/dev-env'
2
+ import AtpAgent from '@atproto/api'
3
+ import {
4
+ ACKNOWLEDGE,
5
+ TAKEDOWN,
6
+ } from '@atproto/api/src/client/types/com/atproto/admin/defs'
7
+ import {
8
+ REASONOTHER,
9
+ REASONSPAM,
10
+ } from '../../src/lexicon/types/com/atproto/moderation/defs'
11
+ import { forSnapshot } from '../_util'
12
+ import basicSeed from '../seeds/basic'
13
+
14
+ describe('admin get repo view', () => {
15
+ let network: TestNetwork
16
+ let agent: AtpAgent
17
+ let sc: SeedClient
18
+
19
+ beforeAll(async () => {
20
+ network = await TestNetwork.create({
21
+ dbPostgresSchema: 'views_admin_get_repo',
22
+ })
23
+ agent = network.pds.getClient()
24
+ sc = network.getSeedClient()
25
+ await basicSeed(sc)
26
+ })
27
+
28
+ afterAll(async () => {
29
+ await network.close()
30
+ })
31
+
32
+ beforeAll(async () => {
33
+ const acknowledge = await sc.takeModerationAction({
34
+ action: ACKNOWLEDGE,
35
+ subject: {
36
+ $type: 'com.atproto.admin.defs#repoRef',
37
+ did: sc.dids.alice,
38
+ },
39
+ })
40
+ await sc.createReport({
41
+ reportedBy: sc.dids.bob,
42
+ reasonType: REASONSPAM,
43
+ subject: {
44
+ $type: 'com.atproto.admin.defs#repoRef',
45
+ did: sc.dids.alice,
46
+ },
47
+ })
48
+ await sc.createReport({
49
+ reportedBy: sc.dids.carol,
50
+ reasonType: REASONOTHER,
51
+ reason: 'defamation',
52
+ subject: {
53
+ $type: 'com.atproto.admin.defs#repoRef',
54
+ did: sc.dids.alice,
55
+ },
56
+ })
57
+ await sc.reverseModerationAction({ id: acknowledge.id })
58
+ await sc.takeModerationAction({
59
+ action: TAKEDOWN,
60
+ subject: {
61
+ $type: 'com.atproto.admin.defs#repoRef',
62
+ did: sc.dids.alice,
63
+ },
64
+ })
65
+ })
66
+
67
+ it('gets a repo by did, even when taken down.', async () => {
68
+ const result = await agent.api.com.atproto.admin.getRepo(
69
+ { did: sc.dids.alice },
70
+ { headers: network.pds.adminAuthHeaders() },
71
+ )
72
+ expect(forSnapshot(result.data)).toMatchSnapshot()
73
+ })
74
+
75
+ it('does not include account emails for triage mods.', async () => {
76
+ const { data: admin } = await agent.api.com.atproto.admin.getRepo(
77
+ { did: sc.dids.bob },
78
+ { headers: network.pds.adminAuthHeaders() },
79
+ )
80
+ const { data: moderator } = await agent.api.com.atproto.admin.getRepo(
81
+ { did: sc.dids.bob },
82
+ { headers: network.pds.adminAuthHeaders('moderator') },
83
+ )
84
+ const { data: triage } = await agent.api.com.atproto.admin.getRepo(
85
+ { did: sc.dids.bob },
86
+ { headers: network.pds.adminAuthHeaders('triage') },
87
+ )
88
+ expect(admin.email).toEqual('bob@test.com')
89
+ expect(moderator.email).toEqual('bob@test.com')
90
+ expect(triage.email).toBeUndefined()
91
+ expect(triage).toEqual({ ...admin, email: undefined })
92
+ })
93
+
94
+ it('fails when repo does not exist.', async () => {
95
+ const promise = agent.api.com.atproto.admin.getRepo(
96
+ { did: 'did:plc:doesnotexist' },
97
+ { headers: network.pds.adminAuthHeaders() },
98
+ )
99
+ await expect(promise).rejects.toThrow('Repo not found')
100
+ })
101
+ })
@@ -1,25 +1,25 @@
1
- import { TestNetwork } from '@atproto/dev-env'
1
+ import { TestNetwork, ImageRef, RecordRef, SeedClient } from '@atproto/dev-env'
2
2
  import { TID, cidForCbor } from '@atproto/common'
3
3
  import AtpAgent, { ComAtprotoAdminTakeModerationAction } from '@atproto/api'
4
4
  import { AtUri } from '@atproto/syntax'
5
- import { forSnapshot } from './_util'
6
- import { ImageRef, RecordRef, SeedClient } from './seeds/client'
7
- import basicSeed from './seeds/basic'
5
+ import { forSnapshot } from '../_util'
6
+ import basicSeed from '../seeds/basic'
8
7
  import {
9
8
  ACKNOWLEDGE,
10
9
  ESCALATE,
11
10
  FLAG,
12
11
  TAKEDOWN,
13
- } from '../src/lexicon/types/com/atproto/admin/defs'
12
+ } from '../../src/lexicon/types/com/atproto/admin/defs'
14
13
  import {
15
14
  REASONOTHER,
16
15
  REASONSPAM,
17
- } from '../src/lexicon/types/com/atproto/moderation/defs'
18
- import { PeriodicModerationActionReversal } from '../src'
16
+ } from '../../src/lexicon/types/com/atproto/moderation/defs'
17
+ import { PeriodicModerationActionReversal } from '../../src'
19
18
 
20
19
  describe('moderation', () => {
21
20
  let network: TestNetwork
22
21
  let agent: AtpAgent
22
+ let pdsAgent: AtpAgent
23
23
  let sc: SeedClient
24
24
 
25
25
  beforeAll(async () => {
@@ -27,8 +27,8 @@ describe('moderation', () => {
27
27
  dbPostgresSchema: 'bsky_moderation',
28
28
  })
29
29
  agent = network.bsky.getClient()
30
- const pdsAgent = network.pds.getClient()
31
- sc = new SeedClient(pdsAgent)
30
+ pdsAgent = network.pds.getClient()
31
+ sc = network.getSeedClient()
32
32
  await basicSeed(sc)
33
33
  await network.processAll()
34
34
  })
@@ -962,6 +962,82 @@ describe('moderation', () => {
962
962
  )
963
963
  })
964
964
 
965
+ it('fans out repo takedowns to pds', async () => {
966
+ const { data: action } =
967
+ await agent.api.com.atproto.admin.takeModerationAction(
968
+ {
969
+ action: TAKEDOWN,
970
+ createdBy: 'did:example:moderator',
971
+ reason: 'Y',
972
+ subject: {
973
+ $type: 'com.atproto.admin.defs#repoRef',
974
+ did: sc.dids.bob,
975
+ },
976
+ },
977
+ {
978
+ encoding: 'application/json',
979
+ headers: network.bsky.adminAuthHeaders(),
980
+ },
981
+ )
982
+
983
+ const res1 = await pdsAgent.api.com.atproto.admin.getSubjectStatus(
984
+ {
985
+ did: sc.dids.bob,
986
+ },
987
+ { headers: network.pds.adminAuthHeaders() },
988
+ )
989
+ expect(res1.data.takedown?.applied).toBe(true)
990
+
991
+ // cleanup
992
+ await reverse(action.id)
993
+
994
+ const res2 = await pdsAgent.api.com.atproto.admin.getSubjectStatus(
995
+ {
996
+ did: sc.dids.bob,
997
+ },
998
+ { headers: network.pds.adminAuthHeaders() },
999
+ )
1000
+ expect(res2.data.takedown?.applied).toBe(false)
1001
+ })
1002
+
1003
+ it('fans out record takedowns to pds', async () => {
1004
+ const post = sc.posts[sc.dids.bob][0]
1005
+ const uri = post.ref.uriStr
1006
+ const cid = post.ref.cidStr
1007
+ const { data: action } =
1008
+ await agent.api.com.atproto.admin.takeModerationAction(
1009
+ {
1010
+ action: TAKEDOWN,
1011
+ createdBy: 'did:example:moderator',
1012
+ reason: 'Y',
1013
+ subject: {
1014
+ $type: 'com.atproto.repo.strongRef',
1015
+ uri,
1016
+ cid,
1017
+ },
1018
+ },
1019
+ {
1020
+ encoding: 'application/json',
1021
+ headers: network.bsky.adminAuthHeaders(),
1022
+ },
1023
+ )
1024
+
1025
+ const res1 = await pdsAgent.api.com.atproto.admin.getSubjectStatus(
1026
+ { uri },
1027
+ { headers: network.pds.adminAuthHeaders() },
1028
+ )
1029
+ expect(res1.data.takedown?.applied).toBe(true)
1030
+
1031
+ // cleanup
1032
+ await reverse(action.id)
1033
+
1034
+ const res2 = await pdsAgent.api.com.atproto.admin.getSubjectStatus(
1035
+ { uri },
1036
+ { headers: network.pds.adminAuthHeaders() },
1037
+ )
1038
+ expect(res2.data.takedown?.applied).toBe(false)
1039
+ })
1040
+
965
1041
  it('allows full moderators to takedown.', async () => {
966
1042
  const { data: action } =
967
1043
  await agent.api.com.atproto.admin.takeModerationAction(
@@ -1161,6 +1237,17 @@ describe('moderation', () => {
1161
1237
  expect(await fetchImage.json()).toEqual({ message: 'Image not found' })
1162
1238
  })
1163
1239
 
1240
+ it('fans takedown out to pds', async () => {
1241
+ const res = await pdsAgent.api.com.atproto.admin.getSubjectStatus(
1242
+ {
1243
+ did: sc.dids.carol,
1244
+ blob: blob.image.ref.toString(),
1245
+ },
1246
+ { headers: network.pds.adminAuthHeaders() },
1247
+ )
1248
+ expect(res.data.takedown?.applied).toBe(true)
1249
+ })
1250
+
1164
1251
  it('restores blob when action is reversed.', async () => {
1165
1252
  await agent.api.com.atproto.admin.reverseModerationAction(
1166
1253
  {
@@ -1185,5 +1272,16 @@ describe('moderation', () => {
1185
1272
  const size = Number(fetchImage.headers.get('content-length'))
1186
1273
  expect(size).toBeGreaterThan(9000)
1187
1274
  })
1275
+
1276
+ it('fans reversal out to pds', async () => {
1277
+ const res = await pdsAgent.api.com.atproto.admin.getSubjectStatus(
1278
+ {
1279
+ did: sc.dids.carol,
1280
+ blob: blob.image.ref.toString(),
1281
+ },
1282
+ { headers: network.pds.adminAuthHeaders() },
1283
+ )
1284
+ expect(res.data.takedown?.applied).toBe(false)
1285
+ })
1188
1286
  })
1189
1287
  })
@@ -0,0 +1,124 @@
1
+ import { SeedClient, TestNetwork } from '@atproto/dev-env'
2
+ import AtpAgent from '@atproto/api'
3
+ import { TAKEDOWN } from '@atproto/api/src/client/types/com/atproto/admin/defs'
4
+ import { paginateAll } from '../_util'
5
+ import usersBulkSeed from '../seeds/users-bulk'
6
+
7
+ describe('admin repo search view', () => {
8
+ let network: TestNetwork
9
+ let agent: AtpAgent
10
+ let sc: SeedClient
11
+ let headers: { [s: string]: string }
12
+
13
+ beforeAll(async () => {
14
+ network = await TestNetwork.create({
15
+ dbPostgresSchema: 'views_admin_repo_search',
16
+ })
17
+ agent = network.pds.getClient()
18
+ sc = network.getSeedClient()
19
+ await usersBulkSeed(sc)
20
+ headers = network.pds.adminAuthHeaders()
21
+ })
22
+
23
+ afterAll(async () => {
24
+ await network.close()
25
+ })
26
+
27
+ beforeAll(async () => {
28
+ await sc.takeModerationAction({
29
+ action: TAKEDOWN,
30
+ subject: {
31
+ $type: 'com.atproto.admin.defs#repoRef',
32
+ did: sc.dids['cara-wiegand69.test'],
33
+ },
34
+ })
35
+ })
36
+
37
+ it('gives relevant results', async () => {
38
+ const result = await agent.api.com.atproto.admin.searchRepos(
39
+ { term: 'car' },
40
+ { headers },
41
+ )
42
+
43
+ const handles = result.data.repos.map((u) => u.handle)
44
+
45
+ const shouldContain = [
46
+ 'cara-wiegand69.test', // Present despite repo takedown
47
+ 'carlos6.test',
48
+ 'carolina-mcdermott77.test',
49
+ ]
50
+
51
+ shouldContain.forEach((handle) => expect(handles).toContain(handle))
52
+
53
+ const shouldNotContain = [
54
+ 'sven70.test',
55
+ 'hilario84.test',
56
+ 'santa-hermann78.test',
57
+ 'dylan61.test',
58
+ 'preston-harris.test',
59
+ 'loyce95.test',
60
+ 'melyna-zboncak.test',
61
+ ]
62
+
63
+ shouldNotContain.forEach((handle) => expect(handles).not.toContain(handle))
64
+ })
65
+
66
+ it('finds repo by did', async () => {
67
+ const term = sc.dids['cara-wiegand69.test']
68
+ const res = await agent.api.com.atproto.admin.searchRepos(
69
+ { term },
70
+ { headers },
71
+ )
72
+
73
+ expect(res.data.repos.length).toEqual(1)
74
+ expect(res.data.repos[0].did).toEqual(term)
75
+ })
76
+
77
+ it('paginates with term', async () => {
78
+ const results = (results) => results.flatMap((res) => res.users)
79
+ const paginator = async (cursor?: string) => {
80
+ const res = await agent.api.com.atproto.admin.searchRepos(
81
+ { term: 'p', cursor, limit: 3 },
82
+ { headers },
83
+ )
84
+ return res.data
85
+ }
86
+
87
+ const paginatedAll = await paginateAll(paginator)
88
+ paginatedAll.forEach((res) =>
89
+ expect(res.repos.length).toBeLessThanOrEqual(3),
90
+ )
91
+
92
+ const full = await agent.api.com.atproto.admin.searchRepos(
93
+ { term: 'p' },
94
+ { headers },
95
+ )
96
+
97
+ expect(full.data.repos.length).toBeGreaterThan(3)
98
+ expect(results(paginatedAll)).toEqual(results([full.data]))
99
+ })
100
+
101
+ it('paginates without term', async () => {
102
+ const results = (results) => results.flatMap((res) => res.repos)
103
+ const paginator = async (cursor?: string) => {
104
+ const res = await agent.api.com.atproto.admin.searchRepos(
105
+ { cursor, limit: 3 },
106
+ { headers },
107
+ )
108
+ return res.data
109
+ }
110
+
111
+ const paginatedAll = await paginateAll(paginator, 5)
112
+ paginatedAll.forEach((res) =>
113
+ expect(res.repos.length).toBeLessThanOrEqual(3),
114
+ )
115
+
116
+ const full = await agent.api.com.atproto.admin.searchRepos(
117
+ { limit: 15 },
118
+ { headers },
119
+ )
120
+
121
+ expect(full.data.repos.length).toEqual(15)
122
+ expect(results(paginatedAll)).toEqual(results([full.data]))
123
+ })
124
+ })
@@ -1,8 +1,7 @@
1
1
  import AtpAgent, { AtUri } from '@atproto/api'
2
- import { SeedClient } from '../seeds/client'
2
+ import { TestNetwork, SeedClient } from '@atproto/dev-env'
3
3
  import basicSeed from '../seeds/basic'
4
4
  import { makeAlgos } from '../../src'
5
- import { TestNetwork } from '@atproto/dev-env'
6
5
 
7
6
  describe('algo hot-classic', () => {
8
7
  let network: TestNetwork
@@ -26,8 +25,7 @@ describe('algo hot-classic', () => {
26
25
  bsky: { algos: makeAlgos(feedPublisherDid) },
27
26
  })
28
27
  agent = new AtpAgent({ service: network.bsky.url })
29
- const pdsAgent = new AtpAgent({ service: network.pds.url })
30
- sc = new SeedClient(pdsAgent)
28
+ sc = network.getSeedClient()
31
29
  await basicSeed(sc)
32
30
 
33
31
  alice = sc.dids.alice
@@ -43,7 +41,7 @@ describe('algo hot-classic', () => {
43
41
  it('returns well liked posts', async () => {
44
42
  const img = await sc.uploadFile(
45
43
  alice,
46
- 'tests/image/fixtures/key-landscape-small.jpg',
44
+ 'tests/sample-img/key-landscape-small.jpg',
47
45
  'image/jpeg',
48
46
  )
49
47
  const one = await sc.post(alice, 'first post', undefined, [img])
@@ -1,9 +1,8 @@
1
1
  import { HOUR } from '@atproto/common'
2
2
  import AtpAgent, { AtUri } from '@atproto/api'
3
- import { SeedClient } from '../seeds/client'
3
+ import { TestNetwork, SeedClient } from '@atproto/dev-env'
4
4
  import basicSeed from '../seeds/basic'
5
5
  import { makeAlgos } from '../../src'
6
- import { TestNetwork } from '@atproto/dev-env'
7
6
 
8
7
  describe.skip('algo whats-hot', () => {
9
8
  let network: TestNetwork
@@ -28,8 +27,7 @@ describe.skip('algo whats-hot', () => {
28
27
  bsky: { algos: makeAlgos(feedPublisherDid) },
29
28
  })
30
29
  agent = new AtpAgent({ service: network.bsky.url })
31
- const pdsAgent = new AtpAgent({ service: network.pds.url })
32
- sc = new SeedClient(pdsAgent)
30
+ sc = network.getSeedClient()
33
31
  await basicSeed(sc)
34
32
 
35
33
  alice = sc.dids.alice
@@ -46,7 +44,7 @@ describe.skip('algo whats-hot', () => {
46
44
  it('returns well liked posts', async () => {
47
45
  const img = await sc.uploadFile(
48
46
  alice,
49
- 'tests/image/fixtures/key-landscape-small.jpg',
47
+ 'tests/sample-img/key-landscape-small.jpg',
50
48
  'image/jpeg',
51
49
  )
52
50
  const one = await sc.post(carol, 'carol is in the chat')
@@ -1,8 +1,7 @@
1
1
  import AtpAgent, { AtUri } from '@atproto/api'
2
- import { RecordRef, SeedClient } from '../seeds/client'
3
2
  import userSeed from '../seeds/users'
4
3
  import { makeAlgos } from '../../src'
5
- import { TestNetwork } from '@atproto/dev-env'
4
+ import { TestNetwork, SeedClient, RecordRef } from '@atproto/dev-env'
6
5
 
7
6
  describe.skip('algo with friends', () => {
8
7
  let network: TestNetwork
@@ -28,8 +27,7 @@ describe.skip('algo with friends', () => {
28
27
  bsky: { algos: makeAlgos(feedPublisherDid) },
29
28
  })
30
29
  agent = new AtpAgent({ service: network.bsky.url })
31
- const pdsAgent = new AtpAgent({ service: network.pds.url })
32
- sc = new SeedClient(pdsAgent)
30
+ sc = network.getSeedClient()
33
31
  await userSeed(sc)
34
32
 
35
33
  alice = sc.dids.alice
@@ -0,0 +1,64 @@
1
+ import AtpAgent from '@atproto/api'
2
+ import { SeedClient, TestNetwork } from '@atproto/dev-env'
3
+ import usersSeed from './seeds/users'
4
+ import { createServiceJwt } from '@atproto/xrpc-server'
5
+ import { Keypair, Secp256k1Keypair } from '@atproto/crypto'
6
+
7
+ describe('auth', () => {
8
+ let network: TestNetwork
9
+ let agent: AtpAgent
10
+ let sc: SeedClient
11
+
12
+ beforeAll(async () => {
13
+ network = await TestNetwork.create({
14
+ dbPostgresSchema: 'bsky_auth',
15
+ })
16
+ agent = network.bsky.getClient()
17
+ sc = network.getSeedClient()
18
+ await usersSeed(sc)
19
+ await network.processAll()
20
+ })
21
+
22
+ afterAll(async () => {
23
+ await network.close()
24
+ })
25
+
26
+ it('handles signing key change for service auth.', async () => {
27
+ const issuer = sc.dids.alice
28
+ const attemptWithKey = async (keypair: Keypair) => {
29
+ const jwt = await createServiceJwt({
30
+ iss: issuer,
31
+ aud: network.bsky.ctx.cfg.serverDid,
32
+ keypair,
33
+ })
34
+ return agent.api.app.bsky.actor.getProfile(
35
+ { actor: sc.dids.carol },
36
+ { headers: { authorization: `Bearer ${jwt}` } },
37
+ )
38
+ }
39
+ const origSigningKey = network.pds.ctx.repoSigningKey
40
+ const newSigningKey = await Secp256k1Keypair.create({ exportable: true })
41
+ // confirm original signing key works
42
+ await expect(attemptWithKey(origSigningKey)).resolves.toBeDefined()
43
+ // confirm next signing key doesn't work yet
44
+ await expect(attemptWithKey(newSigningKey)).rejects.toThrow(
45
+ 'jwt signature does not match jwt issuer',
46
+ )
47
+ // update to new signing key
48
+ await network.plc
49
+ .getClient()
50
+ .updateAtprotoKey(
51
+ issuer,
52
+ network.pds.ctx.plcRotationKey,
53
+ newSigningKey.did(),
54
+ )
55
+ // old signing key still works due to did doc cache
56
+ await expect(attemptWithKey(origSigningKey)).resolves.toBeDefined()
57
+ // new signing key works
58
+ await expect(attemptWithKey(newSigningKey)).resolves.toBeDefined()
59
+ // old signing key no longer works after cache is updated
60
+ await expect(attemptWithKey(origSigningKey)).rejects.toThrow(
61
+ 'jwt signature does not match jwt issuer',
62
+ )
63
+ })
64
+ })
@@ -1,6 +1,5 @@
1
+ import { TestNetwork, SeedClient } from '@atproto/dev-env'
1
2
  import { FuzzyMatcher, encode } from '../../src/auto-moderator/fuzzy-matcher'
2
- import { TestNetwork } from '@atproto/dev-env'
3
- import { SeedClient } from '../seeds/client'
4
3
  import basicSeed from '../seeds/basic'
5
4
  import { AtpAgent } from '@atproto/api'
6
5
  import { ImageInvalidator } from '../../src/image/invalidator'
@@ -25,7 +24,7 @@ describe('fuzzy matcher', () => {
25
24
  })
26
25
  fuzzyMatcher = new FuzzyMatcher(['evil', 'mean', 'bad'], ['baddie'])
27
26
  agent = network.pds.getClient()
28
- sc = new SeedClient(agent)
27
+ sc = network.getSeedClient()
29
28
  await basicSeed(sc)
30
29
  await network.processAll()
31
30
  alice = sc.dids.alice