@atproto/bsky 0.0.214 → 0.0.216

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 (57) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/api/app/bsky/graph/getSuggestedFollowsByActor.js +2 -0
  3. package/dist/api/app/bsky/graph/getSuggestedFollowsByActor.js.map +1 -1
  4. package/dist/api/app/bsky/unspecced/getSuggestedOnboardingUsers.d.ts +4 -0
  5. package/dist/api/app/bsky/unspecced/getSuggestedOnboardingUsers.d.ts.map +1 -0
  6. package/dist/api/app/bsky/unspecced/getSuggestedOnboardingUsers.js +104 -0
  7. package/dist/api/app/bsky/unspecced/getSuggestedOnboardingUsers.js.map +1 -0
  8. package/dist/api/index.d.ts.map +1 -1
  9. package/dist/api/index.js +2 -0
  10. package/dist/api/index.js.map +1 -1
  11. package/dist/feature-gates.d.ts +1 -0
  12. package/dist/feature-gates.d.ts.map +1 -1
  13. package/dist/feature-gates.js +1 -0
  14. package/dist/feature-gates.js.map +1 -1
  15. package/dist/lexicon/index.d.ts +4 -0
  16. package/dist/lexicon/index.d.ts.map +1 -1
  17. package/dist/lexicon/index.js +8 -0
  18. package/dist/lexicon/index.js.map +1 -1
  19. package/dist/lexicon/lexicons.d.ts +244 -10
  20. package/dist/lexicon/lexicons.d.ts.map +1 -1
  21. package/dist/lexicon/lexicons.js +124 -5
  22. package/dist/lexicon/lexicons.js.map +1 -1
  23. package/dist/lexicon/types/app/bsky/draft/defs.d.ts +1 -1
  24. package/dist/lexicon/types/app/bsky/draft/defs.d.ts.map +1 -1
  25. package/dist/lexicon/types/app/bsky/draft/defs.js.map +1 -1
  26. package/dist/lexicon/types/app/bsky/graph/getSuggestedFollowsByActor.d.ts +3 -1
  27. package/dist/lexicon/types/app/bsky/graph/getSuggestedFollowsByActor.d.ts.map +1 -1
  28. package/dist/lexicon/types/app/bsky/graph/getSuggestedFollowsByActor.js.map +1 -1
  29. package/dist/lexicon/types/app/bsky/unspecced/getOnboardingSuggestedUsersSkeleton.d.ts +27 -0
  30. package/dist/lexicon/types/app/bsky/unspecced/getOnboardingSuggestedUsersSkeleton.d.ts.map +1 -0
  31. package/dist/lexicon/types/app/bsky/unspecced/getOnboardingSuggestedUsersSkeleton.js +7 -0
  32. package/dist/lexicon/types/app/bsky/unspecced/getOnboardingSuggestedUsersSkeleton.js.map +1 -0
  33. package/dist/lexicon/types/app/bsky/unspecced/getSuggestedOnboardingUsers.d.ts +26 -0
  34. package/dist/lexicon/types/app/bsky/unspecced/getSuggestedOnboardingUsers.d.ts.map +1 -0
  35. package/dist/lexicon/types/app/bsky/unspecced/getSuggestedOnboardingUsers.js +7 -0
  36. package/dist/lexicon/types/app/bsky/unspecced/getSuggestedOnboardingUsers.js.map +1 -0
  37. package/dist/lexicon/types/app/bsky/unspecced/getSuggestionsSkeleton.d.ts +3 -1
  38. package/dist/lexicon/types/app/bsky/unspecced/getSuggestionsSkeleton.d.ts.map +1 -1
  39. package/dist/lexicon/types/app/bsky/unspecced/getSuggestionsSkeleton.js.map +1 -1
  40. package/dist/lexicon/types/com/germnetwork/declaration.d.ts +7 -1
  41. package/dist/lexicon/types/com/germnetwork/declaration.d.ts.map +1 -1
  42. package/dist/lexicon/types/com/germnetwork/declaration.js.map +1 -1
  43. package/package.json +8 -8
  44. package/src/api/app/bsky/graph/getSuggestedFollowsByActor.ts +3 -0
  45. package/src/api/app/bsky/unspecced/getSuggestedOnboardingUsers.ts +176 -0
  46. package/src/api/index.ts +2 -0
  47. package/src/feature-gates.ts +1 -0
  48. package/src/lexicon/index.ts +26 -0
  49. package/src/lexicon/lexicons.ts +136 -5
  50. package/src/lexicon/types/app/bsky/draft/defs.ts +1 -1
  51. package/src/lexicon/types/app/bsky/graph/getSuggestedFollowsByActor.ts +3 -1
  52. package/src/lexicon/types/app/bsky/unspecced/getOnboardingSuggestedUsersSkeleton.ts +45 -0
  53. package/src/lexicon/types/app/bsky/unspecced/getSuggestedOnboardingUsers.ts +44 -0
  54. package/src/lexicon/types/app/bsky/unspecced/getSuggestionsSkeleton.ts +3 -1
  55. package/src/lexicon/types/com/germnetwork/declaration.ts +7 -1
  56. package/tests/views/get-suggested-onboarding-users.test.ts +186 -0
  57. package/tsconfig.build.tsbuildinfo +1 -1
