@atproto/bsky 0.0.133 → 0.0.134

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 (62) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/api/app/bsky/unspecced/getSuggestedStarterPacks.d.ts +4 -0
  3. package/dist/api/app/bsky/unspecced/getSuggestedStarterPacks.d.ts.map +1 -0
  4. package/dist/api/app/bsky/unspecced/getSuggestedStarterPacks.js +101 -0
  5. package/dist/api/app/bsky/unspecced/getSuggestedStarterPacks.js.map +1 -0
  6. package/dist/api/app/bsky/unspecced/getTrends.d.ts +4 -0
  7. package/dist/api/app/bsky/unspecced/getTrends.d.ts.map +1 -0
  8. package/dist/api/app/bsky/unspecced/getTrends.js +96 -0
  9. package/dist/api/app/bsky/unspecced/getTrends.js.map +1 -0
  10. package/dist/api/index.d.ts.map +1 -1
  11. package/dist/api/index.js +4 -0
  12. package/dist/api/index.js.map +1 -1
  13. package/dist/lexicon/index.d.ts +10 -0
  14. package/dist/lexicon/index.d.ts.map +1 -1
  15. package/dist/lexicon/index.js +20 -0
  16. package/dist/lexicon/index.js.map +1 -1
  17. package/dist/lexicon/lexicons.d.ts +516 -0
  18. package/dist/lexicon/lexicons.d.ts.map +1 -1
  19. package/dist/lexicon/lexicons.js +277 -0
  20. package/dist/lexicon/lexicons.js.map +1 -1
  21. package/dist/lexicon/types/app/bsky/unspecced/defs.d.ts +27 -0
  22. package/dist/lexicon/types/app/bsky/unspecced/defs.d.ts.map +1 -1
  23. package/dist/lexicon/types/app/bsky/unspecced/defs.js +18 -0
  24. package/dist/lexicon/types/app/bsky/unspecced/defs.js.map +1 -1
  25. package/dist/lexicon/types/app/bsky/unspecced/getSuggestedStarterPacks.d.ts +36 -0
  26. package/dist/lexicon/types/app/bsky/unspecced/getSuggestedStarterPacks.d.ts.map +1 -0
  27. package/dist/lexicon/types/app/bsky/unspecced/getSuggestedStarterPacks.js +7 -0
  28. package/dist/lexicon/types/app/bsky/unspecced/getSuggestedStarterPacks.js.map +1 -0
  29. package/dist/lexicon/types/app/bsky/unspecced/getSuggestedStarterPacksSkeleton.d.ts +37 -0
  30. package/dist/lexicon/types/app/bsky/unspecced/getSuggestedStarterPacksSkeleton.d.ts.map +1 -0
  31. package/dist/lexicon/types/app/bsky/unspecced/getSuggestedStarterPacksSkeleton.js +7 -0
  32. package/dist/lexicon/types/app/bsky/unspecced/getSuggestedStarterPacksSkeleton.js.map +1 -0
  33. package/dist/lexicon/types/app/bsky/unspecced/getTrends.d.ts +36 -0
  34. package/dist/lexicon/types/app/bsky/unspecced/getTrends.d.ts.map +1 -0
  35. package/dist/lexicon/types/app/bsky/unspecced/getTrends.js +7 -0
  36. package/dist/lexicon/types/app/bsky/unspecced/getTrends.js.map +1 -0
  37. package/dist/lexicon/types/app/bsky/unspecced/getTrendsSkeleton.d.ts +38 -0
  38. package/dist/lexicon/types/app/bsky/unspecced/getTrendsSkeleton.d.ts.map +1 -0
  39. package/dist/lexicon/types/app/bsky/unspecced/getTrendsSkeleton.js +7 -0
  40. package/dist/lexicon/types/app/bsky/unspecced/getTrendsSkeleton.js.map +1 -0
  41. package/dist/lexicon/types/com/atproto/admin/updateAccountSigningKey.d.ts +31 -0
  42. package/dist/lexicon/types/com/atproto/admin/updateAccountSigningKey.d.ts.map +1 -0
  43. package/dist/lexicon/types/com/atproto/admin/updateAccountSigningKey.js +7 -0
  44. package/dist/lexicon/types/com/atproto/admin/updateAccountSigningKey.js.map +1 -0
  45. package/package.json +9 -9
  46. package/src/api/app/bsky/unspecced/getSuggestedStarterPacks.ts +150 -0
  47. package/src/api/app/bsky/unspecced/getTrends.ts +144 -0
  48. package/src/api/index.ts +4 -0
  49. package/src/lexicon/index.ts +62 -0
  50. package/src/lexicon/lexicons.ts +285 -0
  51. package/src/lexicon/types/app/bsky/unspecced/defs.ts +45 -0
  52. package/src/lexicon/types/app/bsky/unspecced/getSuggestedStarterPacks.ts +54 -0
  53. package/src/lexicon/types/app/bsky/unspecced/getSuggestedStarterPacksSkeleton.ts +55 -0
  54. package/src/lexicon/types/app/bsky/unspecced/getTrends.ts +54 -0
  55. package/src/lexicon/types/app/bsky/unspecced/getTrendsSkeleton.ts +56 -0
  56. package/src/lexicon/types/com/atproto/admin/updateAccountSigningKey.ts +48 -0
  57. package/tests/seed/get-suggested-starter-packs.ts +63 -0
  58. package/tests/seed/get-trends.ts +70 -0
  59. package/tests/views/get-suggested-starter-packs.test.ts +128 -0
  60. package/tests/views/get-trends.test.ts +133 -0
  61. package/tsconfig.build.tsbuildinfo +1 -1
  62. package/tsconfig.tests.tsbuildinfo +1 -1