@@ -2392,9 +2392,10 @@ export const schemaDict = {
2392
2392
  properties: {
2393
2393
  text: {
2394
2394
  type: 'string',
2395
- maxLength: 3000,
2396
- maxGraphemes: 300,
2397
- description: 'The primary post content.',
2395
+ maxLength: 10000,
2396
+ maxGraphemes: 1000,
2397
+ description:
2398
+ 'The primary post content. It has a higher limit than post contents to allow storing a larger text that can later be refined into smaller posts.',
2398
2399
  },
2399
2400
  labels: {
2400
2401
  type: 'union',
@@ -6242,6 +6243,10 @@ export const schemaDict = {
6242
6243
  },
6243
6244
  recId: {
6244
6245
  type: 'integer',
6246
+ description: 'DEPRECATED: use recIdStr instead.',
6247
+ },
6248
+ recIdStr: {
6249
+ type: 'string',
6245
6250
  description:
6246
6251
  'Snowflake for this recommendation, use when submitting recommendation events.',
6247
6252
  },
@@ -8055,6 +8060,59 @@ export const schemaDict = {
8055
8060
  },
8056
8061
  },
8057
8062
  },
8063
+ AppBskyUnspeccedGetOnboardingSuggestedUsersSkeleton: {
8064
+ lexicon: 1,
8065
+ id: 'app.bsky.unspecced.getOnboardingSuggestedUsersSkeleton',
8066
+ defs: {
8067
+ main: {
8068
+ type: 'query',
8069
+ description:
8070
+ 'Get a skeleton of suggested users for onboarding. Intended to be called and hydrated by app.bsky.unspecced.getSuggestedOnboardingUsers',
8071
+ parameters: {
8072
+ type: 'params',
8073
+ properties: {
8074
+ viewer: {
8075
+ type: 'string',
8076
+ format: 'did',
8077
+ description:
8078
+ 'DID of the account making the request (not included for public/unauthenticated queries).',
8079
+ },
8080
+ category: {
8081
+ type: 'string',
8082
+ description: 'Category of users to get suggestions for.',
8083
+ },
8084
+ limit: {
8085
+ type: 'integer',
8086
+ minimum: 1,
8087
+ maximum: 50,
8088
+ default: 25,
8089
+ },
8090
+ },
8091
+ },
8092
+ output: {
8093
+ encoding: 'application/json',
8094
+ schema: {
8095
+ type: 'object',
8096
+ required: ['dids'],
8097
+ properties: {
8098
+ dids: {
8099
+ type: 'array',
8100
+ items: {
8101
+ type: 'string',
8102
+ format: 'did',
8103
+ },
8104
+ },
8105
+ recId: {
8106
+ type: 'string',
8107
+ description:
8108
+ 'Snowflake for this recommendation, use when submitting recommendation events.',
8109
+ },
8110
+ },
8111
+ },
8112
+ },
8113
+ },
8114
+ },
8115
+ },
8058
8116
  AppBskyUnspeccedGetPopularFeedGenerators: {
8059
8117
  lexicon: 1,
8060
8118
  id: 'app.bsky.unspecced.getPopularFeedGenerators',
@@ -8343,6 +8401,52 @@ export const schemaDict = {
8343
8401
  },
8344
8402
  },
8345
8403
  },
8404
+ AppBskyUnspeccedGetSuggestedOnboardingUsers: {
8405
+ lexicon: 1,
8406
+ id: 'app.bsky.unspecced.getSuggestedOnboardingUsers',
8407
+ defs: {
8408
+ main: {
8409
+ type: 'query',
8410
+ description: 'Get a list of suggested users for onboarding',
8411
+ parameters: {
8412
+ type: 'params',
8413
+ properties: {
8414
+ category: {
8415
+ type: 'string',
8416
+ description: 'Category of users to get suggestions for.',
8417
+ },
8418
+ limit: {
8419
+ type: 'integer',
8420
+ minimum: 1,
8421
+ maximum: 50,
8422
+ default: 25,
8423
+ },
8424
+ },
8425
+ },
8426
+ output: {
8427
+ encoding: 'application/json',
8428
+ schema: {
8429
+ type: 'object',
8430
+ required: ['actors'],
8431
+ properties: {
8432
+ actors: {
8433
+ type: 'array',
8434
+ items: {
8435
+ type: 'ref',
8436
+ ref: 'lex:app.bsky.actor.defs#profileView',
8437
+ },
8438
+ },
8439
+ recId: {
8440
+ type: 'string',
8441
+ description:
8442
+ 'Snowflake for this recommendation, use when submitting recommendation events.',
8443
+ },
8444
+ },
8445
+ },
8446
+ },
8447
+ },
8448
+ },
8449
+ },
8346
8450
  AppBskyUnspeccedGetSuggestedStarterPacks: {
8347
8451
  lexicon: 1,
8348
8452
  id: 'app.bsky.unspecced.getSuggestedStarterPacks',
@@ -8581,6 +8685,10 @@ export const schemaDict = {
8581
8685
  },
8582
8686
  recId: {
8583
8687
  type: 'integer',
8688
+ description: 'DEPRECATED: use recIdStr instead.',
8689
+ },
8690
+ recIdStr: {
8691
+ type: 'string',
8584
8692
  description:
8585
8693
  'Snowflake for this recommendation, use when submitting recommendation events.',
8586
8694
  },
@@ -15346,7 +15454,7 @@ export const schemaDict = {
15346
15454
  defs: {
15347
15455
  main: {
15348
15456
  type: 'record',
15349
- description: 'A delegate messaging id',
15457
+ description: 'A declaration of a Germ Network account',
15350
15458
  key: 'literal:self',
15351
15459
  record: {
15352
15460
  type: 'object',
@@ -15354,22 +15462,33 @@ export const schemaDict = {
15354
15462
  properties: {
15355
15463
  version: {
15356
15464
  type: 'string',
15465
+ description:
15466
+ 'Semver version number, without pre-release or build information, for the format of opaque content',
15467
+ minLength: 5,
15468
+ maxLength: 14,
15357
15469
  },
15358
15470
  currentKey: {
15359
15471
  type: 'bytes',
15472
+ description:
15473
+ 'Opaque value, an ed25519 public key prefixed with a byte enum',
15360
15474
  },
15361
15475
  messageMe: {
15362
15476
  type: 'ref',
15477
+ description: 'Controls who can message this account',
15363
15478
  ref: 'lex:com.germnetwork.declaration#messageMe',
15364
15479
  },
15365
15480
  keyPackage: {
15366
15481
  type: 'bytes',
15482
+ description:
15483
+ 'Opaque value, contains MLS KeyPackage(s), and other signature data, and is signed by the currentKey',
15367
15484
  },
15368
15485
  continuityProofs: {
15369
15486
  type: 'array',
15487
+ description: 'Array of opaque values to allow for key rolling',
15370
15488
  items: {
15371
15489
  type: 'bytes',
15372
15490
  },
15491
+ maxLength: 1000,
15373
15492
  },
15374
15493
  },
15375
15494
  },
@@ -15380,11 +15499,19 @@ export const schemaDict = {
15380
15499
  properties: {
15381
15500
  messageMeUrl: {
15382
15501
  type: 'string',
15502
+ description:
15503
+ 'A URL to present to an account that does not have its own com.germnetwork.declaration record, must have an empty fragment component, where the app should fill in the fragment component with the DIDs of the two accounts who wish to message each other',
15383
15504
  format: 'uri',
15505
+ minLength: 1,
15506
+ maxLength: 2047,
15384
15507
  },
15385
15508
  showButtonTo: {
15386
15509
  type: 'string',
15387
- knownValues: ['usersIFollow', 'everyone'],
15510
+ knownValues: ['none', 'usersIFollow', 'everyone'],
15511
+ description:
15512
+ "The policy of who can message the account, this value is included in the keyPackage, but is duplicated here to allow applications to decide if they should show a 'Message on Germ' button to the viewer.",
15513
+ minLength: 1,
15514
+ maxLength: 100,
15388
15515
  },
15389
15516
  },
15390
15517
  },
@@ -15547,6 +15674,8 @@ export const ids = {
15547
15674
  'app.bsky.unspecced.getOnboardingSuggestedStarterPacks',
15548
15675
  AppBskyUnspeccedGetOnboardingSuggestedStarterPacksSkeleton:
15549
15676
  'app.bsky.unspecced.getOnboardingSuggestedStarterPacksSkeleton',
15677
+ AppBskyUnspeccedGetOnboardingSuggestedUsersSkeleton:
15678
+ 'app.bsky.unspecced.getOnboardingSuggestedUsersSkeleton',
15550
15679
  AppBskyUnspeccedGetPopularFeedGenerators:
15551
15680
  'app.bsky.unspecced.getPopularFeedGenerators',
15552
15681
  AppBskyUnspeccedGetPostThreadOtherV2:
@@ -15555,6 +15684,8 @@ export const ids = {
15555
15684
  AppBskyUnspeccedGetSuggestedFeeds: 'app.bsky.unspecced.getSuggestedFeeds',
15556
15685
  AppBskyUnspeccedGetSuggestedFeedsSkeleton:
15557
15686
  'app.bsky.unspecced.getSuggestedFeedsSkeleton',
15687
+ AppBskyUnspeccedGetSuggestedOnboardingUsers:
15688
+ 'app.bsky.unspecced.getSuggestedOnboardingUsers',
15558
15689
  AppBskyUnspeccedGetSuggestedStarterPacks:
15559
15690
  'app.bsky.unspecced.getSuggestedStarterPacks',
15560
15691
  AppBskyUnspeccedGetSuggestedStarterPacksSkeleton:
@@ -75,7 +75,7 @@ export function validateDraft<V>(v: V) {
75
75
  /** One of the posts that compose a draft. */
76
76
  export interface DraftPost {
77
77
  $type?: 'app.bsky.draft.defs#draftPost'
78
- /** The primary post content. */
78
+ /** The primary post content. It has a higher limit than post contents to allow storing a larger text that can later be refined into smaller posts. */
79
79
  text: string
80
80
  labels?: $Typed<ComAtprotoLabelDefs.SelfLabels> | { $type: string }
81
81
  embedImages?: DraftEmbedImage[]
@@ -24,8 +24,10 @@ export interface OutputSchema {
24
24
  suggestions: AppBskyActorDefs.ProfileView[]
25
25
  /** If true, response has fallen-back to generic results, and is not scoped using relativeToDid */
26
26
  isFallback?: boolean
27
- /** Snowflake for this recommendation, use when submitting recommendation events. */
27
+ /** DEPRECATED: use recIdStr instead. */
28
28
  recId?: number
29
+ /** Snowflake for this recommendation, use when submitting recommendation events. */
30
+ recIdStr?: string
29
31
  }
30
32
 
31
33
  export type HandlerInput = void
@@ -0,0 +1,45 @@
1
+ /**
2
+ * GENERATED CODE - DO NOT MODIFY
3
+ */
4
+ import { type ValidationResult, BlobRef } from '@atproto/lexicon'
5
+ import { CID } from 'multiformats/cid'
6
+ import { validate as _validate } from '../../../../lexicons'
7
+ import {
8
+ type $Typed,
9
+ is$typed as _is$typed,
10
+ type OmitKey,
11
+ } from '../../../../util'
12
+
13
+ const is$typed = _is$typed,
14
+ validate = _validate
15
+ const id = 'app.bsky.unspecced.getOnboardingSuggestedUsersSkeleton'
16
+
17
+ export type QueryParams = {
18
+ /** DID of the account making the request (not included for public/unauthenticated queries). */
19
+ viewer?: string
20
+ /** Category of users to get suggestions for. */
21
+ category?: string
22
+ limit: number
23
+ }
24
+ export type InputSchema = undefined
25
+
26
+ export interface OutputSchema {
27
+ dids: string[]
28
+ /** Snowflake for this recommendation, use when submitting recommendation events. */
29
+ recId?: string
30
+ }
31
+
32
+ export type HandlerInput = void
33
+
34
+ export interface HandlerSuccess {
35
+ encoding: 'application/json'
36
+ body: OutputSchema
37
+ headers?: { [key: string]: string }
38
+ }
39
+
40
+ export interface HandlerError {
41
+ status: number
42
+ message?: string
43
+ }
44
+
45
+ export type HandlerOutput = HandlerError | HandlerSuccess
@@ -0,0 +1,44 @@
1
+ /**
2
+ * GENERATED CODE - DO NOT MODIFY
3
+ */
4
+ import { type ValidationResult, BlobRef } from '@atproto/lexicon'
5
+ import { CID } from 'multiformats/cid'
6
+ import { validate as _validate } from '../../../../lexicons'
7
+ import {
8
+ type $Typed,
9
+ is$typed as _is$typed,
10
+ type OmitKey,
11
+ } from '../../../../util'
12
+ import type * as AppBskyActorDefs from '../actor/defs.js'
13
+
14
+ const is$typed = _is$typed,
15
+ validate = _validate
16
+ const id = 'app.bsky.unspecced.getSuggestedOnboardingUsers'
17
+
18
+ export type QueryParams = {
19
+ /** Category of users to get suggestions for. */
20
+ category?: string
21
+ limit: number
22
+ }
23
+ export type InputSchema = undefined
24
+
25
+ export interface OutputSchema {
26
+ actors: AppBskyActorDefs.ProfileView[]
27
+ /** Snowflake for this recommendation, use when submitting recommendation events. */
28
+ recId?: string
29
+ }
30
+
31
+ export type HandlerInput = void
32
+
33
+ export interface HandlerSuccess {
34
+ encoding: 'application/json'
35
+ body: OutputSchema
36
+ headers?: { [key: string]: string }
37
+ }
38
+
39
+ export interface HandlerError {
40
+ status: number
41
+ message?: string
42
+ }
43
+
44
+ export type HandlerOutput = HandlerError | HandlerSuccess
@@ -30,8 +30,10 @@ export interface OutputSchema {
30
30
  actors: AppBskyUnspeccedDefs.SkeletonSearchActor[]
31
31
  /** DID of the account these suggestions are relative to. If this is returned undefined, suggestions are based on the viewer. */
32
32
  relativeToDid?: string
33
- /** Snowflake for this recommendation, use when submitting recommendation events. */
33
+ /** DEPRECATED: use recIdStr instead. */
34
34
  recId?: number
35
+ /** Snowflake for this recommendation, use when submitting recommendation events. */
36
+ recIdStr?: string
35
37
  }
36
38
 
37
39
  export type HandlerInput = void
@@ -12,10 +12,14 @@ const id = 'com.germnetwork.declaration'
12
12
 
13
13
  export interface Main {
14
14
  $type: 'com.germnetwork.declaration'
15
+ /** Semver version number, without pre-release or build information, for the format of opaque content */
15
16
  version: string
17
+ /** Opaque value, an ed25519 public key prefixed with a byte enum */
16
18
  currentKey: Uint8Array
17
19
  messageMe?: MessageMe
20
+ /** Opaque value, contains MLS KeyPackage(s), and other signature data, and is signed by the currentKey */
18
21
  keyPackage?: Uint8Array
22
+ /** Array of opaque values to allow for key rolling */
19
23
  continuityProofs?: Uint8Array[]
20
24
  [k: string]: unknown
21
25
  }
@@ -38,8 +42,10 @@ export {
38
42
 
39
43
  export interface MessageMe {
40
44
  $type?: 'com.germnetwork.declaration#messageMe'
45
+ /** A URL to present to an account that does not have its own com.germnetwork.declaration record, must have an empty fragment component, where the app should fill in the fragment component with the DIDs of the two accounts who wish to message each other */
41
46
  messageMeUrl: string
42
- showButtonTo: 'usersIFollow' | 'everyone' | (string & {})
47
+ /** The policy of who can message the account, this value is included in the keyPackage, but is duplicated here to allow applications to decide if they should show a 'Message on Germ' button to the viewer. */
48
+ showButtonTo: 'none' | 'usersIFollow' | 'everyone' | (string & {})
43
49
  }
44
50
 
45
51
  const hashMessageMe = 'messageMe'
@@ -0,0 +1,186 @@
1
+ import { once } from 'node:events'
2
+ import { Server, createServer } from 'node:http'
3
+ import { AddressInfo } from 'node:net'
4
+ import express, { Application } from 'express'
5
+ import AtpAgent from '@atproto/api'
6
+ import { SeedClient, TestNetwork } from '@atproto/dev-env'
7
+ import { ids } from '../../src/lexicon/lexicons'
8
+ import { OutputSchema } from '../../src/lexicon/types/app/bsky/unspecced/getSuggestedUsersSkeleton'
9
+
10
+ type User = {
11
+ id: string
12
+ did: string
13
+ email: string
14
+ handle: string
15
+ password: string
16
+ displayName: string
17
+ description: string
18
+ selfLabels: undefined
19
+ }
20
+
21
+ function createUser(name: string): User {
22
+ return {
23
+ id: name,
24
+ // @ts-ignore overwritten below
25
+ did: undefined,
26
+ email: `${name}@test.com`,
27
+ handle: `${name}.test`,
28
+ password: `${name}-pass`,
29
+ displayName: name,
30
+ description: `hi im ${name}`,
31
+ selfLabels: undefined,
32
+ }
33
+ }
34
+
35
+ const users = {
36
+ suggestedUser: createUser('suggested-user'),
37
+ viewer: createUser('viewer'),
38
+ viewerBlocker: createUser('viewer-blocker'),
39
+ followedUser: createUser('followed-user'),
40
+ }
41
+
42
+ type Users = typeof users
43
+
44
+ async function seed(sc: SeedClient) {
45
+ const u = structuredClone(users)
46
+
47
+ for (const [key, user] of Object.entries(u)) {
48
+ await sc.createAccount(key, user)
49
+ u[key].did = sc.dids[key]
50
+ }
51
+
52
+ await sc.block(u.viewerBlocker.did, u.suggestedUser.did)
53
+ await sc.follow(u.viewer.did, u.followedUser.did)
54
+
55
+ await sc.network.processAll()
56
+
57
+ return { users: u }
58
+ }
59
+
60
+ describe('getSuggestedOnboardingUsers', () => {
61
+ let network: TestNetwork
62
+ let agent: AtpAgent
63
+ let sc: SeedClient
64
+ let seededUsers: Users
65
+ let mockServer: MockServer
66
+
67
+ beforeAll(async () => {
68
+ mockServer = new MockServer()
69
+ await mockServer.listen()
70
+
71
+ network = await TestNetwork.create({
72
+ dbPostgresSchema: 'bsky_tests_get_suggested_onboarding_users',
73
+ bsky: {
74
+ topicsUrl: mockServer.url,
75
+ topicsApiKey: 'test',
76
+ },
77
+ })
78
+ agent = network.bsky.getClient()
79
+ sc = network.getSeedClient()
80
+
81
+ const result = await seed(sc)
82
+ seededUsers = result.users
83
+
84
+ await network.processAll()
85
+ })
86
+
87
+ afterAll(async () => {
88
+ await network.close()
89
+ await mockServer.stop()
90
+ })
91
+
92
+ describe(`basic handling`, () => {
93
+ beforeAll(() => {
94
+ mockServer.mockedDids.set('suggestedUser', seededUsers.suggestedUser.did)
95
+ })
96
+
97
+ afterAll(() => {
98
+ mockServer.mockedDids.delete('suggestedUser')
99
+ })
100
+
101
+ it(`returns users for non-blocking viewer`, async () => {
102
+ const { data } =
103
+ await agent.app.bsky.unspecced.getSuggestedOnboardingUsers(undefined, {
104
+ headers: await network.serviceHeaders(
105
+ seededUsers.viewer.did,
106
+ ids.AppBskyUnspeccedGetSuggestedOnboardingUsers,
107
+ ),
108
+ })
109
+ const actor = data.actors.find(
110
+ (a) => a.did === seededUsers.suggestedUser.did,
111
+ )
112
+ expect(actor).toBeDefined()
113
+ })
114
+
115
+ it(`does not return user if blocked by viewer`, async () => {
116
+ const { data } =
117
+ await agent.app.bsky.unspecced.getSuggestedOnboardingUsers(undefined, {
118
+ headers: await network.serviceHeaders(
119
+ seededUsers.viewerBlocker.did,
120
+ ids.AppBskyUnspeccedGetSuggestedOnboardingUsers,
121
+ ),
122
+ })
123
+ const actor = data.actors.find(
124
+ (a) => a.did === seededUsers.suggestedUser.did,
125
+ )
126
+ expect(actor).not.toBeDefined()
127
+ })
128
+
129
+ it(`does not return users that viewer already follows`, async () => {
130
+ mockServer.mockedDids.set('followedUser', seededUsers.followedUser.did)
131
+ const { data } =
132
+ await agent.app.bsky.unspecced.getSuggestedOnboardingUsers(undefined, {
133
+ headers: await network.serviceHeaders(
134
+ seededUsers.viewer.did,
135
+ ids.AppBskyUnspeccedGetSuggestedOnboardingUsers,
136
+ ),
137
+ })
138
+ const actor = data.actors.find(
139
+ (a) => a.did === seededUsers.followedUser.did,
140
+ )
141
+ expect(actor).not.toBeDefined()
142
+ mockServer.mockedDids.delete('followedUser')
143
+ })
144
+ })
145
+ })
146
+
147
+ class MockServer {
148
+ app: Application
149
+ server: Server
150
+
151
+ mockedDids = new Map<string, string>()
152
+
153
+ constructor() {
154
+ this.app = this.createApp()
155
+ this.server = createServer(this.app)
156
+ }
157
+
158
+ async listen(port?: number) {
159
+ this.server.listen(port)
160
+ await once(this.server, 'listening')
161
+ }
162
+
163
+ async stop() {
164
+ this.server.close()
165
+ await once(this.server, 'close')
166
+ }
167
+
168
+ get url() {
169
+ const address = this.server.address() as AddressInfo
170
+ return `http://localhost:${address.port}`
171
+ }
172
+
173
+ private createApp() {
174
+ const app = express()
175
+ app.get(
176
+ '/xrpc/app.bsky.unspecced.getSuggestedUsersSkeleton',
177
+ (req, res) => {
178
+ const skeleton: OutputSchema = {
179
+ dids: Array.from(this.mockedDids.values()),
180
+ }
181
+ return res.json(skeleton)
182
+ },
183
+ )
184
+ return app
185
+ }
186
+ }