@@ -0,0 +1,55 @@
1
+ /**
2
+ * GENERATED CODE - DO NOT MODIFY
3
+ */
4
+ import express from 'express'
5
+ import { type ValidationResult, BlobRef } from '@atproto/lexicon'
6
+ import { CID } from 'multiformats/cid'
7
+ import { validate as _validate } from '../../../../lexicons'
8
+ import {
9
+ type $Typed,
10
+ is$typed as _is$typed,
11
+ type OmitKey,
12
+ } from '../../../../util'
13
+ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
14
+
15
+ const is$typed = _is$typed,
16
+ validate = _validate
17
+ const id = 'app.bsky.unspecced.getSuggestedStarterPacksSkeleton'
18
+
19
+ export interface QueryParams {
20
+ /** DID of the account making the request (not included for public/unauthenticated queries). */
21
+ viewer?: string
22
+ limit: number
23
+ }
24
+
25
+ export type InputSchema = undefined
26
+
27
+ export interface OutputSchema {
28
+ starterPacks: string[]
29
+ }
30
+
31
+ export type HandlerInput = undefined
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 | HandlerPipeThrough
45
+ export type HandlerReqCtx<HA extends HandlerAuth = never> = {
46
+ auth: HA
47
+ params: QueryParams
48
+ input: HandlerInput
49
+ req: express.Request
50
+ res: express.Response
51
+ resetRouteRateLimits: () => Promise<void>
52
+ }
53
+ export type Handler<HA extends HandlerAuth = never> = (
54
+ ctx: HandlerReqCtx<HA>,
55
+ ) => Promise<HandlerOutput> | HandlerOutput
@@ -0,0 +1,54 @@
1
+ /**
2
+ * GENERATED CODE - DO NOT MODIFY
3
+ */
4
+ import express from 'express'
5
+ import { type ValidationResult, BlobRef } from '@atproto/lexicon'
6
+ import { CID } from 'multiformats/cid'
7
+ import { validate as _validate } from '../../../../lexicons'
8
+ import {
9
+ type $Typed,
10
+ is$typed as _is$typed,
11
+ type OmitKey,
12
+ } from '../../../../util'
13
+ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
14
+ import type * as AppBskyUnspeccedDefs from './defs.js'
15
+
16
+ const is$typed = _is$typed,
17
+ validate = _validate
18
+ const id = 'app.bsky.unspecced.getTrends'
19
+
20
+ export interface QueryParams {
21
+ limit: number
22
+ }
23
+
24
+ export type InputSchema = undefined
25
+
26
+ export interface OutputSchema {
27
+ trends: AppBskyUnspeccedDefs.TrendView[]
28
+ }
29
+
30
+ export type HandlerInput = undefined
31
+
32
+ export interface HandlerSuccess {
33
+ encoding: 'application/json'
34
+ body: OutputSchema
35
+ headers?: { [key: string]: string }
36
+ }
37
+
38
+ export interface HandlerError {
39
+ status: number
40
+ message?: string
41
+ }
42
+
43
+ export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
44
+ export type HandlerReqCtx<HA extends HandlerAuth = never> = {
45
+ auth: HA
46
+ params: QueryParams
47
+ input: HandlerInput
48
+ req: express.Request
49
+ res: express.Response
50
+ resetRouteRateLimits: () => Promise<void>
51
+ }
52
+ export type Handler<HA extends HandlerAuth = never> = (
53
+ ctx: HandlerReqCtx<HA>,
54
+ ) => Promise<HandlerOutput> | HandlerOutput
@@ -0,0 +1,56 @@
1
+ /**
2
+ * GENERATED CODE - DO NOT MODIFY
3
+ */
4
+ import express from 'express'
5
+ import { type ValidationResult, BlobRef } from '@atproto/lexicon'
6
+ import { CID } from 'multiformats/cid'
7
+ import { validate as _validate } from '../../../../lexicons'
8
+ import {
9
+ type $Typed,
10
+ is$typed as _is$typed,
11
+ type OmitKey,
12
+ } from '../../../../util'
13
+ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
14
+ import type * as AppBskyUnspeccedDefs from './defs.js'
15
+
16
+ const is$typed = _is$typed,
17
+ validate = _validate
18
+ const id = 'app.bsky.unspecced.getTrendsSkeleton'
19
+
20
+ export interface QueryParams {
21
+ /** DID of the account making the request (not included for public/unauthenticated queries). */
22
+ viewer?: string
23
+ limit: number
24
+ }
25
+
26
+ export type InputSchema = undefined
27
+
28
+ export interface OutputSchema {
29
+ trends: AppBskyUnspeccedDefs.SkeletonTrend[]
30
+ }
31
+
32
+ export type HandlerInput = undefined
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 | HandlerPipeThrough
46
+ export type HandlerReqCtx<HA extends HandlerAuth = never> = {
47
+ auth: HA
48
+ params: QueryParams
49
+ input: HandlerInput
50
+ req: express.Request
51
+ res: express.Response
52
+ resetRouteRateLimits: () => Promise<void>
53
+ }
54
+ export type Handler<HA extends HandlerAuth = never> = (
55
+ ctx: HandlerReqCtx<HA>,
56
+ ) => Promise<HandlerOutput> | HandlerOutput
@@ -0,0 +1,48 @@
1
+ /**
2
+ * GENERATED CODE - DO NOT MODIFY
3
+ */
4
+ import express from 'express'
5
+ import { type ValidationResult, BlobRef } from '@atproto/lexicon'
6
+ import { CID } from 'multiformats/cid'
7
+ import { validate as _validate } from '../../../../lexicons'
8
+ import {
9
+ type $Typed,
10
+ is$typed as _is$typed,
11
+ type OmitKey,
12
+ } from '../../../../util'
13
+ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
14
+
15
+ const is$typed = _is$typed,
16
+ validate = _validate
17
+ const id = 'com.atproto.admin.updateAccountSigningKey'
18
+
19
+ export interface QueryParams {}
20
+
21
+ export interface InputSchema {
22
+ did: string
23
+ /** Did-key formatted public key */
24
+ signingKey: string
25
+ }
26
+
27
+ export interface HandlerInput {
28
+ encoding: 'application/json'
29
+ body: InputSchema
30
+ }
31
+
32
+ export interface HandlerError {
33
+ status: number
34
+ message?: string
35
+ }
36
+
37
+ export type HandlerOutput = HandlerError | void
38
+ export type HandlerReqCtx<HA extends HandlerAuth = never> = {
39
+ auth: HA
40
+ params: QueryParams
41
+ input: HandlerInput
42
+ req: express.Request
43
+ res: express.Response
44
+ resetRouteRateLimits: () => Promise<void>
45
+ }
46
+ export type Handler<HA extends HandlerAuth = never> = (
47
+ ctx: HandlerReqCtx<HA>,
48
+ ) => Promise<HandlerOutput> | HandlerOutput
@@ -0,0 +1,63 @@
1
+ import { SeedClient, TestNetwork, TestNetworkNoAppView } from '@atproto/dev-env'
2
+
3
+ export type User = {
4
+ id: string
5
+ did: string
6
+ email: string
7
+ handle: string
8
+ password: string
9
+ displayName: string
10
+ description: string
11
+ selfLabels: undefined
12
+ }
13
+
14
+ function createUser(name: string): User {
15
+ return {
16
+ id: name,
17
+ // @ts-ignore overwritten below
18
+ did: undefined,
19
+ email: `${name}@test.com`,
20
+ handle: `${name}.test`,
21
+ password: `${name}-pass`,
22
+ displayName: name,
23
+ description: `hi im ${name} label_me`,
24
+ selfLabels: undefined,
25
+ }
26
+ }
27
+
28
+ const users = {
29
+ creator: createUser('creator'),
30
+ poster: createUser('poster'),
31
+
32
+ viewer: createUser('viewer'),
33
+ viewerBlocker: createUser('viewerBlocker'),
34
+ }
35
+
36
+ export type Users = typeof users
37
+ export type StarterPacks = SeedClient['starterpacks']
38
+
39
+ export async function trendsSeed(
40
+ sc: SeedClient<TestNetwork | TestNetworkNoAppView>,
41
+ ) {
42
+ const u = structuredClone(users)
43
+
44
+ await sc.createAccount('creator', u.creator)
45
+ await sc.createAccount('poster', u.poster)
46
+ await sc.createAccount('viewer', u.viewer)
47
+ await sc.createAccount('viewerBlocker', u.viewerBlocker)
48
+
49
+ Object.values(u).forEach((user) => {
50
+ u[user.id].did = sc.dids[user.id]
51
+ })
52
+
53
+ await sc.createStarterPack(u.creator.did, 'test', [u.poster.did])
54
+ await sc.block(u.viewerBlocker.did, u.creator.did)
55
+
56
+ await sc.network.processAll()
57
+
58
+ return {
59
+ users: u,
60
+ starterpacks: sc.starterpacks,
61
+ seedClient: sc,
62
+ }
63
+ }
@@ -0,0 +1,70 @@
1
+ import { SeedClient, TestNetwork, TestNetworkNoAppView } from '@atproto/dev-env'
2
+
3
+ export type User = {
4
+ id: string
5
+ did: string
6
+ email: string
7
+ handle: string
8
+ password: string
9
+ displayName: string
10
+ description: string
11
+ selfLabels: undefined
12
+ }
13
+
14
+ function createUser(name: string): User {
15
+ return {
16
+ id: name,
17
+ // @ts-ignore overwritten below
18
+ did: undefined,
19
+ email: `${name}@test.com`,
20
+ handle: `${name}.test`,
21
+ password: `${name}-pass`,
22
+ displayName: name,
23
+ description: `hi im ${name} label_me`,
24
+ selfLabels: undefined,
25
+ }
26
+ }
27
+
28
+ const users = {
29
+ trender: createUser('trender'),
30
+
31
+ posterA: createUser('posterA'),
32
+ posterB: createUser('posterB'),
33
+ posterC: createUser('posterC'),
34
+ posterD: createUser('posterD'),
35
+
36
+ viewer: createUser('viewer'),
37
+ viewerBlocker: createUser('viewerBlocker'),
38
+ }
39
+
40
+ export type Users = typeof users
41
+ export type Feeds = SeedClient['feedgens']
42
+
43
+ export async function trendsSeed(
44
+ sc: SeedClient<TestNetwork | TestNetworkNoAppView>,
45
+ ) {
46
+ const u = structuredClone(users)
47
+
48
+ await sc.createAccount('trender', u.trender)
49
+ await sc.createAccount('posterA', u.posterA)
50
+ await sc.createAccount('posterB', u.posterB)
51
+ await sc.createAccount('posterC', u.posterC)
52
+ await sc.createAccount('posterD', u.posterD)
53
+ await sc.createAccount('viewer', u.viewer)
54
+ await sc.createAccount('viewerBlocker', u.viewerBlocker)
55
+
56
+ Object.values(u).forEach((user) => {
57
+ u[user.id].did = sc.dids[user.id]
58
+ })
59
+
60
+ await sc.createFeedGen(u.trender.did, 'did:web:example.com', 'trendA')
61
+ await sc.block(u.viewerBlocker.did, u.posterC.did)
62
+
63
+ await sc.network.processAll()
64
+
65
+ return {
66
+ users: u,
67
+ feeds: sc.feedgens,
68
+ seedClient: sc,
69
+ }
70
+ }
@@ -0,0 +1,128 @@
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/getSuggestedStarterPacksSkeleton'
9
+ import {
10
+ StarterPacks,
11
+ Users,
12
+ trendsSeed,
13
+ } from '../seed/get-suggested-starter-packs'
14
+
15
+ describe('getSuggestedStarterPacks', () => {
16
+ let network: TestNetwork
17
+ let agent: AtpAgent
18
+ let sc: SeedClient
19
+ let users: Users
20
+ let mockServer: MockServer
21
+ let starterpacks: StarterPacks
22
+
23
+ beforeAll(async () => {
24
+ mockServer = new MockServer()
25
+ await mockServer.listen()
26
+
27
+ network = await TestNetwork.create({
28
+ dbPostgresSchema: 'bsky_tests_get_suggested_starter_packs',
29
+ bsky: {
30
+ topicsUrl: mockServer.url,
31
+ topicsApiKey: 'test',
32
+ },
33
+ })
34
+ agent = network.bsky.getClient()
35
+ sc = network.getSeedClient()
36
+
37
+ const result = await trendsSeed(sc)
38
+ users = result.users
39
+ starterpacks = result.starterpacks
40
+
41
+ await network.processAll()
42
+ })
43
+
44
+ afterAll(async () => {
45
+ await network.close()
46
+ await mockServer.stop()
47
+ })
48
+
49
+ describe(`basic handling`, () => {
50
+ beforeAll(() => {
51
+ const pack = Object.values(starterpacks[users.creator.did])[0]
52
+ mockServer.mockedStarterPackUris.set('a', pack.ref.uriStr)
53
+ })
54
+
55
+ afterAll(() => {
56
+ mockServer.mockedStarterPackUris.delete('a')
57
+ })
58
+
59
+ it(`returns pack for non-blocking user`, async () => {
60
+ const { data } = await agent.app.bsky.unspecced.getSuggestedStarterPacks(
61
+ undefined,
62
+ {
63
+ headers: await network.serviceHeaders(
64
+ users.viewer.did,
65
+ ids.AppBskyUnspeccedGetSuggestedStarterPacks,
66
+ ),
67
+ },
68
+ )
69
+ const sp = data.starterPacks[0]
70
+ expect(sp).toBeDefined()
71
+ })
72
+
73
+ it(`does not return pack if creator blocked by viewer`, async () => {
74
+ const { data } = await agent.app.bsky.unspecced.getSuggestedStarterPacks(
75
+ undefined,
76
+ {
77
+ headers: await network.serviceHeaders(
78
+ users.viewerBlocker.did,
79
+ ids.AppBskyUnspeccedGetSuggestedStarterPacks,
80
+ ),
81
+ },
82
+ )
83
+ const sp = data.starterPacks[0]
84
+ expect(sp).not.toBeDefined()
85
+ })
86
+ })
87
+ })
88
+
89
+ class MockServer {
90
+ app: Application
91
+ server: Server
92
+
93
+ mockedStarterPackUris = new Map<string, OutputSchema['starterPacks'][0]>()
94
+
95
+ constructor() {
96
+ this.app = this.createApp()
97
+ this.server = createServer(this.app)
98
+ }
99
+
100
+ async listen(port?: number) {
101
+ this.server.listen(port)
102
+ await once(this.server, 'listening')
103
+ }
104
+
105
+ async stop() {
106
+ this.server.close()
107
+ await once(this.server, 'close')
108
+ }
109
+
110
+ get url() {
111
+ const address = this.server.address() as AddressInfo
112
+ return `http://localhost:${address.port}`
113
+ }
114
+
115
+ private createApp() {
116
+ const app = express()
117
+ app.get(
118
+ '/xrpc/app.bsky.unspecced.getSuggestedStarterPacksSkeleton',
119
+ (req, res) => {
120
+ const skeleton: OutputSchema = {
121
+ starterPacks: Array.from(this.mockedStarterPackUris.values()),
122
+ }
123
+ return res.json(skeleton)
124
+ },
125
+ )
126
+ return app
127
+ }
128
+ }
@@ -0,0 +1,133 @@
1
+ import assert from 'node:assert'
2
+ import { once } from 'node:events'
3
+ import { Server, createServer } from 'node:http'
4
+ import { AddressInfo } from 'node:net'
5
+ import express, { Application } from 'express'
6
+ import AtpAgent from '@atproto/api'
7
+ import { SeedClient, TestNetwork } from '@atproto/dev-env'
8
+ import { ids } from '../../src/lexicon/lexicons'
9
+ import { OutputSchema } from '../../src/lexicon/types/app/bsky/unspecced/getTrendsSkeleton'
10
+ import { Users, trendsSeed } from '../seed/get-trends'
11
+
12
+ describe('getTrends', () => {
13
+ let network: TestNetwork
14
+ let agent: AtpAgent
15
+ let sc: SeedClient
16
+ let users: Users
17
+ let mockTrendServer: MockTrendsServer
18
+
19
+ beforeAll(async () => {
20
+ mockTrendServer = new MockTrendsServer()
21
+ await mockTrendServer.listen()
22
+
23
+ network = await TestNetwork.create({
24
+ dbPostgresSchema: 'bsky_tests_get_trends_test_b',
25
+ bsky: {
26
+ topicsUrl: mockTrendServer.url,
27
+ topicsApiKey: 'test',
28
+ },
29
+ })
30
+ agent = network.bsky.getClient()
31
+ sc = network.getSeedClient()
32
+
33
+ const result = await trendsSeed(sc)
34
+ users = result.users
35
+
36
+ await network.processAll()
37
+ })
38
+
39
+ afterAll(async () => {
40
+ await network.close()
41
+ await mockTrendServer.stop()
42
+ })
43
+
44
+ describe(`basic handling`, () => {
45
+ beforeAll(() => {
46
+ mockTrendServer.mockedTrendSkeletons.set('a', {
47
+ topic: 'a',
48
+ displayName: 'A',
49
+ link: '/test',
50
+ startedAt: new Date().toISOString(),
51
+ postCount: 3,
52
+ dids: [users.posterA.did, users.posterB.did, users.posterC.did],
53
+ })
54
+ })
55
+
56
+ afterAll(() => {
57
+ mockTrendServer.mockedTrendSkeletons.delete('a')
58
+ })
59
+
60
+ it(`returns all users for non-blocked user`, async () => {
61
+ const { data } = await agent.app.bsky.unspecced.getTrends(undefined, {
62
+ headers: await network.serviceHeaders(
63
+ users.viewer.did,
64
+ ids.AppBskyUnspeccedGetTrends,
65
+ ),
66
+ })
67
+ const trendA = data.trends.find((t) => t.topic === 'a')
68
+
69
+ assert(trendA)
70
+
71
+ expect(trendA.actors.map((a) => a.did)).toEqual([
72
+ users.posterA.did,
73
+ users.posterB.did,
74
+ users.posterC.did,
75
+ ])
76
+ })
77
+
78
+ it(`does not return user blocked by viewer`, async () => {
79
+ const { data } = await agent.app.bsky.unspecced.getTrends(undefined, {
80
+ headers: await network.serviceHeaders(
81
+ users.viewerBlocker.did,
82
+ ids.AppBskyUnspeccedGetTrends,
83
+ ),
84
+ })
85
+ const trendA = data.trends.find((t) => t.topic === 'a')
86
+
87
+ assert(trendA)
88
+
89
+ expect(trendA.actors.map((a) => a.did)).toEqual([
90
+ users.posterA.did,
91
+ users.posterB.did,
92
+ ])
93
+ })
94
+ })
95
+ })
96
+
97
+ class MockTrendsServer {
98
+ app: Application
99
+ server: Server
100
+
101
+ mockedTrendSkeletons = new Map<string, OutputSchema['trends'][0]>()
102
+
103
+ constructor() {
104
+ this.app = this.createApp()
105
+ this.server = createServer(this.app)
106
+ }
107
+
108
+ async listen(port?: number) {
109
+ this.server.listen(port)
110
+ await once(this.server, 'listening')
111
+ }
112
+
113
+ async stop() {
114
+ this.server.close()
115
+ await once(this.server, 'close')
116
+ }
117
+
118
+ get url() {
119
+ const address = this.server.address() as AddressInfo
120
+ return `http://localhost:${address.port}`
121
+ }
122
+
123
+ private createApp() {
124
+ const app = express()
125
+ app.get('/xrpc/app.bsky.unspecced.getTrendsSkeleton', (req, res) => {
126
+ const skeleton: OutputSchema = {
127
+ trends: Array.from(this.mockedTrendSkeletons.values()),
128
+ }
129
+ return res.json(skeleton)
130
+ })
131
+ return app
132
+ }
133
+ }